blob: 7a463b99d75a637741ad9d05e1e5eaa0d92327de [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, 2007, 2008, 2009, 2011 Apple Inc. All rights reserved.
* Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
*
* 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/HTMLObjectElement.h"
#include "bindings/core/v8/ScriptEventListener.h"
#include "core/HTMLNames.h"
#include "core/dom/Attribute.h"
#include "core/dom/Document.h"
#include "core/dom/ElementTraversal.h"
#include "core/dom/TagCollection.h"
#include "core/dom/Text.h"
#include "core/dom/shadow/ShadowRoot.h"
#include "core/fetch/ImageResource.h"
#include "core/frame/Settings.h"
#include "core/html/HTMLImageLoader.h"
#include "core/html/HTMLMetaElement.h"
#include "core/html/HTMLParamElement.h"
#include "core/html/parser/HTMLParserIdioms.h"
#include "core/plugins/PluginView.h"
#include "platform/MIMETypeRegistry.h"
#include "platform/Widget.h"
namespace blink {
using namespace HTMLNames;
inline HTMLObjectElement::HTMLObjectElement(Document& document, HTMLFormElement* form, bool createdByParser)
: HTMLPlugInElement(objectTag, document, createdByParser, ShouldNotPreferPlugInsForImages)
, m_useFallbackContent(false)
{
associateByParser(form);
}
inline HTMLObjectElement::~HTMLObjectElement()
{
}
HTMLObjectElement* HTMLObjectElement::create(Document& document, HTMLFormElement* form, bool createdByParser)
{
HTMLObjectElement* element = new HTMLObjectElement(document, form, createdByParser);
element->ensureUserAgentShadowRoot();
return element;
}
DEFINE_TRACE(HTMLObjectElement)
{
FormAssociatedElement::trace(visitor);
HTMLPlugInElement::trace(visitor);
}
LayoutPart* HTMLObjectElement::existingLayoutPart() const
{
return layoutPart(); // This will return 0 if the layoutObject is not a LayoutPart.
}
bool HTMLObjectElement::isPresentationAttribute(const QualifiedName& name) const
{
if (name == borderAttr)
return true;
return HTMLPlugInElement::isPresentationAttribute(name);
}
void HTMLObjectElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStylePropertySet* style)
{
if (name == borderAttr)
applyBorderAttributeToStyle(value, style);
else
HTMLPlugInElement::collectStyleForPresentationAttribute(name, value, style);
}
void HTMLObjectElement::parseAttribute(const QualifiedName& name, const AtomicString& oldValue, const AtomicString& value)
{
if (name == formAttr) {
formAttributeChanged();
} else if (name == typeAttr) {
m_serviceType = value.lower();
size_t pos = m_serviceType.find(";");
if (pos != kNotFound)
m_serviceType = m_serviceType.left(pos);
// TODO(schenney): crbug.com/572908 What is the right thing to do here? Should we
// suppress the reload stuff when a persistable widget-type is specified?
reloadPluginOnAttributeChange(name);
if (!layoutObject())
requestPluginCreationWithoutLayoutObjectIfPossible();
} else if (name == dataAttr) {
m_url = stripLeadingAndTrailingHTMLSpaces(value);
if (layoutObject() && isImageType()) {
setNeedsWidgetUpdate(true);
if (!m_imageLoader)
m_imageLoader = HTMLImageLoader::create(this);
m_imageLoader->updateFromElement(ImageLoader::UpdateIgnorePreviousError);
} else {
reloadPluginOnAttributeChange(name);
}
} else if (name == classidAttr) {
m_classId = value;
reloadPluginOnAttributeChange(name);
} else {
HTMLPlugInElement::parseAttribute(name, oldValue, value);
}
}
static void mapDataParamToSrc(Vector<String>* paramNames, Vector<String>* paramValues)
{
// Some plugins don't understand the "data" attribute of the OBJECT tag (i.e. Real and WMP
// require "src" attribute).
int srcIndex = -1, dataIndex = -1;
for (unsigned i = 0; i < paramNames->size(); ++i) {
if (equalIgnoringCase((*paramNames)[i], "src"))
srcIndex = i;
else if (equalIgnoringCase((*paramNames)[i], "data"))
dataIndex = i;
}
if (srcIndex == -1 && dataIndex != -1) {
paramNames->append("src");
paramValues->append((*paramValues)[dataIndex]);
}
}
// TODO(schenney): crbug.com/572908 This function should not deal with url or serviceType!
void HTMLObjectElement::parametersForPlugin(Vector<String>& paramNames, Vector<String>& paramValues, String& url, String& serviceType)
{
HashSet<StringImpl*, CaseFoldingHash> uniqueParamNames;
String urlParameter;
// Scan the PARAM children and store their name/value pairs.
// Get the URL and type from the params if we don't already have them.
for (HTMLParamElement* p = Traversal<HTMLParamElement>::firstChild(*this); p; p = Traversal<HTMLParamElement>::nextSibling(*p)) {
String name = p->name();
if (name.isEmpty())
continue;
uniqueParamNames.add(name.impl());
paramNames.append(p->name());
paramValues.append(p->value());
// TODO(schenney): crbug.com/572908 url adjustment does not belong in this function.
if (url.isEmpty() && urlParameter.isEmpty() && (equalIgnoringCase(name, "src") || equalIgnoringCase(name, "movie") || equalIgnoringCase(name, "code") || equalIgnoringCase(name, "url")))
urlParameter = stripLeadingAndTrailingHTMLSpaces(p->value());
// TODO(schenney): crbug.com/572908 serviceType calculation does not belong in this function.
if (serviceType.isEmpty() && equalIgnoringCase(name, "type")) {
serviceType = p->value();
size_t pos = serviceType.find(";");
if (pos != kNotFound)
serviceType = serviceType.left(pos);
}
}
// When OBJECT is used for an applet via Sun's Java plugin, the CODEBASE attribute in the tag
// points to the Java plugin itself (an ActiveX component) while the actual applet CODEBASE is
// in a PARAM tag. See <http://java.sun.com/products/plugin/1.2/docs/tags.html>. This means
// we have to explicitly suppress the tag's CODEBASE attribute if there is none in a PARAM,
// else our Java plugin will misinterpret it. [4004531]
String codebase;
if (MIMETypeRegistry::isJavaAppletMIMEType(serviceType)) {
codebase = "codebase";
uniqueParamNames.add(codebase.impl()); // pretend we found it in a PARAM already
}
// Turn the attributes of the <object> element into arrays, but don't override <param> values.
AttributeCollection attributes = this->attributes();
for (const Attribute& attribute : attributes) {
const AtomicString& name = attribute.name().localName();
if (!uniqueParamNames.contains(name.impl())) {
paramNames.append(name.getString());
paramValues.append(attribute.value().getString());
}
}
mapDataParamToSrc(&paramNames, &paramValues);
// HTML5 says that an object resource's URL is specified by the object's data
// attribute, not by a param element. However, for compatibility, allow the
// resource's URL to be given by a param named "src", "movie", "code" or "url"
// if we know that resource points to a plugin.
if (url.isEmpty() && !urlParameter.isEmpty()) {
KURL completedURL = document().completeURL(urlParameter);
bool useFallback;
if (shouldUsePlugin(completedURL, serviceType, false, useFallback))
url = urlParameter;
}
}
bool HTMLObjectElement::hasFallbackContent() const
{
for (Node* child = firstChild(); child; child = child->nextSibling()) {
// Ignore whitespace-only text, and <param> tags, any other content is fallback content.
if (child->isTextNode()) {
if (!toText(child)->containsOnlyWhitespace())
return true;
} else if (!isHTMLParamElement(*child)) {
return true;
}
}
return false;
}
bool HTMLObjectElement::hasValidClassId()
{
if (MIMETypeRegistry::isJavaAppletMIMEType(m_serviceType) && classId().startsWith("java:", TextCaseInsensitive))
return true;
// HTML5 says that fallback content should be rendered if a non-empty
// classid is specified for which the UA can't find a suitable plugin.
return classId().isEmpty();
}
void HTMLObjectElement::reloadPluginOnAttributeChange(const QualifiedName& name)
{
// Following,
// http://www.whatwg.org/specs/web-apps/current-work/#the-object-element
// (Enumerated list below "Whenever one of the following conditions occur:")
//
// the updating of certain attributes should bring about "redetermination"
// of what the element contains.
bool needsInvalidation;
if (name == typeAttr) {
needsInvalidation = !fastHasAttribute(classidAttr) && !fastHasAttribute(dataAttr);
} else if (name == dataAttr) {
needsInvalidation = !fastHasAttribute(classidAttr);
} else if (name == classidAttr) {
needsInvalidation = true;
} else {
ASSERT_NOT_REACHED();
needsInvalidation = false;
}
setNeedsWidgetUpdate(true);
if (needsInvalidation)
lazyReattachIfNeeded();
}
// TODO(schenney): crbug.com/572908 This should be unified with HTMLEmbedElement::updateWidget and
// moved down into HTMLPluginElement.cpp
void HTMLObjectElement::updateWidgetInternal()
{
ASSERT(!layoutEmbeddedItem().showsUnavailablePluginIndicator());
ASSERT(needsWidgetUpdate());
setNeedsWidgetUpdate(false);
// TODO(schenney): crbug.com/572908 This should ASSERT isFinishedParsingChildren() instead.
if (!isFinishedParsingChildren()) {
dispatchErrorEvent();
return;
}
// TODO(schenney): crbug.com/572908 I'm not sure it's ever possible to get into updateWidget during a
// removal, but just in case we should avoid loading the frame to prevent security bugs.
if (!SubframeLoadingDisabler::canLoadFrame(*this)) {
dispatchErrorEvent();
return;
}
String url = this->url();
String serviceType = m_serviceType;
// TODO(schenney): crbug.com/572908 These should be joined into a PluginParameters class.
Vector<String> paramNames;
Vector<String> paramValues;
parametersForPlugin(paramNames, paramValues, url, serviceType);
// Note: url is modified above by parametersForPlugin.
if (!allowedToLoadFrameURL(url)) {
dispatchErrorEvent();
return;
}
// TODO(schenney): crbug.com/572908 Is it possible to get here without a layoutObject
// now that we don't have beforeload events?
if (!layoutObject())
return;
if (!hasValidClassId() || !requestObject(url, serviceType, paramNames, paramValues)) {
if (!url.isEmpty())
dispatchErrorEvent();
if (hasFallbackContent())
renderFallbackContent();
}
}
Node::InsertionNotificationRequest HTMLObjectElement::insertedInto(ContainerNode* insertionPoint)
{
HTMLPlugInElement::insertedInto(insertionPoint);
FormAssociatedElement::insertedInto(insertionPoint);
return InsertionDone;
}
void HTMLObjectElement::removedFrom(ContainerNode* insertionPoint)
{
HTMLPlugInElement::removedFrom(insertionPoint);
FormAssociatedElement::removedFrom(insertionPoint);
}
void HTMLObjectElement::childrenChanged(const ChildrenChange& change)
{
if (isConnected() && !useFallbackContent()) {
setNeedsWidgetUpdate(true);
lazyReattachIfNeeded();
}
HTMLPlugInElement::childrenChanged(change);
}
bool HTMLObjectElement::isURLAttribute(const Attribute& attribute) const
{
return attribute.name() == codebaseAttr || attribute.name() == dataAttr
|| (attribute.name() == usemapAttr && attribute.value()[0] != '#')
|| HTMLPlugInElement::isURLAttribute(attribute);
}
bool HTMLObjectElement::hasLegalLinkAttribute(const QualifiedName& name) const
{
return name == classidAttr || name == dataAttr || name == codebaseAttr || HTMLPlugInElement::hasLegalLinkAttribute(name);
}
const QualifiedName& HTMLObjectElement::subResourceAttributeName() const
{
return dataAttr;
}
const AtomicString HTMLObjectElement::imageSourceURL() const
{
return getAttribute(dataAttr);
}
// TODO(schenney): crbug.com/572908 Remove this hack.
void HTMLObjectElement::reattachFallbackContent()
{
// This can happen inside of attachLayoutTree() in the middle of a recalcStyle so we need to
// reattach synchronously here.
if (document().inStyleRecalc())
reattachLayoutTree();
else
lazyReattachIfAttached();
}
void HTMLObjectElement::renderFallbackContent()
{
if (useFallbackContent())
return;
if (!isConnected())
return;
// Before we give up and use fallback content, check to see if this is a MIME type issue.
if (m_imageLoader && m_imageLoader->image() && m_imageLoader->image()->getStatus() != Resource::LoadError) {
m_serviceType = m_imageLoader->image()->response().mimeType();
if (!isImageType()) {
// If we don't think we have an image type anymore, then clear the image from the loader.
m_imageLoader->setImage(0);
reattachFallbackContent();
return;
}
}
m_useFallbackContent = true;
// TODO(schenney): crbug.com/572908 Style gets recalculated which is suboptimal.
reattachFallbackContent();
}
bool HTMLObjectElement::isExposed() const
{
// http://www.whatwg.org/specs/web-apps/current-work/#exposed
for (HTMLObjectElement* ancestor = Traversal<HTMLObjectElement>::firstAncestor(*this); ancestor; ancestor = Traversal<HTMLObjectElement>::firstAncestor(*ancestor)) {
if (ancestor->isExposed())
return false;
}
for (HTMLElement& element : Traversal<HTMLElement>::descendantsOf(*this)) {
if (isHTMLObjectElement(element) || isHTMLEmbedElement(element))
return false;
}
return true;
}
bool HTMLObjectElement::containsJavaApplet() const
{
if (MIMETypeRegistry::isJavaAppletMIMEType(getAttribute(typeAttr)))
return true;
for (HTMLElement& child : Traversal<HTMLElement>::childrenOf(*this)) {
if (isHTMLParamElement(child)
&& equalIgnoringCase(child.getNameAttribute(), "type")
&& MIMETypeRegistry::isJavaAppletMIMEType(child.getAttribute(valueAttr).getString()))
return true;
if (isHTMLObjectElement(child) && toHTMLObjectElement(child).containsJavaApplet())
return true;
}
return false;
}
void HTMLObjectElement::didMoveToNewDocument(Document& oldDocument)
{
FormAssociatedElement::didMoveToNewDocument(oldDocument);
HTMLPlugInElement::didMoveToNewDocument(oldDocument);
}
HTMLFormElement* HTMLObjectElement::formOwner() const
{
return FormAssociatedElement::form();
}
bool HTMLObjectElement::isInteractiveContent() const
{
return fastHasAttribute(usemapAttr);
}
bool HTMLObjectElement::useFallbackContent() const
{
return HTMLPlugInElement::useFallbackContent() || m_useFallbackContent;
}
} // namespace blink