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