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