Home | History | Annotate | Download | only in html
      1 /**
      2  * Copyright (C) 1999 Lars Knoll (knoll (at) kde.org)
      3  *           (C) 1999 Antti Koivisto (koivisto (at) kde.org)
      4  *           (C) 2000 Stefan Schimanski (1Stein (at) 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 "bindings/v8/ScriptController.h"
     27 #include "bindings/v8/npruntime_impl.h"
     28 #include "core/CSSPropertyNames.h"
     29 #include "core/HTMLNames.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/FrameView.h"
     35 #include "core/frame/LocalFrame.h"
     36 #include "core/frame/csp/ContentSecurityPolicy.h"
     37 #include "core/html/HTMLContentElement.h"
     38 #include "core/html/HTMLImageLoader.h"
     39 #include "core/html/PluginDocument.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_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 
     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 
     85 void HTMLPlugInElement::trace(Visitor* visitor)
     86 {
     87     visitor->trace(m_imageLoader);
     88     HTMLFrameOwnerElement::trace(visitor);
     89 }
     90 
     91 bool HTMLPlugInElement::canProcessDrag() const
     92 {
     93     return pluginWidget() && pluginWidget()->isPluginView() && toPluginView(pluginWidget())->canProcessDrag();
     94 }
     95 
     96 bool HTMLPlugInElement::willRespondToMouseClickEvents()
     97 {
     98     if (isDisabledFormControl())
     99         return false;
    100     RenderObject* r = renderer();
    101     return r && (r->isEmbeddedObject() || r->isWidget());
    102 }
    103 
    104 void HTMLPlugInElement::removeAllEventListeners()
    105 {
    106     HTMLFrameOwnerElement::removeAllEventListeners();
    107     if (RenderWidget* renderer = existingRenderWidget()) {
    108         if (Widget* widget = renderer->widget())
    109             widget->eventListenersRemoved();
    110     }
    111 }
    112 
    113 void HTMLPlugInElement::didMoveToNewDocument(Document& oldDocument)
    114 {
    115     if (m_imageLoader)
    116         m_imageLoader->elementDidMoveToNewDocument();
    117     HTMLFrameOwnerElement::didMoveToNewDocument(oldDocument);
    118 }
    119 
    120 void HTMLPlugInElement::attach(const AttachContext& context)
    121 {
    122     HTMLFrameOwnerElement::attach(context);
    123 
    124     if (!renderer() || useFallbackContent())
    125         return;
    126 
    127     if (isImageType()) {
    128         if (!m_imageLoader)
    129             m_imageLoader = HTMLImageLoader::create(this);
    130         m_imageLoader->updateFromElement();
    131     } else if (needsWidgetUpdate()
    132         && renderEmbeddedObject()
    133         && !renderEmbeddedObject()->showsUnavailablePluginIndicator()
    134         && !wouldLoadAsNetscapePlugin(m_url, m_serviceType)
    135         && !m_isDelayingLoadEvent) {
    136         m_isDelayingLoadEvent = true;
    137         document().incrementLoadEventDelayCount();
    138         document().loadPluginsSoon();
    139     }
    140 }
    141 
    142 void HTMLPlugInElement::updateWidget()
    143 {
    144     RefPtrWillBeRawPtr<HTMLPlugInElement> protector(this);
    145     updateWidgetInternal();
    146     if (m_isDelayingLoadEvent) {
    147         m_isDelayingLoadEvent = false;
    148         document().decrementLoadEventDelayCount();
    149     }
    150 }
    151 
    152 void HTMLPlugInElement::requestPluginCreationWithoutRendererIfPossible()
    153 {
    154     if (m_serviceType.isEmpty())
    155         return;
    156 
    157     if (!document().frame()
    158         || !document().frame()->loader().client()->canCreatePluginWithoutRenderer(m_serviceType))
    159         return;
    160 
    161     if (renderer() && renderer()->isWidget())
    162         return;
    163 
    164     createPluginWithoutRenderer();
    165 }
    166 
    167 void HTMLPlugInElement::createPluginWithoutRenderer()
    168 {
    169     ASSERT(document().frame()->loader().client()->canCreatePluginWithoutRenderer(m_serviceType));
    170 
    171     KURL url;
    172     Vector<String> paramNames;
    173     Vector<String> paramValues;
    174 
    175     paramNames.append("type");
    176     paramValues.append(m_serviceType);
    177 
    178     bool useFallback = false;
    179     loadPlugin(url, m_serviceType, paramNames, paramValues, useFallback, false);
    180 }
    181 
    182 bool HTMLPlugInElement::shouldAccelerate() const
    183 {
    184     if (Widget* widget = ownedWidget())
    185         return widget->isPluginView() && toPluginView(widget)->platformLayer();
    186     return false;
    187 }
    188 
    189 void HTMLPlugInElement::detach(const AttachContext& context)
    190 {
    191     // Update the widget the next time we attach (detaching destroys the plugin).
    192     // FIXME: None of this "needsWidgetUpdate" related code looks right.
    193     if (renderer() && !useFallbackContent())
    194         setNeedsWidgetUpdate(true);
    195     if (m_isDelayingLoadEvent) {
    196         m_isDelayingLoadEvent = false;
    197         document().decrementLoadEventDelayCount();
    198     }
    199 
    200     // Only try to persist a plugin widget we actually own.
    201     Widget* plugin = ownedWidget();
    202     if (plugin && plugin->pluginShouldPersist())
    203         m_persistedPluginWidget = plugin;
    204     resetInstance();
    205     // FIXME - is this next line necessary?
    206     setWidget(nullptr);
    207 
    208     if (m_isCapturingMouseEvents) {
    209         if (LocalFrame* frame = document().frame())
    210             frame->eventHandler().setCapturingMouseEventsNode(nullptr);
    211         m_isCapturingMouseEvents = false;
    212     }
    213 
    214     if (m_NPObject) {
    215         _NPN_ReleaseObject(m_NPObject);
    216         m_NPObject = 0;
    217     }
    218 
    219     HTMLFrameOwnerElement::detach(context);
    220 }
    221 
    222 RenderObject* HTMLPlugInElement::createRenderer(RenderStyle* style)
    223 {
    224     // Fallback content breaks the DOM->Renderer class relationship of this
    225     // class and all superclasses because createObject won't necessarily
    226     // return a RenderEmbeddedObject, RenderPart or even RenderWidget.
    227     if (useFallbackContent())
    228         return RenderObject::createObject(this, style);
    229 
    230     if (isImageType()) {
    231         RenderImage* image = new RenderImage(this);
    232         image->setImageResource(RenderImageResource::create());
    233         return image;
    234     }
    235 
    236     return new RenderEmbeddedObject(this);
    237 }
    238 
    239 void HTMLPlugInElement::willRecalcStyle(StyleRecalcChange)
    240 {
    241     // FIXME: Why is this necessary? Manual re-attach is almost always wrong.
    242     if (!useFallbackContent() && needsWidgetUpdate() && renderer() && !isImageType())
    243         reattach();
    244 }
    245 
    246 void HTMLPlugInElement::finishParsingChildren()
    247 {
    248     HTMLFrameOwnerElement::finishParsingChildren();
    249     if (useFallbackContent())
    250         return;
    251 
    252     setNeedsWidgetUpdate(true);
    253     if (inDocument())
    254         setNeedsStyleRecalc(SubtreeStyleChange);
    255 }
    256 
    257 void HTMLPlugInElement::resetInstance()
    258 {
    259     m_pluginWrapper.clear();
    260 }
    261 
    262 SharedPersistent<v8::Object>* HTMLPlugInElement::pluginWrapper()
    263 {
    264     LocalFrame* frame = document().frame();
    265     if (!frame)
    266         return 0;
    267 
    268     // If the host dynamically turns off JavaScript (or Java) we will still
    269     // return the cached allocated Bindings::Instance. Not supporting this
    270     // edge-case is OK.
    271     if (!m_pluginWrapper) {
    272         Widget* plugin;
    273 
    274         if (m_persistedPluginWidget)
    275             plugin = m_persistedPluginWidget.get();
    276         else
    277             plugin = pluginWidget();
    278 
    279         if (plugin)
    280             m_pluginWrapper = frame->script().createPluginWrapper(plugin);
    281     }
    282     return m_pluginWrapper.get();
    283 }
    284 
    285 Widget* HTMLPlugInElement::pluginWidget() const
    286 {
    287     if (RenderWidget* renderWidget = renderWidgetForJSBindings())
    288         return renderWidget->widget();
    289     return 0;
    290 }
    291 
    292 bool HTMLPlugInElement::isPresentationAttribute(const QualifiedName& name) const
    293 {
    294     if (name == widthAttr || name == heightAttr || name == vspaceAttr || name == hspaceAttr || name == alignAttr)
    295         return true;
    296     return HTMLFrameOwnerElement::isPresentationAttribute(name);
    297 }
    298 
    299 void HTMLPlugInElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStylePropertySet* style)
    300 {
    301     if (name == widthAttr) {
    302         addHTMLLengthToStyle(style, CSSPropertyWidth, value);
    303     } else if (name == heightAttr) {
    304         addHTMLLengthToStyle(style, CSSPropertyHeight, value);
    305     } else if (name == vspaceAttr) {
    306         addHTMLLengthToStyle(style, CSSPropertyMarginTop, value);
    307         addHTMLLengthToStyle(style, CSSPropertyMarginBottom, value);
    308     } else if (name == hspaceAttr) {
    309         addHTMLLengthToStyle(style, CSSPropertyMarginLeft, value);
    310         addHTMLLengthToStyle(style, CSSPropertyMarginRight, value);
    311     } else if (name == alignAttr) {
    312         applyAlignmentAttributeToStyle(value, style);
    313     } else {
    314         HTMLFrameOwnerElement::collectStyleForPresentationAttribute(name, value, style);
    315     }
    316 }
    317 
    318 void HTMLPlugInElement::defaultEventHandler(Event* event)
    319 {
    320     // Firefox seems to use a fake event listener to dispatch events to plug-in
    321     // (tested with mouse events only). This is observable via different order
    322     // of events - in Firefox, event listeners specified in HTML attributes
    323     // fires first, then an event gets dispatched to plug-in, and only then
    324     // other event listeners fire. Hopefully, this difference does not matter in
    325     // practice.
    326 
    327     // FIXME: Mouse down and scroll events are passed down to plug-in via custom
    328     // code in EventHandler; these code paths should be united.
    329 
    330     RenderObject* r = renderer();
    331     if (!r || !r->isWidget())
    332         return;
    333     if (r->isEmbeddedObject()) {
    334         if (toRenderEmbeddedObject(r)->showsUnavailablePluginIndicator())
    335             return;
    336         if (displayState() < Playing)
    337             return;
    338     }
    339     RefPtr<Widget> widget = toRenderWidget(r)->widget();
    340     if (!widget)
    341         return;
    342     widget->handleEvent(event);
    343     if (event->defaultHandled())
    344         return;
    345     HTMLFrameOwnerElement::defaultEventHandler(event);
    346 }
    347 
    348 RenderWidget* HTMLPlugInElement::renderWidgetForJSBindings() const
    349 {
    350     // Needs to load the plugin immediatedly because this function is called
    351     // when JavaScript code accesses the plugin.
    352     // FIXME: Check if dispatching events here is safe.
    353     document().updateLayoutIgnorePendingStylesheets(Document::RunPostLayoutTasksSynchronously);
    354     return existingRenderWidget();
    355 }
    356 
    357 bool HTMLPlugInElement::isKeyboardFocusable() const
    358 {
    359     if (!document().isActive())
    360         return false;
    361     return pluginWidget() && pluginWidget()->isPluginView() && toPluginView(pluginWidget())->supportsKeyboardFocus();
    362 }
    363 
    364 bool HTMLPlugInElement::hasCustomFocusLogic() const
    365 {
    366     return !hasAuthorShadowRoot();
    367 }
    368 
    369 bool HTMLPlugInElement::isPluginElement() const
    370 {
    371     return true;
    372 }
    373 
    374 bool HTMLPlugInElement::rendererIsFocusable() const
    375 {
    376     if (HTMLFrameOwnerElement::supportsFocus() && HTMLFrameOwnerElement::rendererIsFocusable())
    377         return true;
    378 
    379     if (useFallbackContent() || !renderer() || !renderer()->isEmbeddedObject())
    380         return false;
    381     return !toRenderEmbeddedObject(renderer())->showsUnavailablePluginIndicator();
    382 }
    383 
    384 NPObject* HTMLPlugInElement::getNPObject()
    385 {
    386     ASSERT(document().frame());
    387     if (!m_NPObject)
    388         m_NPObject = document().frame()->script().createScriptObjectForPluginElement(this);
    389     return m_NPObject;
    390 }
    391 
    392 bool HTMLPlugInElement::isImageType()
    393 {
    394     if (m_serviceType.isEmpty() && protocolIs(m_url, "data"))
    395         m_serviceType = mimeTypeFromDataURL(m_url);
    396 
    397     if (LocalFrame* frame = document().frame()) {
    398         KURL completedURL = document().completeURL(m_url);
    399         return frame->loader().client()->objectContentType(completedURL, m_serviceType, shouldPreferPlugInsForImages()) == ObjectContentImage;
    400     }
    401 
    402     return Image::supportsType(m_serviceType);
    403 }
    404 
    405 RenderEmbeddedObject* HTMLPlugInElement::renderEmbeddedObject() const
    406 {
    407     // HTMLObjectElement and HTMLEmbedElement may return arbitrary renderers
    408     // when using fallback content.
    409     if (!renderer() || !renderer()->isEmbeddedObject())
    410         return 0;
    411     return toRenderEmbeddedObject(renderer());
    412 }
    413 
    414 // We don't use m_url, as it may not be the final URL that the object loads,
    415 // depending on <param> values.
    416 bool HTMLPlugInElement::allowedToLoadFrameURL(const String& url)
    417 {
    418     KURL completeURL = document().completeURL(url);
    419     if (contentFrame() && protocolIsJavaScript(completeURL)
    420         && !document().securityOrigin()->canAccess(contentDocument()->securityOrigin()))
    421         return false;
    422     return document().frame()->isURLAllowed(completeURL);
    423 }
    424 
    425 // We don't use m_url, or m_serviceType as they may not be the final values
    426 // that <object> uses depending on <param> values.
    427 bool HTMLPlugInElement::wouldLoadAsNetscapePlugin(const String& url, const String& serviceType)
    428 {
    429     ASSERT(document().frame());
    430     KURL completedURL;
    431     if (!url.isEmpty())
    432         completedURL = document().completeURL(url);
    433     return document().frame()->loader().client()->objectContentType(completedURL, serviceType, shouldPreferPlugInsForImages()) == ObjectContentNetscapePlugin;
    434 }
    435 
    436 bool HTMLPlugInElement::requestObject(const String& url, const String& mimeType, const Vector<String>& paramNames, const Vector<String>& paramValues)
    437 {
    438     if (url.isEmpty() && mimeType.isEmpty())
    439         return false;
    440 
    441     // FIXME: None of this code should use renderers!
    442     RenderEmbeddedObject* renderer = renderEmbeddedObject();
    443     ASSERT(renderer);
    444     if (!renderer)
    445         return false;
    446 
    447     KURL completedURL = document().completeURL(url);
    448     if (!pluginIsLoadable(completedURL, mimeType))
    449         return false;
    450 
    451     bool useFallback;
    452     bool requireRenderer = true;
    453     if (shouldUsePlugin(completedURL, mimeType, hasFallbackContent(), useFallback))
    454         return loadPlugin(completedURL, mimeType, paramNames, paramValues, useFallback, requireRenderer);
    455 
    456     // If the plug-in element already contains a subframe,
    457     // loadOrRedirectSubframe will re-use it. Otherwise, it will create a new
    458     // frame and set it as the RenderPart's widget, causing what was previously
    459     // in the widget to be torn down.
    460     return loadOrRedirectSubframe(completedURL, getNameAttribute(), true);
    461 }
    462 
    463 bool HTMLPlugInElement::loadPlugin(const KURL& url, const String& mimeType, const Vector<String>& paramNames, const Vector<String>& paramValues, bool useFallback, bool requireRenderer)
    464 {
    465     LocalFrame* frame = document().frame();
    466 
    467     if (!frame->loader().allowPlugins(AboutToInstantiatePlugin))
    468         return false;
    469 
    470     RenderEmbeddedObject* renderer = renderEmbeddedObject();
    471     // FIXME: This code should not depend on renderer!
    472     if ((!renderer && requireRenderer) || useFallback)
    473         return false;
    474 
    475     WTF_LOG(Plugins, "%p Plug-in URL: %s", this, m_url.utf8().data());
    476     WTF_LOG(Plugins, "   Loaded URL: %s", url.string().utf8().data());
    477     m_loadedUrl = url;
    478 
    479     RefPtr<Widget> widget = m_persistedPluginWidget;
    480     if (!widget) {
    481         bool loadManually = document().isPluginDocument() && !document().containsPlugins() && toPluginDocument(document()).shouldLoadPluginManually();
    482         FrameLoaderClient::DetachedPluginPolicy policy = requireRenderer ? FrameLoaderClient::FailOnDetachedPlugin : FrameLoaderClient::AllowDetachedPlugin;
    483         widget = frame->loader().client()->createPlugin(this, url, paramNames, paramValues, mimeType, loadManually, policy);
    484     }
    485 
    486     if (!widget) {
    487         if (renderer && !renderer->showsUnavailablePluginIndicator())
    488             renderer->setPluginUnavailabilityReason(RenderEmbeddedObject::PluginMissing);
    489         return false;
    490     }
    491 
    492     if (renderer) {
    493         setWidget(widget);
    494         m_persistedPluginWidget = nullptr;
    495     } else if (widget != m_persistedPluginWidget) {
    496         m_persistedPluginWidget = widget;
    497     }
    498     document().setContainsPlugins();
    499     scheduleSVGFilterLayerUpdateHack();
    500     return true;
    501 }
    502 
    503 bool HTMLPlugInElement::shouldUsePlugin(const KURL& url, const String& mimeType, bool hasFallback, bool& useFallback)
    504 {
    505     // Allow other plug-ins to win over QuickTime because if the user has
    506     // installed a plug-in that can handle TIFF (which QuickTime can also
    507     // handle) they probably intended to override QT.
    508     if (document().frame()->page() && (mimeType == "image/tiff" || mimeType == "image/tif" || mimeType == "image/x-tiff")) {
    509         const PluginData* pluginData = document().frame()->page()->pluginData();
    510         String pluginName = pluginData ? pluginData->pluginNameForMimeType(mimeType) : String();
    511         if (!pluginName.isEmpty() && !pluginName.contains("QuickTime", false))
    512             return true;
    513     }
    514 
    515     ObjectContentType objectType = document().frame()->loader().client()->objectContentType(url, mimeType, shouldPreferPlugInsForImages());
    516     // If an object's content can't be handled and it has no fallback, let
    517     // it be handled as a plugin to show the broken plugin icon.
    518     useFallback = objectType == ObjectContentNone && hasFallback;
    519     return objectType == ObjectContentNone || objectType == ObjectContentNetscapePlugin || objectType == ObjectContentOtherPlugin;
    520 
    521 }
    522 
    523 void HTMLPlugInElement::dispatchErrorEvent()
    524 {
    525     if (document().isPluginDocument() && document().ownerElement())
    526         document().ownerElement()->dispatchEvent(Event::create(EventTypeNames::error));
    527     else
    528         dispatchEvent(Event::create(EventTypeNames::error));
    529 }
    530 
    531 bool HTMLPlugInElement::pluginIsLoadable(const KURL& url, const String& mimeType)
    532 {
    533     LocalFrame* frame = document().frame();
    534     Settings* settings = frame->settings();
    535     if (!settings)
    536         return false;
    537 
    538     if (MIMETypeRegistry::isJavaAppletMIMEType(mimeType) && !settings->javaEnabled())
    539         return false;
    540 
    541     if (document().isSandboxed(SandboxPlugins))
    542         return false;
    543 
    544     if (!document().securityOrigin()->canDisplay(url)) {
    545         FrameLoader::reportLocalLoadFailed(frame, url.string());
    546         return false;
    547     }
    548 
    549     AtomicString declaredMimeType = document().isPluginDocument() && document().ownerElement() ?
    550         document().ownerElement()->fastGetAttribute(HTMLNames::typeAttr) :
    551         fastGetAttribute(HTMLNames::typeAttr);
    552     if (!document().contentSecurityPolicy()->allowObjectFromSource(url)
    553         || !document().contentSecurityPolicy()->allowPluginType(mimeType, declaredMimeType, url)) {
    554         renderEmbeddedObject()->setPluginUnavailabilityReason(RenderEmbeddedObject::PluginBlockedByContentSecurityPolicy);
    555         return false;
    556     }
    557 
    558     return frame->loader().mixedContentChecker()->canRunInsecureContent(document().securityOrigin(), url);
    559 }
    560 
    561 void HTMLPlugInElement::didAddUserAgentShadowRoot(ShadowRoot&)
    562 {
    563     userAgentShadowRoot()->appendChild(HTMLContentElement::create(document()));
    564 }
    565 
    566 void HTMLPlugInElement::willAddFirstAuthorShadowRoot()
    567 {
    568     lazyReattachIfAttached();
    569 }
    570 
    571 bool HTMLPlugInElement::hasFallbackContent() const
    572 {
    573     return false;
    574 }
    575 
    576 bool HTMLPlugInElement::useFallbackContent() const
    577 {
    578     return hasAuthorShadowRoot();
    579 }
    580 
    581 }
    582