root/Source/core/html/HTMLPlugInElement.cpp

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. m_displayState
  2. canProcessDrag
  3. willRespondToMouseClickEvents
  4. removeAllEventListeners
  5. didMoveToNewDocument
  6. attach
  7. updateWidget
  8. requestPluginCreationWithoutRendererIfPossible
  9. createPluginWithoutRenderer
  10. detach
  11. createRenderer
  12. willRecalcStyle
  13. finishParsingChildren
  14. resetInstance
  15. pluginWrapper
  16. pluginWidget
  17. isPresentationAttribute
  18. collectStyleForPresentationAttribute
  19. defaultEventHandler
  20. renderWidgetForJSBindings
  21. isKeyboardFocusable
  22. hasCustomFocusLogic
  23. isPluginElement
  24. rendererIsFocusable
  25. getNPObject
  26. isImageType
  27. loadedMimeType
  28. renderEmbeddedObject
  29. allowedToLoadFrameURL
  30. wouldLoadAsNetscapePlugin
  31. requestObject
  32. loadPlugin
  33. shouldUsePlugin
  34. dispatchErrorEvent
  35. pluginIsLoadable
  36. didAddUserAgentShadowRoot
  37. didAddShadowRoot
  38. useFallbackContent

/**
 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
 *           (C) 1999 Antti Koivisto (koivisto@kde.org)
 *           (C) 2000 Stefan Schimanski (1Stein@gmx.de)
 * Copyright (C) 2004, 2005, 2006 Apple Computer, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include "config.h"
#include "core/html/HTMLPlugInElement.h"

#include "CSSPropertyNames.h"
#include "HTMLNames.h"
#include "bindings/v8/ScriptController.h"
#include "bindings/v8/npruntime_impl.h"
#include "core/dom/Document.h"
#include "core/dom/Node.h"
#include "core/dom/shadow/ShadowRoot.h"
#include "core/events/Event.h"
#include "core/frame/FrameView.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/csp/ContentSecurityPolicy.h"
#include "core/html/HTMLContentElement.h"
#include "core/html/HTMLImageLoader.h"
#include "core/html/PluginDocument.h"
#include "core/loader/FrameLoaderClient.h"
#include "core/page/EventHandler.h"
#include "core/page/Page.h"
#include "core/frame/Settings.h"
#include "core/plugins/PluginView.h"
#include "core/rendering/RenderEmbeddedObject.h"
#include "core/rendering/RenderImage.h"
#include "core/rendering/RenderWidget.h"
#include "platform/Logging.h"
#include "platform/MIMETypeFromURL.h"
#include "platform/MIMETypeRegistry.h"
#include "platform/Widget.h"
#include "platform/plugins/PluginData.h"

namespace WebCore {

using namespace HTMLNames;

HTMLPlugInElement::HTMLPlugInElement(const QualifiedName& tagName, Document& doc, bool createdByParser, PreferPlugInsForImagesOption preferPlugInsForImagesOption)
    : HTMLFrameOwnerElement(tagName, doc)
    , m_isDelayingLoadEvent(false)
    , m_NPObject(0)
    , m_isCapturingMouseEvents(false)
    // m_needsWidgetUpdate(!createdByParser) allows HTMLObjectElement to delay
    // widget updates until after all children are parsed. For HTMLEmbedElement
    // this delay is unnecessary, but it is simpler to make both classes share
    // the same codepath in this class.
    , m_needsWidgetUpdate(!createdByParser)
    , m_shouldPreferPlugInsForImages(preferPlugInsForImagesOption == ShouldPreferPlugInsForImages)
    , m_displayState(Playing)
{
    setHasCustomStyleCallbacks();
}

HTMLPlugInElement::~HTMLPlugInElement()
{
    ASSERT(!m_pluginWrapper); // cleared in detach()
    ASSERT(!m_isDelayingLoadEvent);

    if (m_NPObject) {
        _NPN_ReleaseObject(m_NPObject);
        m_NPObject = 0;
    }
}

bool HTMLPlugInElement::canProcessDrag() const
{
    return pluginWidget() && pluginWidget()->isPluginView() && toPluginView(pluginWidget())->canProcessDrag();
}

bool HTMLPlugInElement::willRespondToMouseClickEvents()
{
    if (isDisabledFormControl())
        return false;
    RenderObject* r = renderer();
    return r && (r->isEmbeddedObject() || r->isWidget());
}

void HTMLPlugInElement::removeAllEventListeners()
{
    HTMLFrameOwnerElement::removeAllEventListeners();
    if (RenderWidget* renderer = existingRenderWidget()) {
        if (Widget* widget = renderer->widget())
            widget->eventListenersRemoved();
    }
}

void HTMLPlugInElement::didMoveToNewDocument(Document& oldDocument)
{
    if (m_imageLoader)
        m_imageLoader->elementDidMoveToNewDocument();
    HTMLFrameOwnerElement::didMoveToNewDocument(oldDocument);
}

void HTMLPlugInElement::attach(const AttachContext& context)
{
    HTMLFrameOwnerElement::attach(context);

    if (!renderer() || useFallbackContent())
        return;

    if (isImageType()) {
        if (!m_imageLoader)
            m_imageLoader = adoptPtr(new HTMLImageLoader(this));
        m_imageLoader->updateFromElement();
    } else if (needsWidgetUpdate()
        && renderEmbeddedObject()
        && !renderEmbeddedObject()->showsUnavailablePluginIndicator()
        && !wouldLoadAsNetscapePlugin(m_url, m_serviceType)
        && !m_isDelayingLoadEvent) {
        m_isDelayingLoadEvent = true;
        document().incrementLoadEventDelayCount();
        document().loadPluginsSoon();
    }
}

void HTMLPlugInElement::updateWidget()
{
    RefPtr<HTMLPlugInElement> protector(this);
    updateWidgetInternal();
    if (m_isDelayingLoadEvent) {
        m_isDelayingLoadEvent = false;
        document().decrementLoadEventDelayCount();
    }
}

void HTMLPlugInElement::requestPluginCreationWithoutRendererIfPossible()
{
    if (m_serviceType.isEmpty())
        return;

    if (!document().frame()->loader().client()->canCreatePluginWithoutRenderer(m_serviceType))
        return;

    if (renderer() && renderer()->isWidget())
        return;

    createPluginWithoutRenderer();
}

void HTMLPlugInElement::createPluginWithoutRenderer()
{
    ASSERT(document().frame()->loader().client()->canCreatePluginWithoutRenderer(m_serviceType));

    KURL url;
    Vector<String> paramNames;
    Vector<String> paramValues;

    paramNames.append("type");
    paramValues.append(m_serviceType);

    bool useFallback = false;
    loadPlugin(url, m_serviceType, paramNames, paramValues, useFallback, false);
}

void HTMLPlugInElement::detach(const AttachContext& context)
{
    // Update the widget the next time we attach (detaching destroys the plugin).
    // FIXME: None of this "needsWidgetUpdate" related code looks right.
    if (renderer() && !useFallbackContent())
        setNeedsWidgetUpdate(true);
    if (m_isDelayingLoadEvent) {
        m_isDelayingLoadEvent = false;
        document().decrementLoadEventDelayCount();
    }

    // Only try to persist a plugin widget we actually own.
    Widget* plugin = ownedWidget();
    if (plugin && plugin->pluginShouldPersist())
        m_persistedPluginWidget = plugin;
    resetInstance();
    // FIXME - is this next line necessary?
    setWidget(nullptr);

    if (m_isCapturingMouseEvents) {
        if (LocalFrame* frame = document().frame())
            frame->eventHandler().setCapturingMouseEventsNode(nullptr);
        m_isCapturingMouseEvents = false;
    }

    if (m_NPObject) {
        _NPN_ReleaseObject(m_NPObject);
        m_NPObject = 0;
    }

    HTMLFrameOwnerElement::detach(context);
}

RenderObject* HTMLPlugInElement::createRenderer(RenderStyle* style)
{
    // Fallback content breaks the DOM->Renderer class relationship of this
    // class and all superclasses because createObject won't necessarily
    // return a RenderEmbeddedObject, RenderPart or even RenderWidget.
    if (useFallbackContent())
        return RenderObject::createObject(this, style);

    if (isImageType()) {
        RenderImage* image = new RenderImage(this);
        image->setImageResource(RenderImageResource::create());
        return image;
    }

    return new RenderEmbeddedObject(this);
}

void HTMLPlugInElement::willRecalcStyle(StyleRecalcChange)
{
    // FIXME: Why is this necessary? Manual re-attach is almost always wrong.
    if (!useFallbackContent() && needsWidgetUpdate() && renderer() && !isImageType())
        reattach();
}

void HTMLPlugInElement::finishParsingChildren()
{
    HTMLFrameOwnerElement::finishParsingChildren();
    if (useFallbackContent())
        return;

    setNeedsWidgetUpdate(true);
    if (inDocument())
        setNeedsStyleRecalc(SubtreeStyleChange);
}

void HTMLPlugInElement::resetInstance()
{
    m_pluginWrapper.clear();
}

SharedPersistent<v8::Object>* HTMLPlugInElement::pluginWrapper()
{
    LocalFrame* frame = document().frame();
    if (!frame)
        return 0;

    // If the host dynamically turns off JavaScript (or Java) we will still
    // return the cached allocated Bindings::Instance. Not supporting this
    // edge-case is OK.
    if (!m_pluginWrapper) {
        Widget* plugin;

        if (m_persistedPluginWidget)
            plugin = m_persistedPluginWidget.get();
        else
            plugin = pluginWidget();

        if (plugin)
            m_pluginWrapper = frame->script().createPluginWrapper(plugin);
    }
    return m_pluginWrapper.get();
}

Widget* HTMLPlugInElement::pluginWidget() const
{
    if (RenderWidget* renderWidget = renderWidgetForJSBindings())
        return renderWidget->widget();
    return 0;
}

bool HTMLPlugInElement::isPresentationAttribute(const QualifiedName& name) const
{
    if (name == widthAttr || name == heightAttr || name == vspaceAttr || name == hspaceAttr || name == alignAttr)
        return true;
    return HTMLFrameOwnerElement::isPresentationAttribute(name);
}

void HTMLPlugInElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStylePropertySet* style)
{
    if (name == widthAttr) {
        addHTMLLengthToStyle(style, CSSPropertyWidth, value);
    } else if (name == heightAttr) {
        addHTMLLengthToStyle(style, CSSPropertyHeight, value);
    } else if (name == vspaceAttr) {
        addHTMLLengthToStyle(style, CSSPropertyMarginTop, value);
        addHTMLLengthToStyle(style, CSSPropertyMarginBottom, value);
    } else if (name == hspaceAttr) {
        addHTMLLengthToStyle(style, CSSPropertyMarginLeft, value);
        addHTMLLengthToStyle(style, CSSPropertyMarginRight, value);
    } else if (name == alignAttr) {
        applyAlignmentAttributeToStyle(value, style);
    } else {
        HTMLFrameOwnerElement::collectStyleForPresentationAttribute(name, value, style);
    }
}

void HTMLPlugInElement::defaultEventHandler(Event* event)
{
    // Firefox seems to use a fake event listener to dispatch events to plug-in
    // (tested with mouse events only). This is observable via different order
    // of events - in Firefox, event listeners specified in HTML attributes
    // fires first, then an event gets dispatched to plug-in, and only then
    // other event listeners fire. Hopefully, this difference does not matter in
    // practice.

    // FIXME: Mouse down and scroll events are passed down to plug-in via custom
    // code in EventHandler; these code paths should be united.

    RenderObject* r = renderer();
    if (!r || !r->isWidget())
        return;
    if (r->isEmbeddedObject()) {
        if (toRenderEmbeddedObject(r)->showsUnavailablePluginIndicator())
            return;
        if (displayState() < Playing)
            return;
    }
    RefPtr<Widget> widget = toRenderWidget(r)->widget();
    if (!widget)
        return;
    widget->handleEvent(event);
    if (event->defaultHandled())
        return;
    HTMLFrameOwnerElement::defaultEventHandler(event);
}

RenderWidget* HTMLPlugInElement::renderWidgetForJSBindings() const
{
    // Needs to load the plugin immediatedly because this function is called
    // when JavaScript code accesses the plugin.
    // FIXME: Check if dispatching events here is safe.
    document().updateLayoutIgnorePendingStylesheets(Document::RunPostLayoutTasksSynchronously);
    return existingRenderWidget();
}

bool HTMLPlugInElement::isKeyboardFocusable() const
{
    if (!document().isActive())
        return false;
    return pluginWidget() && pluginWidget()->isPluginView() && toPluginView(pluginWidget())->supportsKeyboardFocus();
}

bool HTMLPlugInElement::hasCustomFocusLogic() const
{
    return !hasAuthorShadowRoot();
}

bool HTMLPlugInElement::isPluginElement() const
{
    return true;
}

bool HTMLPlugInElement::rendererIsFocusable() const
{
    if (HTMLFrameOwnerElement::supportsFocus() && HTMLFrameOwnerElement::rendererIsFocusable())
        return true;

    if (useFallbackContent() || !renderer() || !renderer()->isEmbeddedObject())
        return false;
    return !toRenderEmbeddedObject(renderer())->showsUnavailablePluginIndicator();
}

NPObject* HTMLPlugInElement::getNPObject()
{
    ASSERT(document().frame());
    if (!m_NPObject)
        m_NPObject = document().frame()->script().createScriptObjectForPluginElement(this);
    return m_NPObject;
}

bool HTMLPlugInElement::isImageType()
{
    if (m_serviceType.isEmpty() && protocolIs(m_url, "data"))
        m_serviceType = mimeTypeFromDataURL(m_url);

    if (LocalFrame* frame = document().frame()) {
        KURL completedURL = document().completeURL(m_url);
        return frame->loader().client()->objectContentType(completedURL, m_serviceType, shouldPreferPlugInsForImages()) == ObjectContentImage;
    }

    return Image::supportsType(m_serviceType);
}

const String HTMLPlugInElement::loadedMimeType() const
{
    String mimeType = m_serviceType;
    if (mimeType.isEmpty())
        mimeType = mimeTypeFromURL(m_loadedUrl);
    return mimeType;
}

RenderEmbeddedObject* HTMLPlugInElement::renderEmbeddedObject() const
{
    // HTMLObjectElement and HTMLEmbedElement may return arbitrary renderers
    // when using fallback content.
    if (!renderer() || !renderer()->isEmbeddedObject())
        return 0;
    return toRenderEmbeddedObject(renderer());
}

// We don't use m_url, as it may not be the final URL that the object loads,
// depending on <param> values.
bool HTMLPlugInElement::allowedToLoadFrameURL(const String& url)
{
    KURL completeURL = document().completeURL(url);
    if (contentFrame() && protocolIsJavaScript(completeURL)
        && !document().securityOrigin()->canAccess(contentDocument()->securityOrigin()))
        return false;
    return document().frame()->isURLAllowed(completeURL);
}

// We don't use m_url, or m_serviceType as they may not be the final values
// that <object> uses depending on <param> values.
bool HTMLPlugInElement::wouldLoadAsNetscapePlugin(const String& url, const String& serviceType)
{
    ASSERT(document().frame());
    KURL completedURL;
    if (!url.isEmpty())
        completedURL = document().completeURL(url);
    return document().frame()->loader().client()->objectContentType(completedURL, serviceType, shouldPreferPlugInsForImages()) == ObjectContentNetscapePlugin;
}

bool HTMLPlugInElement::requestObject(const String& url, const String& mimeType, const Vector<String>& paramNames, const Vector<String>& paramValues)
{
    if (url.isEmpty() && mimeType.isEmpty())
        return false;

    // FIXME: None of this code should use renderers!
    RenderEmbeddedObject* renderer = renderEmbeddedObject();
    ASSERT(renderer);
    if (!renderer)
        return false;

    KURL completedURL = document().completeURL(url);
    if (!pluginIsLoadable(completedURL, mimeType))
        return false;

    bool useFallback;
    bool requireRenderer = true;
    if (shouldUsePlugin(completedURL, mimeType, renderer->hasFallbackContent(), useFallback))
        return loadPlugin(completedURL, mimeType, paramNames, paramValues, useFallback, requireRenderer);

    // If the plug-in element already contains a subframe,
    // loadOrRedirectSubframe will re-use it. Otherwise, it will create a new
    // frame and set it as the RenderPart's widget, causing what was previously
    // in the widget to be torn down.
    return loadOrRedirectSubframe(completedURL, getNameAttribute(), true);
}

bool HTMLPlugInElement::loadPlugin(const KURL& url, const String& mimeType, const Vector<String>& paramNames, const Vector<String>& paramValues, bool useFallback, bool requireRenderer)
{
    LocalFrame* frame = document().frame();

    if (!frame->loader().allowPlugins(AboutToInstantiatePlugin))
        return false;

    RenderEmbeddedObject* renderer = renderEmbeddedObject();
    // FIXME: This code should not depend on renderer!
    if ((!renderer && requireRenderer) || useFallback)
        return false;

    WTF_LOG(Plugins, "%p Plug-in URL: %s", this, m_url.utf8().data());
    WTF_LOG(Plugins, "   Loaded URL: %s", url.string().utf8().data());
    m_loadedUrl = url;

    RefPtr<Widget> widget = m_persistedPluginWidget;
    if (!widget) {
        bool loadManually = document().isPluginDocument() && !document().containsPlugins() && toPluginDocument(document()).shouldLoadPluginManually();
        FrameLoaderClient::DetachedPluginPolicy policy = requireRenderer ? FrameLoaderClient::FailOnDetachedPlugin : FrameLoaderClient::AllowDetachedPlugin;
        widget = frame->loader().client()->createPlugin(this, url, paramNames, paramValues, mimeType, loadManually, policy);
    }

    if (!widget) {
        if (renderer && !renderer->showsUnavailablePluginIndicator())
            renderer->setPluginUnavailabilityReason(RenderEmbeddedObject::PluginMissing);
        return false;
    }

    if (renderer) {
        setWidget(widget);
        m_persistedPluginWidget = nullptr;
    } else if (widget != m_persistedPluginWidget) {
        m_persistedPluginWidget = widget;
    }
    document().setContainsPlugins();
    scheduleLayerUpdate();
    return true;
}

bool HTMLPlugInElement::shouldUsePlugin(const KURL& url, const String& mimeType, bool hasFallback, bool& useFallback)
{
    // Allow other plug-ins to win over QuickTime because if the user has
    // installed a plug-in that can handle TIFF (which QuickTime can also
    // handle) they probably intended to override QT.
    if (document().frame()->page() && (mimeType == "image/tiff" || mimeType == "image/tif" || mimeType == "image/x-tiff")) {
        const PluginData* pluginData = document().frame()->page()->pluginData();
        String pluginName = pluginData ? pluginData->pluginNameForMimeType(mimeType) : String();
        if (!pluginName.isEmpty() && !pluginName.contains("QuickTime", false))
            return true;
    }

    ObjectContentType objectType = document().frame()->loader().client()->objectContentType(url, mimeType, shouldPreferPlugInsForImages());
    // If an object's content can't be handled and it has no fallback, let
    // it be handled as a plugin to show the broken plugin icon.
    useFallback = objectType == ObjectContentNone && hasFallback;
    return objectType == ObjectContentNone || objectType == ObjectContentNetscapePlugin || objectType == ObjectContentOtherPlugin;

}

void HTMLPlugInElement::dispatchErrorEvent()
{
    if (document().isPluginDocument() && document().ownerElement())
        document().ownerElement()->dispatchEvent(Event::create(EventTypeNames::error));
    else
        dispatchEvent(Event::create(EventTypeNames::error));
}

bool HTMLPlugInElement::pluginIsLoadable(const KURL& url, const String& mimeType)
{
    LocalFrame* frame = document().frame();
    Settings* settings = frame->settings();
    if (!settings)
        return false;

    if (MIMETypeRegistry::isJavaAppletMIMEType(mimeType) && !settings->javaEnabled())
        return false;

    if (document().isSandboxed(SandboxPlugins))
        return false;

    if (!document().securityOrigin()->canDisplay(url)) {
        FrameLoader::reportLocalLoadFailed(frame, url.string());
        return false;
    }

    AtomicString declaredMimeType = document().isPluginDocument() && document().ownerElement() ?
        document().ownerElement()->fastGetAttribute(HTMLNames::typeAttr) :
        fastGetAttribute(HTMLNames::typeAttr);
    if (!document().contentSecurityPolicy()->allowObjectFromSource(url)
        || !document().contentSecurityPolicy()->allowPluginType(mimeType, declaredMimeType, url)) {
        renderEmbeddedObject()->setPluginUnavailabilityReason(RenderEmbeddedObject::PluginBlockedByContentSecurityPolicy);
        return false;
    }

    return frame->loader().mixedContentChecker()->canRunInsecureContent(document().securityOrigin(), url);
}

void HTMLPlugInElement::didAddUserAgentShadowRoot(ShadowRoot&)
{
    userAgentShadowRoot()->appendChild(HTMLContentElement::create(document()));
}

void HTMLPlugInElement::didAddShadowRoot(ShadowRoot& root)
{
    if (root.isOldestAuthorShadowRoot())
        lazyReattachIfAttached();
}

bool HTMLPlugInElement::useFallbackContent() const
{
    return hasAuthorShadowRoot();
}

}

/* [<][>][^][v][top][bottom][index][help] */