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 "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 
     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 
     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 bool HTMLPlugInElement::canProcessDrag() const
     86 {
     87     return pluginWidget() && pluginWidget()->isPluginView() && toPluginView(pluginWidget())->canProcessDrag();
     88 }
     89 
     90 bool HTMLPlugInElement::willRespondToMouseClickEvents()
     91 {
     92     if (isDisabledFormControl())
     93         return false;
     94     RenderObject* r = renderer();
     95     return r && (r->isEmbeddedObject() || r->isWidget());
     96 }
     97 
     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 
    107 void HTMLPlugInElement::didMoveToNewDocument(Document& oldDocument)
    108 {
    109     if (m_imageLoader)
    110         m_imageLoader->elementDidMoveToNewDocument();
    111     HTMLFrameOwnerElement::didMoveToNewDocument(oldDocument);
    112 }
    113 
    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 
    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 (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 
    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();
    204 }
    205 
    206 void HTMLPlugInElement::resetInstance()
    207 {
    208     m_pluginWrapper.clear();
    209 }
    210 
    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 
    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 
    240 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 
    254 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 
    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 
    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 
    310 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 
    319 bool HTMLPlugInElement::isKeyboardFocusable() const
    320 {
    321     if (!document().isActive())
    322         return false;
    323     return pluginWidget() && pluginWidget()->isPluginView() && toPluginView(pluginWidget())->supportsKeyboardFocus();
    324 }
    325 
    326 bool HTMLPlugInElement::hasCustomFocusLogic() const
    327 {
    328     return !hasAuthorShadowRoot();
    329 }
    330 
    331 bool HTMLPlugInElement::isPluginElement() const
    332 {
    333     return true;
    334 }
    335 
    336 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 
    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 
    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 
    367 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 
    375 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.
    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.
    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 
    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 
    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 
    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 
    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 
    515 void HTMLPlugInElement::didAddUserAgentShadowRoot(ShadowRoot&)
    516 {
    517     userAgentShadowRoot()->appendChild(HTMLContentElement::create(document()));
    518 }
    519 
    520 void HTMLPlugInElement::didAddShadowRoot(ShadowRoot& root)
    521 {
    522     if (root.isOldestAuthorShadowRoot())
    523         lazyReattachIfAttached();
    524 }
    525 
    526 bool HTMLPlugInElement::useFallbackContent() const
    527 {
    528     return hasAuthorShadowRoot();
    529 }
    530 
    531 }
    532