9b00bb2bf990ffdea1787a7501b9bddab47eb539
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / core / html / HTMLPlugInElement.cpp
1 /**
2  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
4  *           (C) 2000 Stefan Schimanski (1Stein@gmx.de)
5  * Copyright (C) 2004, 2005, 2006 Apple Computer, Inc.
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public License
18  * along with this library; see the file COPYING.LIB.  If not, write to
19  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  */
22
23 #include "config.h"
24 #include "core/html/HTMLPlugInElement.h"
25
26 #include "CSSPropertyNames.h"
27 #include "HTMLNames.h"
28 #include "bindings/v8/ScriptController.h"
29 #include "bindings/v8/npruntime_impl.h"
30 #include "core/dom/Document.h"
31 #include "core/dom/Node.h"
32 #include "core/dom/shadow/ShadowRoot.h"
33 #include "core/events/Event.h"
34 #include "core/frame/LocalFrame.h"
35 #include "core/frame/csp/ContentSecurityPolicy.h"
36 #include "core/html/HTMLContentElement.h"
37 #include "core/html/HTMLImageLoader.h"
38 #include "core/html/PluginDocument.h"
39 #include "core/loader/FrameLoaderClient.h"
40 #include "core/page/EventHandler.h"
41 #include "core/page/Page.h"
42 #include "core/frame/Settings.h"
43 #include "core/plugins/PluginView.h"
44 #include "core/rendering/RenderEmbeddedObject.h"
45 #include "core/rendering/RenderImage.h"
46 #include "core/rendering/RenderWidget.h"
47 #include "platform/Logging.h"
48 #include "platform/MIMETypeFromURL.h"
49 #include "platform/MIMETypeRegistry.h"
50 #include "platform/Widget.h"
51 #include "platform/plugins/PluginData.h"
52
53 namespace WebCore {
54
55 using namespace HTMLNames;
56
57 HTMLPlugInElement::HTMLPlugInElement(const QualifiedName& tagName, Document& doc, bool createdByParser, PreferPlugInsForImagesOption preferPlugInsForImagesOption)
58     : HTMLFrameOwnerElement(tagName, doc)
59     , m_isDelayingLoadEvent(false)
60     , m_NPObject(0)
61     , m_isCapturingMouseEvents(false)
62     // m_needsWidgetUpdate(!createdByParser) allows HTMLObjectElement to delay
63     // widget updates until after all children are parsed. For HTMLEmbedElement
64     // this delay is unnecessary, but it is simpler to make both classes share
65     // the same codepath in this class.
66     , m_needsWidgetUpdate(!createdByParser)
67     , m_shouldPreferPlugInsForImages(preferPlugInsForImagesOption == ShouldPreferPlugInsForImages)
68     , m_displayState(Playing)
69 {
70     setHasCustomStyleCallbacks();
71 }
72
73 HTMLPlugInElement::~HTMLPlugInElement()
74 {
75     ASSERT(!m_pluginWrapper); // cleared in detach()
76     ASSERT(!m_isDelayingLoadEvent);
77
78     if (m_NPObject) {
79         _NPN_ReleaseObject(m_NPObject);
80         m_NPObject = 0;
81     }
82 }
83
84 bool HTMLPlugInElement::canProcessDrag() const
85 {
86     return pluginWidget() && pluginWidget()->isPluginView() && toPluginView(pluginWidget())->canProcessDrag();
87 }
88
89 bool HTMLPlugInElement::willRespondToMouseClickEvents()
90 {
91     if (isDisabledFormControl())
92         return false;
93     RenderObject* r = renderer();
94     return r && (r->isEmbeddedObject() || r->isWidget());
95 }
96
97 void HTMLPlugInElement::removeAllEventListeners()
98 {
99     HTMLFrameOwnerElement::removeAllEventListeners();
100     if (RenderWidget* renderer = existingRenderWidget()) {
101         if (Widget* widget = renderer->widget())
102             widget->eventListenersRemoved();
103     }
104 }
105
106 void HTMLPlugInElement::didMoveToNewDocument(Document& oldDocument)
107 {
108     if (m_imageLoader)
109         m_imageLoader->elementDidMoveToNewDocument();
110     HTMLFrameOwnerElement::didMoveToNewDocument(oldDocument);
111 }
112
113 void HTMLPlugInElement::attach(const AttachContext& context)
114 {
115     HTMLFrameOwnerElement::attach(context);
116
117     if (!renderer() || useFallbackContent())
118         return;
119     if (isImageType()) {
120         if (!m_imageLoader)
121             m_imageLoader = adoptPtr(new HTMLImageLoader(this));
122         m_imageLoader->updateFromElement();
123     } else if (needsWidgetUpdate()
124         && renderEmbeddedObject()
125         && !renderEmbeddedObject()->showsUnavailablePluginIndicator()
126         && !wouldLoadAsNetscapePlugin(m_url, m_serviceType)
127         && !m_isDelayingLoadEvent) {
128         m_isDelayingLoadEvent = true;
129         document().incrementLoadEventDelayCount();
130         document().loadPluginsSoon();
131     }
132 }
133
134 void HTMLPlugInElement::updateWidget()
135 {
136     RefPtr<HTMLPlugInElement> protector(this);
137     updateWidgetInternal();
138     if (m_isDelayingLoadEvent) {
139         m_isDelayingLoadEvent = false;
140         document().decrementLoadEventDelayCount();
141     }
142 }
143
144 void HTMLPlugInElement::detach(const AttachContext& context)
145 {
146     // Update the widget the next time we attach (detaching destroys the plugin).
147     // FIXME: None of this "needsWidgetUpdate" related code looks right.
148     if (renderer() && !useFallbackContent())
149         setNeedsWidgetUpdate(true);
150     if (m_isDelayingLoadEvent) {
151         m_isDelayingLoadEvent = false;
152         document().decrementLoadEventDelayCount();
153     }
154
155     resetInstance();
156
157     if (m_isCapturingMouseEvents) {
158         if (LocalFrame* frame = document().frame())
159             frame->eventHandler().setCapturingMouseEventsNode(nullptr);
160         m_isCapturingMouseEvents = false;
161     }
162
163     if (m_NPObject) {
164         _NPN_ReleaseObject(m_NPObject);
165         m_NPObject = 0;
166     }
167
168     HTMLFrameOwnerElement::detach(context);
169 }
170
171 RenderObject* HTMLPlugInElement::createRenderer(RenderStyle* style)
172 {
173     // Fallback content breaks the DOM->Renderer class relationship of this
174     // class and all superclasses because createObject won't necessarily
175     // return a RenderEmbeddedObject, RenderPart or even RenderWidget.
176     if (useFallbackContent())
177         return RenderObject::createObject(this, style);
178
179     if (isImageType()) {
180         RenderImage* image = new RenderImage(this);
181         image->setImageResource(RenderImageResource::create());
182         return image;
183     }
184
185     return new RenderEmbeddedObject(this);
186 }
187
188 void HTMLPlugInElement::willRecalcStyle(StyleRecalcChange)
189 {
190     // FIXME: Why is this necessary? Manual re-attach is almost always wrong.
191     if (!useFallbackContent() && needsWidgetUpdate() && renderer() && !isImageType())
192         reattach();
193 }
194
195 void HTMLPlugInElement::finishParsingChildren()
196 {
197     HTMLFrameOwnerElement::finishParsingChildren();
198     if (useFallbackContent())
199         return;
200
201     setNeedsWidgetUpdate(true);
202     if (inDocument())
203         setNeedsStyleRecalc(SubtreeStyleChange);
204 }
205
206 void HTMLPlugInElement::resetInstance()
207 {
208     m_pluginWrapper.clear();
209 }
210
211 SharedPersistent<v8::Object>* HTMLPlugInElement::pluginWrapper()
212 {
213     LocalFrame* frame = document().frame();
214     if (!frame)
215         return 0;
216
217     // If the host dynamically turns off JavaScript (or Java) we will still
218     // return the cached allocated Bindings::Instance. Not supporting this
219     // edge-case is OK.
220     if (!m_pluginWrapper) {
221         if (Widget* widget = pluginWidget())
222             m_pluginWrapper = frame->script().createPluginWrapper(widget);
223     }
224     return m_pluginWrapper.get();
225 }
226
227 Widget* HTMLPlugInElement::pluginWidget() const
228 {
229     if (RenderWidget* renderWidget = renderWidgetForJSBindings())
230         return renderWidget->widget();
231     return 0;
232 }
233
234 bool HTMLPlugInElement::isPresentationAttribute(const QualifiedName& name) const
235 {
236     if (name == widthAttr || name == heightAttr || name == vspaceAttr || name == hspaceAttr || name == alignAttr)
237         return true;
238     return HTMLFrameOwnerElement::isPresentationAttribute(name);
239 }
240
241 void HTMLPlugInElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStylePropertySet* style)
242 {
243     if (name == widthAttr) {
244         addHTMLLengthToStyle(style, CSSPropertyWidth, value);
245     } else if (name == heightAttr) {
246         addHTMLLengthToStyle(style, CSSPropertyHeight, value);
247     } else if (name == vspaceAttr) {
248         addHTMLLengthToStyle(style, CSSPropertyMarginTop, value);
249         addHTMLLengthToStyle(style, CSSPropertyMarginBottom, value);
250     } else if (name == hspaceAttr) {
251         addHTMLLengthToStyle(style, CSSPropertyMarginLeft, value);
252         addHTMLLengthToStyle(style, CSSPropertyMarginRight, value);
253     } else if (name == alignAttr) {
254         applyAlignmentAttributeToStyle(value, style);
255     } else {
256         HTMLFrameOwnerElement::collectStyleForPresentationAttribute(name, value, style);
257     }
258 }
259
260 void HTMLPlugInElement::defaultEventHandler(Event* event)
261 {
262     // Firefox seems to use a fake event listener to dispatch events to plug-in
263     // (tested with mouse events only). This is observable via different order
264     // of events - in Firefox, event listeners specified in HTML attributes
265     // fires first, then an event gets dispatched to plug-in, and only then
266     // other event listeners fire. Hopefully, this difference does not matter in
267     // practice.
268
269     // FIXME: Mouse down and scroll events are passed down to plug-in via custom
270     // code in EventHandler; these code paths should be united.
271
272     RenderObject* r = renderer();
273     if (!r || !r->isWidget())
274         return;
275     if (r->isEmbeddedObject()) {
276         if (toRenderEmbeddedObject(r)->showsUnavailablePluginIndicator())
277             return;
278         if (displayState() < Playing)
279             return;
280     }
281     RefPtr<Widget> widget = toRenderWidget(r)->widget();
282     if (!widget)
283         return;
284     widget->handleEvent(event);
285     if (event->defaultHandled())
286         return;
287     HTMLFrameOwnerElement::defaultEventHandler(event);
288 }
289
290 RenderWidget* HTMLPlugInElement::renderWidgetForJSBindings() const
291 {
292     // Needs to load the plugin immediatedly because this function is called
293     // when JavaScript code accesses the plugin.
294     // FIXME: Check if dispatching events here is safe.
295     document().updateLayoutIgnorePendingStylesheets(Document::RunPostLayoutTasksSynchronously);
296     return existingRenderWidget();
297 }
298
299 bool HTMLPlugInElement::isKeyboardFocusable() const
300 {
301     if (!document().isActive())
302         return false;
303     return pluginWidget() && pluginWidget()->isPluginView() && toPluginView(pluginWidget())->supportsKeyboardFocus();
304 }
305
306 bool HTMLPlugInElement::hasCustomFocusLogic() const
307 {
308     return !hasAuthorShadowRoot();
309 }
310
311 bool HTMLPlugInElement::isPluginElement() const
312 {
313     return true;
314 }
315
316 bool HTMLPlugInElement::rendererIsFocusable() const
317 {
318     if (HTMLFrameOwnerElement::supportsFocus() && HTMLFrameOwnerElement::rendererIsFocusable())
319         return true;
320
321     if (useFallbackContent() || !renderer() || !renderer()->isEmbeddedObject())
322         return false;
323     return !toRenderEmbeddedObject(renderer())->showsUnavailablePluginIndicator();
324 }
325
326 NPObject* HTMLPlugInElement::getNPObject()
327 {
328     ASSERT(document().frame());
329     if (!m_NPObject)
330         m_NPObject = document().frame()->script().createScriptObjectForPluginElement(this);
331     return m_NPObject;
332 }
333
334 bool HTMLPlugInElement::isImageType()
335 {
336     if (m_serviceType.isEmpty() && protocolIs(m_url, "data"))
337         m_serviceType = mimeTypeFromDataURL(m_url);
338
339     if (LocalFrame* frame = document().frame()) {
340         KURL completedURL = document().completeURL(m_url);
341         return frame->loader().client()->objectContentType(completedURL, m_serviceType, shouldPreferPlugInsForImages()) == ObjectContentImage;
342     }
343
344     return Image::supportsType(m_serviceType);
345 }
346
347 const String HTMLPlugInElement::loadedMimeType() const
348 {
349     String mimeType = m_serviceType;
350     if (mimeType.isEmpty())
351         mimeType = mimeTypeFromURL(m_loadedUrl);
352     return mimeType;
353 }
354
355 RenderEmbeddedObject* HTMLPlugInElement::renderEmbeddedObject() const
356 {
357     // HTMLObjectElement and HTMLEmbedElement may return arbitrary renderers
358     // when using fallback content.
359     if (!renderer() || !renderer()->isEmbeddedObject())
360         return 0;
361     return toRenderEmbeddedObject(renderer());
362 }
363
364 // We don't use m_url, as it may not be the final URL that the object loads,
365 // depending on <param> values.
366 bool HTMLPlugInElement::allowedToLoadFrameURL(const String& url)
367 {
368     KURL completeURL = document().completeURL(url);
369     if (contentFrame() && protocolIsJavaScript(completeURL)
370         && !document().securityOrigin()->canAccess(contentDocument()->securityOrigin()))
371         return false;
372     return document().frame()->isURLAllowed(completeURL);
373 }
374
375 // We don't use m_url, or m_serviceType as they may not be the final values
376 // that <object> uses depending on <param> values.
377 bool HTMLPlugInElement::wouldLoadAsNetscapePlugin(const String& url, const String& serviceType)
378 {
379     ASSERT(document().frame());
380     KURL completedURL;
381     if (!url.isEmpty())
382         completedURL = document().completeURL(url);
383     return document().frame()->loader().client()->objectContentType(completedURL, serviceType, shouldPreferPlugInsForImages()) == ObjectContentNetscapePlugin;
384 }
385
386 bool HTMLPlugInElement::requestObject(const String& url, const String& mimeType, const Vector<String>& paramNames, const Vector<String>& paramValues)
387 {
388     if (url.isEmpty() && mimeType.isEmpty())
389         return false;
390
391     // FIXME: None of this code should use renderers!
392     RenderEmbeddedObject* renderer = renderEmbeddedObject();
393     ASSERT(renderer);
394     if (!renderer)
395         return false;
396
397     KURL completedURL = document().completeURL(url);
398     if (!pluginIsLoadable(completedURL, mimeType))
399         return false;
400
401     bool useFallback;
402     if (shouldUsePlugin(completedURL, mimeType, renderer->hasFallbackContent(), useFallback))
403         return loadPlugin(completedURL, mimeType, paramNames, paramValues, useFallback);
404
405     // If the plug-in element already contains a subframe,
406     // loadOrRedirectSubframe will re-use it. Otherwise, it will create a new
407     // frame and set it as the RenderPart's widget, causing what was previously
408     // in the widget to be torn down.
409     return loadOrRedirectSubframe(completedURL, getNameAttribute(), true);
410 }
411
412 bool HTMLPlugInElement::loadPlugin(const KURL& url, const String& mimeType, const Vector<String>& paramNames, const Vector<String>& paramValues, bool useFallback)
413 {
414     LocalFrame* frame = document().frame();
415
416     if (!frame->loader().allowPlugins(AboutToInstantiatePlugin))
417         return false;
418
419     RenderEmbeddedObject* renderer = renderEmbeddedObject();
420     // FIXME: This code should not depend on renderer!
421     if (!renderer || useFallback)
422         return false;
423
424     WTF_LOG(Plugins, "%p Plug-in URL: %s", this, m_url.utf8().data());
425     WTF_LOG(Plugins, "   Loaded URL: %s", url.string().utf8().data());
426     m_loadedUrl = url;
427
428     bool loadManually = document().isPluginDocument() && !document().containsPlugins() && toPluginDocument(document()).shouldLoadPluginManually();
429     RefPtr<Widget> widget = frame->loader().client()->createPlugin(this, url, paramNames, paramValues, mimeType, loadManually, FrameLoaderClient::FailOnDetachedPlugin);
430
431     if (!widget) {
432         if (!renderer->showsUnavailablePluginIndicator())
433             renderer->setPluginUnavailabilityReason(RenderEmbeddedObject::PluginMissing);
434         return false;
435     }
436
437     renderer->setWidget(widget);
438     document().setContainsPlugins();
439     scheduleLayerUpdate();
440     return true;
441 }
442
443 bool HTMLPlugInElement::shouldUsePlugin(const KURL& url, const String& mimeType, bool hasFallback, bool& useFallback)
444 {
445     // Allow other plug-ins to win over QuickTime because if the user has
446     // installed a plug-in that can handle TIFF (which QuickTime can also
447     // handle) they probably intended to override QT.
448     if (document().frame()->page() && (mimeType == "image/tiff" || mimeType == "image/tif" || mimeType == "image/x-tiff")) {
449         const PluginData* pluginData = document().frame()->page()->pluginData();
450         String pluginName = pluginData ? pluginData->pluginNameForMimeType(mimeType) : String();
451         if (!pluginName.isEmpty() && !pluginName.contains("QuickTime", false))
452             return true;
453     }
454
455     ObjectContentType objectType = document().frame()->loader().client()->objectContentType(url, mimeType, shouldPreferPlugInsForImages());
456     // If an object's content can't be handled and it has no fallback, let
457     // it be handled as a plugin to show the broken plugin icon.
458     useFallback = objectType == ObjectContentNone && hasFallback;
459     return objectType == ObjectContentNone || objectType == ObjectContentNetscapePlugin || objectType == ObjectContentOtherPlugin;
460
461 }
462
463 void HTMLPlugInElement::dispatchErrorEvent()
464 {
465     if (document().isPluginDocument() && document().ownerElement())
466         document().ownerElement()->dispatchEvent(Event::create(EventTypeNames::error));
467     else
468         dispatchEvent(Event::create(EventTypeNames::error));
469 }
470
471 bool HTMLPlugInElement::pluginIsLoadable(const KURL& url, const String& mimeType)
472 {
473     LocalFrame* frame = document().frame();
474     Settings* settings = frame->settings();
475     if (!settings)
476         return false;
477
478     if (MIMETypeRegistry::isJavaAppletMIMEType(mimeType) && !settings->javaEnabled())
479         return false;
480
481     if (document().isSandboxed(SandboxPlugins))
482         return false;
483
484     if (!document().securityOrigin()->canDisplay(url)) {
485         FrameLoader::reportLocalLoadFailed(frame, url.string());
486         return false;
487     }
488
489     AtomicString declaredMimeType = document().isPluginDocument() && document().ownerElement() ?
490         document().ownerElement()->fastGetAttribute(HTMLNames::typeAttr) :
491         fastGetAttribute(HTMLNames::typeAttr);
492     if (!document().contentSecurityPolicy()->allowObjectFromSource(url)
493         || !document().contentSecurityPolicy()->allowPluginType(mimeType, declaredMimeType, url)) {
494         renderEmbeddedObject()->setPluginUnavailabilityReason(RenderEmbeddedObject::PluginBlockedByContentSecurityPolicy);
495         return false;
496     }
497
498     return frame->loader().mixedContentChecker()->canRunInsecureContent(document().securityOrigin(), url);
499 }
500
501 void HTMLPlugInElement::didAddUserAgentShadowRoot(ShadowRoot&)
502 {
503     userAgentShadowRoot()->appendChild(HTMLContentElement::create(document()));
504 }
505
506 void HTMLPlugInElement::didAddShadowRoot(ShadowRoot& root)
507 {
508     if (root.isOldestAuthorShadowRoot())
509         lazyReattachIfAttached();
510 }
511
512 bool HTMLPlugInElement::useFallbackContent() const
513 {
514     return hasAuthorShadowRoot();
515 }
516
517 }