/* * 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::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(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(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* paramNames, Vector* 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& paramNames, Vector& paramValues, String& url, String& serviceType) { HashSet 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(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 . 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 element into arrays, but don't override 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 tags, any other content is fallback content. if (child->isTextNode()) { if (!static_cast(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 paramNames; Vector 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(this->document()); document->addNamedItem(m_name); document->addExtraNamedItem(m_id); } FormAssociatedElement::insertedIntoDocument(); } void HTMLObjectElement::removedFromDocument() { if (isDocNamedItem() && document()->isHTMLDocument()) { HTMLDocument* document = static_cast(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, 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 " elements with no children other than // elements, unknown elements and whitespace can be // found by name in a document, and other elements cannot." bool wasNamedItem = m_docNamedItem; bool isNamedItem = true; Node* child = firstChild(); while (child && isNamedItem) { if (child->isElementNode()) { Element* element = static_cast(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(child)->containsOnlyWhitespace()) isNamedItem = false; } else isNamedItem = false; child = child->nextSibling(); } if (isNamedItem != wasNamedItem && document()->isHTMLDocument()) { HTMLDocument* document = static_cast(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(child)->containsJavaApplet()) return true; if (child->hasTagName(appletTag)) return true; } return false; } void HTMLObjectElement::addSubresourceAttributeURLs(ListHashSet& 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(); } }