/*
* 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 "config.h"
#include "HTMLObjectElement.h"
#include "Attribute.h"
#include "CSSValueKeywords.h"
#include "EventNames.h"
#include "ExceptionCode.h"
#include "Frame.h"
#include "HTMLDocument.h"
#include "HTMLFormElement.h"
#include "HTMLImageLoader.h"
#include "HTMLNames.h"
#include "HTMLParamElement.h"
#include "HTMLParserIdioms.h"
#include "MIMETypeRegistry.h"
#include "RenderEmbeddedObject.h"
#include "RenderImage.h"
#include "RenderWidget.h"
#include "ScriptController.h"
#include "ScriptEventListener.h"
#include "Text.h"
namespace WebCore {
using namespace HTMLNames;
inline HTMLObjectElement::HTMLObjectElement(const QualifiedName& tagName, Document* document, HTMLFormElement* form, bool createdByParser)
: HTMLPlugInImageElement(tagName, document, createdByParser, ShouldNotPreferPlugInsForImages)
, FormAssociatedElement(form)
, m_docNamedItem(true)
, m_useFallbackContent(false)
{
ASSERT(hasTagName(objectTag));
if (!this->form())
setForm(findFormAncestor());
if (this->form())
this->form()->registerFormElement(this);
}
inline HTMLObjectElement::~HTMLObjectElement()
{
if (form())
form()->removeFormElement(this);
}
PassRefPtr<HTMLObjectElement> HTMLObjectElement::create(const QualifiedName& tagName, Document* document, HTMLFormElement* form, bool createdByParser)
{
return adoptRef(new HTMLObjectElement(tagName, document, form, createdByParser));
}
RenderWidget* HTMLObjectElement::renderWidgetForJSBindings() const
{
document()->updateLayoutIgnorePendingStylesheets();
return renderPart(); // This will return 0 if the renderer is not a RenderPart.
}
void HTMLObjectElement::parseMappedAttribute(Attribute* attr)
{
if (attr->name() == typeAttr) {
m_serviceType = attr->value().lower();
size_t pos = m_serviceType.find(";");
if (pos != notFound)
m_serviceType = m_serviceType.left(pos);
if (renderer())
setNeedsWidgetUpdate(true);
if (!isImageType() && m_imageLoader)
m_imageLoader.clear();
} else if (attr->name() == dataAttr) {
m_url = stripLeadingAndTrailingHTMLSpaces(attr->value());
if (renderer()) {
setNeedsWidgetUpdate(true);
if (isImageType()) {
if (!m_imageLoader)
m_imageLoader = adoptPtr(new HTMLImageLoader(this));
m_imageLoader->updateFromElementIgnoringPreviousError();
}
}
} else if (attr->name() == classidAttr) {
m_classId = attr->value();
if (renderer())
setNeedsWidgetUpdate(true);
} else if (attr->name() == onloadAttr)
setAttributeEventListener(eventNames().loadEvent, createAttributeEventListener(this, attr));
else if (attr->name() == onbeforeloadAttr)
setAttributeEventListener(eventNames().beforeloadEvent, createAttributeEventListener(this, attr));
else if (attr->name() == nameAttr) {
const AtomicString& newName = attr->value();
if (isDocNamedItem() && inDocument() && document()->isHTMLDocument()) {
HTMLDocument* document = static_cast<HTMLDocument*>(this->document());
document->removeNamedItem(m_name);
document->addNamedItem(newName);
}
m_name = newName;
} else if (attr->name() == borderAttr) {
addCSSLength(attr, CSSPropertyBorderWidth, attr->value().toInt() ? attr->value() : "0");
addCSSProperty(attr, CSSPropertyBorderTopStyle, CSSValueSolid);
addCSSProperty(attr, CSSPropertyBorderRightStyle, CSSValueSolid);
addCSSProperty(attr, CSSPropertyBorderBottomStyle, CSSValueSolid);
addCSSProperty(attr, CSSPropertyBorderLeftStyle, CSSValueSolid);
} else if (isIdAttributeName(attr->name())) {
const AtomicString& newId = attr->value();
if (isDocNamedItem() && inDocument() && document()->isHTMLDocument()) {
HTMLDocument* document = static_cast<HTMLDocument*>(this->document());
document->removeExtraNamedItem(m_id);
document->addExtraNamedItem(newId);
}
m_id = newId;
// also call superclass
HTMLPlugInImageElement::parseMappedAttribute(attr);
} else
HTMLPlugInImageElement::parseMappedAttribute(attr);
}
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 int 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]);
}
}
// FIXME: 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 (Node* child = firstChild(); child; child = child->nextSibling()) {
if (!child->hasTagName(paramTag))
continue;
HTMLParamElement* p = static_cast<HTMLParamElement*>(child);
String name = p->name();
if (name.isEmpty())
continue;
uniqueParamNames.add(name.impl());
paramNames.append(p->name());
paramValues.append(p->value());
// FIXME: 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());
// FIXME: serviceType calculation does not belong in this function.
if (serviceType.isEmpty() && equalIgnoringCase(name, "type")) {
serviceType = p->value();
size_t pos = serviceType.find(";");
if (pos != notFound)
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.
NamedNodeMap* attributes = this->attributes(true);
if (attributes) {
for (unsigned i = 0; i < attributes->length(); ++i) {
Attribute* it = attributes->attributeItem(i);
const AtomicString& name = it->name().localName();
if (!uniqueParamNames.contains(name.impl())) {
paramNames.append(name.string());
paramValues.append(it->value().string());
}
}
}
mapDataParamToSrc(¶mNames, ¶mValues);
// 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 plug-in.
if (url.isEmpty() && !urlParameter.isEmpty()) {
SubframeLoader* loader = document()->frame()->loader()->subframeLoader();
if (loader->resourceWillUsePlugin(urlParameter, serviceType, shouldPreferPlugInsForImages()))
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 (!static_cast<Text*>(child)->containsOnlyWhitespace())
return true;
} else if (!child->hasTagName(paramTag))
return true;
}
return false;
}
bool HTMLObjectElement::hasValidClassId()
{
#if PLATFORM(QT)
if (equalIgnoringCase(serviceType(), "application/x-qt-plugin") || equalIgnoringCase(serviceType(), "application/x-qt-styled-widget"))
return true;
#endif
if (MIMETypeRegistry::isJavaAppletMIMEType(serviceType()) && classId().startsWith("java:", false))
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 plug-in.
return classId().isEmpty();
}
// FIXME: This should be unified with HTMLEmbedElement::updateWidget and
// moved down into HTMLPluginImageElement.cpp
void HTMLObjectElement::updateWidget(PluginCreationOption pluginCreationOption)
{
ASSERT(!renderEmbeddedObject()->pluginCrashedOrWasMissing());
// FIXME: We should ASSERT(needsWidgetUpdate()), but currently
// FrameView::updateWidget() calls updateWidget(false) without checking if
// the widget actually needs updating!
setNeedsWidgetUpdate(false);
// FIXME: This should ASSERT isFinishedParsingChildren() instead.
if (!isFinishedParsingChildren())
return;
String url = this->url();
String serviceType = this->serviceType();
// FIXME: 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))
return;
bool fallbackContent = hasFallbackContent();
renderEmbeddedObject()->setHasFallbackContent(fallbackContent);
if (pluginCreationOption == CreateOnlyNonNetscapePlugins && wouldLoadAsNetscapePlugin(url, serviceType))
return;
ASSERT(!m_inBeforeLoadEventHandler);
m_inBeforeLoadEventHandler = true;
bool beforeLoadAllowedLoad = dispatchBeforeLoadEvent(url);
m_inBeforeLoadEventHandler = false;
// beforeload events can modify the DOM, potentially causing
// RenderWidget::destroy() to be called. Ensure we haven't been
// destroyed before continuing.
// FIXME: Should this render fallback content?
if (!renderer())
return;
SubframeLoader* loader = document()->frame()->loader()->subframeLoader();
bool success = beforeLoadAllowedLoad && hasValidClassId() && loader->requestObject(this, url, getAttribute(nameAttr), serviceType, paramNames, paramValues);
if (!success && fallbackContent)
renderFallbackContent();
}
bool HTMLObjectElement::rendererIsNeeded(RenderStyle* style)
{
// FIXME: This check should not be needed, detached documents never render!
Frame* frame = document()->frame();
if (!frame)
return false;
return HTMLPlugInImageElement::rendererIsNeeded(style);
}
void HTMLObjectElement::insertedIntoDocument()
{
HTMLPlugInImageElement::insertedIntoDocument();
if (!inDocument())
return;
if (isDocNamedItem() && document()->isHTMLDocument()) {
HTMLDocument* document = static_cast<HTMLDocument*>(this->document());
document->addNamedItem(m_name);
document->addExtraNamedItem(m_id);
}
FormAssociatedElement::insertedIntoDocument();
}
void HTMLObjectElement::removedFromDocument()
{
if (isDocNamedItem() && document()->isHTMLDocument()) {
HTMLDocument* document = static_cast<HTMLDocument*>(this->document());
document->removeNamedItem(m_name);
document->removeExtraNamedItem(m_id);
}
HTMLPlugInImageElement::removedFromDocument();
FormAssociatedElement::removedFromDocument();
}
void HTMLObjectElement::attributeChanged(Attribute* attr, bool preserveDecls)
{
if (attr->name() == formAttr)
formAttributeChanged();
else
HTMLPlugInImageElement::attributeChanged(attr, preserveDecls);
}
void HTMLObjectElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta)
{
updateDocNamedItem();
if (inDocument() && !useFallbackContent()) {
setNeedsWidgetUpdate(true);
setNeedsStyleRecalc();
}
HTMLPlugInImageElement::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta);
}
bool HTMLObjectElement::isURLAttribute(Attribute *attr) const
{
return (attr->name() == dataAttr || (attr->name() == usemapAttr && attr->value().string()[0] != '#'));
}
const QualifiedName& HTMLObjectElement::imageSourceAttributeName() const
{
return dataAttr;
}
void HTMLObjectElement::renderFallbackContent()
{
if (useFallbackContent())
return;
if (!inDocument())
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()->status() != CachedResource::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);
detach();
attach();
return;
}
}
m_useFallbackContent = true;
// FIXME: Style gets recalculated which is suboptimal.
detach();
attach();
}
// FIXME: This should be removed, all callers are almost certainly wrong.
static bool isRecognizedTagName(const QualifiedName& tagName)
{
DEFINE_STATIC_LOCAL(HashSet<AtomicStringImpl*>, tagList, ());
if (tagList.isEmpty()) {
size_t tagCount = 0;
QualifiedName** tags = HTMLNames::getHTMLTags(&tagCount);
for (size_t i = 0; i < tagCount; i++) {
if (*tags[i] == bgsoundTag
|| *tags[i] == commandTag
|| *tags[i] == detailsTag
|| *tags[i] == figcaptionTag
|| *tags[i] == figureTag
|| *tags[i] == summaryTag
|| *tags[i] == trackTag) {
// Even though we have atoms for these tags, we don't want to
// treat them as "recognized tags" for the purpose of parsing
// because that changes how we parse documents.
continue;
}
tagList.add(tags[i]->localName().impl());
}
}
return tagList.contains(tagName.localName().impl());
}
void HTMLObjectElement::updateDocNamedItem()
{
// The rule is "<object> elements with no children other than
// <param> elements, unknown elements and whitespace can be
// found by name in a document, and other <object> elements cannot."
bool wasNamedItem = m_docNamedItem;
bool isNamedItem = true;
Node* child = firstChild();
while (child && isNamedItem) {
if (child->isElementNode()) {
Element* element = static_cast<Element*>(child);
// FIXME: Use of isRecognizedTagName is almost certainly wrong here.
if (isRecognizedTagName(element->tagQName()) && !element->hasTagName(paramTag))
isNamedItem = false;
} else if (child->isTextNode()) {
if (!static_cast<Text*>(child)->containsOnlyWhitespace())
isNamedItem = false;
} else
isNamedItem = false;
child = child->nextSibling();
}
if (isNamedItem != wasNamedItem && document()->isHTMLDocument()) {
HTMLDocument* document = static_cast<HTMLDocument*>(this->document());
if (isNamedItem) {
document->addNamedItem(m_name);
document->addExtraNamedItem(m_id);
} else {
document->removeNamedItem(m_name);
document->removeExtraNamedItem(m_id);
}
}
m_docNamedItem = isNamedItem;
}
bool HTMLObjectElement::containsJavaApplet() const
{
if (MIMETypeRegistry::isJavaAppletMIMEType(getAttribute(typeAttr)))
return true;
for (Element* child = firstElementChild(); child; child = child->nextElementSibling()) {
if (child->hasTagName(paramTag)
&& equalIgnoringCase(child->getAttribute(nameAttr), "type")
&& MIMETypeRegistry::isJavaAppletMIMEType(child->getAttribute(valueAttr).string()))
return true;
if (child->hasTagName(objectTag)
&& static_cast<HTMLObjectElement*>(child)->containsJavaApplet())
return true;
if (child->hasTagName(appletTag))
return true;
}
return false;
}
void HTMLObjectElement::addSubresourceAttributeURLs(ListHashSet<KURL>& urls) const
{
HTMLPlugInImageElement::addSubresourceAttributeURLs(urls);
addSubresourceURL(urls, document()->completeURL(getAttribute(dataAttr)));
// FIXME: Passing a string that starts with "#" to the completeURL function does
// not seem like it would work. The image element has similar but not identical code.
const AtomicString& useMap = getAttribute(usemapAttr);
if (useMap.startsWith("#"))
addSubresourceURL(urls, document()->completeURL(useMap));
}
void HTMLObjectElement::willMoveToNewOwnerDocument()
{
FormAssociatedElement::willMoveToNewOwnerDocument();
HTMLPlugInImageElement::willMoveToNewOwnerDocument();
}
void HTMLObjectElement::insertedIntoTree(bool deep)
{
FormAssociatedElement::insertedIntoTree();
HTMLPlugInImageElement::insertedIntoTree(deep);
}
void HTMLObjectElement::removedFromTree(bool deep)
{
FormAssociatedElement::removedFromTree();
HTMLPlugInImageElement::removedFromTree(deep);
}
bool HTMLObjectElement::appendFormData(FormDataList&, bool)
{
// FIXME: Implements this function.
return false;
}
const AtomicString& HTMLObjectElement::formControlName() const
{
return m_name.isNull() ? emptyAtom : m_name;
}
HTMLFormElement* HTMLObjectElement::virtualForm() const
{
return FormAssociatedElement::form();
}
}