blob: 19a31667e9f13cb1e3e211d199d8f0facf9f5a8e [file] [log] [blame]
/**
* 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 "core/html/HTMLPlugInElement.h"
#include "bindings/core/v8/ScriptController.h"
#include "core/CSSPropertyNames.h"
#include "core/HTMLNames.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/Settings.h"
#include "core/frame/csp/ContentSecurityPolicy.h"
#include "core/html/HTMLContentElement.h"
#include "core/html/HTMLImageLoader.h"
#include "core/html/PluginDocument.h"
#include "core/input/EventHandler.h"
#include "core/inspector/ConsoleMessage.h"
#include "core/layout/LayoutImage.h"
#include "core/layout/LayoutPart.h"
#include "core/layout/api/LayoutEmbeddedItem.h"
#include "core/loader/FrameLoaderClient.h"
#include "core/loader/MixedContentChecker.h"
#include "core/page/Page.h"
#include "core/page/scrolling/ScrollingCoordinator.h"
#include "core/plugins/PluginView.h"
#include "platform/Histogram.h"
#include "platform/MIMETypeFromURL.h"
#include "platform/MIMETypeRegistry.h"
#include "platform/Widget.h"
#include "platform/network/ResourceRequest.h"
#include "platform/plugins/PluginData.h"
#include "public/platform/WebURLRequest.h"
namespace blink {
using namespace HTMLNames;
namespace {
// Used for histograms, do not change the order.
enum PluginRequestObjectResult {
PluginRequestObjectResultFailure = 0,
PluginRequestObjectResultSuccess = 1,
// Keep at the end.
PluginRequestObjectResultMax
};
} // anonymous namespace
HTMLPlugInElement::HTMLPlugInElement(
const QualifiedName& tagName,
Document& doc,
bool createdByParser,
PreferPlugInsForImagesOption preferPlugInsForImagesOption)
: HTMLFrameOwnerElement(tagName, doc),
m_isDelayingLoadEvent(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) {}
HTMLPlugInElement::~HTMLPlugInElement() {
DCHECK(!m_pluginWrapper); // cleared in detachLayoutTree()
DCHECK(!m_isDelayingLoadEvent);
}
DEFINE_TRACE(HTMLPlugInElement) {
visitor->trace(m_imageLoader);
visitor->trace(m_persistedPluginWidget);
HTMLFrameOwnerElement::trace(visitor);
}
void HTMLPlugInElement::setPersistedPluginWidget(Widget* widget) {
if (m_persistedPluginWidget == widget)
return;
if (m_persistedPluginWidget) {
if (m_persistedPluginWidget->isPluginView()) {
m_persistedPluginWidget->hide();
disposeWidgetSoon(m_persistedPluginWidget.release());
} else {
DCHECK(m_persistedPluginWidget->isFrameView() ||
m_persistedPluginWidget->isRemoteFrameView());
}
}
m_persistedPluginWidget = widget;
}
bool HTMLPlugInElement::requestObjectInternal(
const String& url,
const String& mimeType,
const Vector<String>& paramNames,
const Vector<String>& paramValues) {
if (url.isEmpty() && mimeType.isEmpty())
return false;
if (protocolIsJavaScript(url))
return false;
KURL completedURL = url.isEmpty() ? KURL() : document().completeURL(url);
if (!allowedToLoadObject(completedURL, mimeType))
return false;
bool useFallback;
if (!shouldUsePlugin(completedURL, mimeType, hasFallbackContent(),
useFallback)) {
// If the plugin element already contains a subframe,
// loadOrRedirectSubframe will re-use it. Otherwise, it will create a
// new frame and set it as the LayoutPart's widget, causing what was
// previously in the widget to be torn down.
return loadOrRedirectSubframe(completedURL, getNameAttribute(), true);
}
return loadPlugin(completedURL, mimeType, paramNames, paramValues,
useFallback, true);
}
bool HTMLPlugInElement::canProcessDrag() const {
return pluginWidget() && pluginWidget()->isPluginView() &&
toPluginView(pluginWidget())->canProcessDrag();
}
bool HTMLPlugInElement::canStartSelection() const {
return useFallbackContent() && Node::canStartSelection();
}
bool HTMLPlugInElement::willRespondToMouseClickEvents() {
if (isDisabledFormControl())
return false;
LayoutObject* r = layoutObject();
return r && (r->isEmbeddedObject() || r->isLayoutPart());
}
void HTMLPlugInElement::removeAllEventListeners() {
HTMLFrameOwnerElement::removeAllEventListeners();
if (LayoutPart* layoutObject = existingLayoutPart()) {
if (Widget* widget = layoutObject->widget())
widget->eventListenersRemoved();
}
}
void HTMLPlugInElement::didMoveToNewDocument(Document& oldDocument) {
if (m_imageLoader)
m_imageLoader->elementDidMoveToNewDocument();
HTMLFrameOwnerElement::didMoveToNewDocument(oldDocument);
}
void HTMLPlugInElement::attachLayoutTree(const AttachContext& context) {
HTMLFrameOwnerElement::attachLayoutTree(context);
if (!layoutObject() || useFallbackContent()) {
// If we don't have a layoutObject we have to dispose of any plugins
// which we persisted over a reattach.
if (m_persistedPluginWidget) {
HTMLFrameOwnerElement::UpdateSuspendScope suspendWidgetHierarchyUpdates;
setPersistedPluginWidget(nullptr);
}
return;
}
if (isImageType()) {
if (!m_imageLoader)
m_imageLoader = HTMLImageLoader::create(this);
m_imageLoader->updateFromElement();
} else if (needsWidgetUpdate() && !layoutEmbeddedItem().isNull() &&
!layoutEmbeddedItem().showsUnavailablePluginIndicator() &&
!wouldLoadAsNetscapePlugin(m_url, m_serviceType) &&
!m_isDelayingLoadEvent) {
m_isDelayingLoadEvent = true;
document().incrementLoadEventDelayCount();
document().loadPluginsSoon();
}
}
void HTMLPlugInElement::updateWidget() {
updateWidgetInternal();
if (m_isDelayingLoadEvent) {
m_isDelayingLoadEvent = false;
document().decrementLoadEventDelayCount();
}
}
void HTMLPlugInElement::removedFrom(ContainerNode* insertionPoint) {
// If we've persisted the plugin and we're removed from the tree then
// make sure we cleanup the persistance pointer.
if (m_persistedPluginWidget) {
HTMLFrameOwnerElement::UpdateSuspendScope suspendWidgetHierarchyUpdates;
setPersistedPluginWidget(nullptr);
}
HTMLFrameOwnerElement::removedFrom(insertionPoint);
}
void HTMLPlugInElement::requestPluginCreationWithoutLayoutObjectIfPossible() {
if (m_serviceType.isEmpty())
return;
if (!document().frame() ||
!document().frame()->loader().client()->canCreatePluginWithoutRenderer(
m_serviceType))
return;
if (layoutObject() && layoutObject()->isLayoutPart())
return;
createPluginWithoutLayoutObject();
}
void HTMLPlugInElement::createPluginWithoutLayoutObject() {
DCHECK(document().frame()->loader().client()->canCreatePluginWithoutRenderer(
m_serviceType));
KURL url;
// CSP can block src-less objects.
if (!allowedToLoadObject(url, m_serviceType))
return;
Vector<String> paramNames;
Vector<String> paramValues;
paramNames.append("type");
paramValues.append(m_serviceType);
bool useFallback = false;
loadPlugin(url, m_serviceType, paramNames, paramValues, useFallback, false);
}
bool HTMLPlugInElement::shouldAccelerate() const {
if (Widget* widget = ownedWidget())
return widget->isPluginView() && toPluginView(widget)->platformLayer();
return false;
}
void HTMLPlugInElement::detachLayoutTree(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 (layoutObject() && !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 && context.performingReattach) {
setPersistedPluginWidget(releaseWidget());
} else {
// Clear the widget; will trigger disposal of it with Oilpan.
setWidget(nullptr);
}
resetInstance();
HTMLFrameOwnerElement::detachLayoutTree(context);
}
LayoutObject* HTMLPlugInElement::createLayoutObject(
const ComputedStyle& style) {
// Fallback content breaks the DOM->layoutObject class relationship of this
// class and all superclasses because createObject won't necessarily return
// a LayoutEmbeddedObject or LayoutPart.
if (useFallbackContent())
return LayoutObject::createObject(this, style);
if (isImageType()) {
LayoutImage* image = new LayoutImage(this);
image->setImageResource(LayoutImageResource::create());
return image;
}
m_pluginIsAvailable = true;
return new LayoutEmbeddedObject(this);
}
void HTMLPlugInElement::finishParsingChildren() {
HTMLFrameOwnerElement::finishParsingChildren();
if (useFallbackContent())
return;
setNeedsWidgetUpdate(true);
if (isConnected())
lazyReattachIfNeeded();
}
void HTMLPlugInElement::resetInstance() {
m_pluginWrapper.clear();
}
SharedPersistent<v8::Object>* HTMLPlugInElement::pluginWrapper() {
LocalFrame* frame = document().frame();
if (!frame)
return nullptr;
// 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 (LayoutPart* layoutPart = layoutPartForJSBindings())
return layoutPart->widget();
return nullptr;
}
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 plugin
// (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 plugin, 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 plugin via custom
// code in EventHandler; these code paths should be united.
LayoutObject* r = layoutObject();
if (!r || !r->isLayoutPart())
return;
if (r->isEmbeddedObject()) {
if (LayoutEmbeddedItem(toLayoutEmbeddedObject(r))
.showsUnavailablePluginIndicator())
return;
}
Widget* widget = toLayoutPart(r)->widget();
if (!widget)
return;
widget->handleEvent(event);
if (event->defaultHandled())
return;
HTMLFrameOwnerElement::defaultEventHandler(event);
}
LayoutPart* HTMLPlugInElement::layoutPartForJSBindings() 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().updateStyleAndLayoutIgnorePendingStylesheets(
Document::RunPostLayoutTasksSynchronously);
return existingLayoutPart();
}
bool HTMLPlugInElement::isKeyboardFocusable() const {
if (HTMLFrameOwnerElement::isKeyboardFocusable())
return true;
return document().isActive() && pluginWidget() &&
pluginWidget()->isPluginView() &&
toPluginView(pluginWidget())->supportsKeyboardFocus();
}
bool HTMLPlugInElement::hasCustomFocusLogic() const {
return !useFallbackContent();
}
bool HTMLPlugInElement::isPluginElement() const {
return true;
}
bool HTMLPlugInElement::layoutObjectIsFocusable() const {
if (HTMLFrameOwnerElement::supportsFocus() &&
HTMLFrameOwnerElement::layoutObjectIsFocusable())
return true;
if (useFallbackContent() || !HTMLFrameOwnerElement::layoutObjectIsFocusable())
return false;
return m_pluginIsAvailable;
}
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()->getObjectContentType(
completedURL, m_serviceType, shouldPreferPlugInsForImages()) ==
ObjectContentImage;
}
return Image::supportsType(m_serviceType);
}
LayoutEmbeddedItem HTMLPlugInElement::layoutEmbeddedItem() const {
// HTMLObjectElement and HTMLEmbedElement may return arbitrary layoutObjects
// when using fallback content.
if (!layoutObject() || !layoutObject()->isEmbeddedObject())
return LayoutEmbeddedItem(nullptr);
return LayoutEmbeddedItem(toLayoutEmbeddedObject(layoutObject()));
}
// 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().getSecurityOrigin()->canAccess(
contentFrame()->securityContext()->getSecurityOrigin()))
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) {
DCHECK(document().frame());
KURL completedURL;
if (!url.isEmpty())
completedURL = document().completeURL(url);
return document().frame()->loader().client()->getObjectContentType(
completedURL, serviceType, shouldPreferPlugInsForImages()) ==
ObjectContentNetscapePlugin;
}
bool HTMLPlugInElement::requestObject(const String& url,
const String& mimeType,
const Vector<String>& paramNames,
const Vector<String>& paramValues) {
bool result = requestObjectInternal(url, mimeType, paramNames, paramValues);
DEFINE_STATIC_LOCAL(
EnumerationHistogram, resultHistogram,
("Plugin.RequestObjectResult", PluginRequestObjectResultMax));
resultHistogram.count(result ? PluginRequestObjectResultSuccess
: PluginRequestObjectResultFailure);
return result;
}
bool HTMLPlugInElement::loadPlugin(const KURL& url,
const String& mimeType,
const Vector<String>& paramNames,
const Vector<String>& paramValues,
bool useFallback,
bool requireLayoutObject) {
if (!allowedToLoadPlugin(url, mimeType))
return false;
LocalFrame* frame = document().frame();
if (!frame->loader().allowPlugins(AboutToInstantiatePlugin))
return false;
LayoutEmbeddedItem layoutItem = layoutEmbeddedItem();
// FIXME: This code should not depend on layoutObject!
if ((layoutItem.isNull() && requireLayoutObject) || useFallback)
return false;
VLOG(1) << this << " Plugin URL: " << m_url;
VLOG(1) << "Loaded URL: " << url.getString();
m_loadedUrl = url;
if (m_persistedPluginWidget) {
setWidget(m_persistedPluginWidget.release());
} else {
bool loadManually =
document().isPluginDocument() && !document().containsPlugins();
FrameLoaderClient::DetachedPluginPolicy policy =
requireLayoutObject ? FrameLoaderClient::FailOnDetachedPlugin
: FrameLoaderClient::AllowDetachedPlugin;
Widget* widget = frame->loader().client()->createPlugin(
this, url, paramNames, paramValues, mimeType, loadManually, policy);
if (!widget) {
if (!layoutItem.isNull() &&
!layoutItem.showsUnavailablePluginIndicator()) {
m_pluginIsAvailable = false;
layoutItem.setPluginAvailability(LayoutEmbeddedObject::PluginMissing);
}
return false;
}
if (!layoutItem.isNull())
setWidget(widget);
else
setPersistedPluginWidget(widget);
}
document().setContainsPlugins();
// TODO(esprehn): WebPluginContainerImpl::setWebLayer also schedules a
// compositing update, do we need both?
setNeedsCompositingUpdate();
// Make sure any input event handlers introduced by the plugin are taken into
// account.
if (Page* page = document().frame()->page()) {
if (ScrollingCoordinator* scrollingCoordinator =
page->scrollingCoordinator())
scrollingCoordinator->notifyGeometryChanged();
}
return true;
}
bool HTMLPlugInElement::shouldUsePlugin(const KURL& url,
const String& mimeType,
bool hasFallback,
bool& useFallback) {
ObjectContentType objectType =
document().frame()->loader().client()->getObjectContentType(
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;
}
void HTMLPlugInElement::dispatchErrorEvent() {
if (document().isPluginDocument() && document().localOwner())
document().localOwner()->dispatchEvent(
Event::create(EventTypeNames::error));
else
dispatchEvent(Event::create(EventTypeNames::error));
}
bool HTMLPlugInElement::allowedToLoadObject(const KURL& url,
const String& mimeType) {
if (url.isEmpty() && mimeType.isEmpty())
return false;
LocalFrame* frame = document().frame();
Settings* settings = frame->settings();
if (!settings)
return false;
if (MIMETypeRegistry::isJavaAppletMIMEType(mimeType))
return false;
if (!document().getSecurityOrigin()->canDisplay(url)) {
FrameLoader::reportLocalLoadFailed(frame, url.getString());
return false;
}
AtomicString declaredMimeType = fastGetAttribute(HTMLNames::typeAttr);
if (!document().contentSecurityPolicy()->allowObjectFromSource(url) ||
!document().contentSecurityPolicy()->allowPluginTypeForDocument(
document(), mimeType, declaredMimeType, url)) {
if (LayoutEmbeddedItem layoutItem = layoutEmbeddedItem()) {
m_pluginIsAvailable = false;
layoutItem.setPluginAvailability(
LayoutEmbeddedObject::PluginBlockedByContentSecurityPolicy);
}
return false;
}
// If the URL is empty, a plugin could still be instantiated if a MIME-type
// is specified.
return (!mimeType.isEmpty() && url.isEmpty()) ||
!MixedContentChecker::shouldBlockFetch(
frame, WebURLRequest::RequestContextObject,
WebURLRequest::FrameTypeNone,
ResourceRequest::RedirectStatus::NoRedirect, url);
}
bool HTMLPlugInElement::allowedToLoadPlugin(const KURL& url,
const String& mimeType) {
if (document().isSandboxed(SandboxPlugins)) {
document().addConsoleMessage(ConsoleMessage::create(
SecurityMessageSource, ErrorMessageLevel,
"Failed to load '" + url.elidedString() + "' as a plugin, because the "
"frame into which the plugin "
"is loading is sandboxed."));
return false;
}
return true;
}
void HTMLPlugInElement::didAddUserAgentShadowRoot(ShadowRoot&) {
userAgentShadowRoot()->appendChild(HTMLContentElement::create(document()));
}
bool HTMLPlugInElement::hasFallbackContent() const {
return false;
}
bool HTMLPlugInElement::useFallbackContent() const {
return false;
}
void HTMLPlugInElement::lazyReattachIfNeeded() {
if (!useFallbackContent() && needsWidgetUpdate() && layoutObject() &&
!isImageType()) {
lazyReattachIfAttached();
setPersistedPluginWidget(nullptr);
}
}
} // namespace blink