Home | History | Annotate | Download | only in html
      1 /*
      2  * Copyright (C) 1999 Lars Knoll (knoll (at) kde.org)
      3  *           (C) 1999 Antti Koivisto (koivisto (at) kde.org)
      4  *           (C) 2000 Stefan Schimanski (1Stein (at) gmx.de)
      5  * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2011 Apple Inc. All rights reserved.
      6  * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
      7  *
      8  * This library is free software; you can redistribute it and/or
      9  * modify it under the terms of the GNU Library General Public
     10  * License as published by the Free Software Foundation; either
     11  * version 2 of the License, or (at your option) any later version.
     12  *
     13  * This library is distributed in the hope that it will be useful,
     14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     16  * Library General Public License for more details.
     17  *
     18  * You should have received a copy of the GNU Library General Public License
     19  * along with this library; see the file COPYING.LIB.  If not, write to
     20  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     21  * Boston, MA 02110-1301, USA.
     22  */
     23 
     24 #include "config.h"
     25 #include "HTMLObjectElement.h"
     26 
     27 #include "Attribute.h"
     28 #include "CSSValueKeywords.h"
     29 #include "EventNames.h"
     30 #include "ExceptionCode.h"
     31 #include "Frame.h"
     32 #include "HTMLDocument.h"
     33 #include "HTMLFormElement.h"
     34 #include "HTMLImageLoader.h"
     35 #include "HTMLNames.h"
     36 #include "HTMLParamElement.h"
     37 #include "HTMLParserIdioms.h"
     38 #include "MIMETypeRegistry.h"
     39 #include "RenderEmbeddedObject.h"
     40 #include "RenderImage.h"
     41 #include "RenderWidget.h"
     42 #include "ScriptController.h"
     43 #include "ScriptEventListener.h"
     44 #include "Text.h"
     45 
     46 namespace WebCore {
     47 
     48 using namespace HTMLNames;
     49 
     50 inline HTMLObjectElement::HTMLObjectElement(const QualifiedName& tagName, Document* document, HTMLFormElement* form, bool createdByParser)
     51     : HTMLPlugInImageElement(tagName, document, createdByParser, ShouldNotPreferPlugInsForImages)
     52     , FormAssociatedElement(form)
     53     , m_docNamedItem(true)
     54     , m_useFallbackContent(false)
     55 {
     56     ASSERT(hasTagName(objectTag));
     57     if (!this->form())
     58         setForm(findFormAncestor());
     59     if (this->form())
     60         this->form()->registerFormElement(this);
     61 }
     62 
     63 inline HTMLObjectElement::~HTMLObjectElement()
     64 {
     65     if (form())
     66         form()->removeFormElement(this);
     67 }
     68 
     69 PassRefPtr<HTMLObjectElement> HTMLObjectElement::create(const QualifiedName& tagName, Document* document, HTMLFormElement* form, bool createdByParser)
     70 {
     71     return adoptRef(new HTMLObjectElement(tagName, document, form, createdByParser));
     72 }
     73 
     74 RenderWidget* HTMLObjectElement::renderWidgetForJSBindings() const
     75 {
     76     document()->updateLayoutIgnorePendingStylesheets();
     77     return renderPart(); // This will return 0 if the renderer is not a RenderPart.
     78 }
     79 
     80 void HTMLObjectElement::parseMappedAttribute(Attribute* attr)
     81 {
     82     if (attr->name() == typeAttr) {
     83         m_serviceType = attr->value().lower();
     84         size_t pos = m_serviceType.find(";");
     85         if (pos != notFound)
     86             m_serviceType = m_serviceType.left(pos);
     87         if (renderer())
     88             setNeedsWidgetUpdate(true);
     89         if (!isImageType() && m_imageLoader)
     90             m_imageLoader.clear();
     91     } else if (attr->name() == dataAttr) {
     92         m_url = stripLeadingAndTrailingHTMLSpaces(attr->value());
     93         if (renderer()) {
     94             setNeedsWidgetUpdate(true);
     95             if (isImageType()) {
     96                 if (!m_imageLoader)
     97                     m_imageLoader = adoptPtr(new HTMLImageLoader(this));
     98                 m_imageLoader->updateFromElementIgnoringPreviousError();
     99             }
    100         }
    101     } else if (attr->name() == classidAttr) {
    102         m_classId = attr->value();
    103         if (renderer())
    104             setNeedsWidgetUpdate(true);
    105     } else if (attr->name() == onloadAttr)
    106         setAttributeEventListener(eventNames().loadEvent, createAttributeEventListener(this, attr));
    107     else if (attr->name() == onbeforeloadAttr)
    108         setAttributeEventListener(eventNames().beforeloadEvent, createAttributeEventListener(this, attr));
    109     else if (attr->name() == nameAttr) {
    110         const AtomicString& newName = attr->value();
    111         if (isDocNamedItem() && inDocument() && document()->isHTMLDocument()) {
    112             HTMLDocument* document = static_cast<HTMLDocument*>(this->document());
    113             document->removeNamedItem(m_name);
    114             document->addNamedItem(newName);
    115         }
    116         m_name = newName;
    117     } else if (attr->name() == borderAttr) {
    118         addCSSLength(attr, CSSPropertyBorderWidth, attr->value().toInt() ? attr->value() : "0");
    119         addCSSProperty(attr, CSSPropertyBorderTopStyle, CSSValueSolid);
    120         addCSSProperty(attr, CSSPropertyBorderRightStyle, CSSValueSolid);
    121         addCSSProperty(attr, CSSPropertyBorderBottomStyle, CSSValueSolid);
    122         addCSSProperty(attr, CSSPropertyBorderLeftStyle, CSSValueSolid);
    123     } else if (isIdAttributeName(attr->name())) {
    124         const AtomicString& newId = attr->value();
    125         if (isDocNamedItem() && inDocument() && document()->isHTMLDocument()) {
    126             HTMLDocument* document = static_cast<HTMLDocument*>(this->document());
    127             document->removeExtraNamedItem(m_id);
    128             document->addExtraNamedItem(newId);
    129         }
    130         m_id = newId;
    131         // also call superclass
    132         HTMLPlugInImageElement::parseMappedAttribute(attr);
    133     } else
    134         HTMLPlugInImageElement::parseMappedAttribute(attr);
    135 }
    136 
    137 static void mapDataParamToSrc(Vector<String>* paramNames, Vector<String>* paramValues)
    138 {
    139     // Some plugins don't understand the "data" attribute of the OBJECT tag (i.e. Real and WMP
    140     // require "src" attribute).
    141     int srcIndex = -1, dataIndex = -1;
    142     for (unsigned int i = 0; i < paramNames->size(); ++i) {
    143         if (equalIgnoringCase((*paramNames)[i], "src"))
    144             srcIndex = i;
    145         else if (equalIgnoringCase((*paramNames)[i], "data"))
    146             dataIndex = i;
    147     }
    148 
    149     if (srcIndex == -1 && dataIndex != -1) {
    150         paramNames->append("src");
    151         paramValues->append((*paramValues)[dataIndex]);
    152     }
    153 }
    154 
    155 // FIXME: This function should not deal with url or serviceType!
    156 void HTMLObjectElement::parametersForPlugin(Vector<String>& paramNames, Vector<String>& paramValues, String& url, String& serviceType)
    157 {
    158     HashSet<StringImpl*, CaseFoldingHash> uniqueParamNames;
    159     String urlParameter;
    160 
    161     // Scan the PARAM children and store their name/value pairs.
    162     // Get the URL and type from the params if we don't already have them.
    163     for (Node* child = firstChild(); child; child = child->nextSibling()) {
    164         if (!child->hasTagName(paramTag))
    165             continue;
    166 
    167         HTMLParamElement* p = static_cast<HTMLParamElement*>(child);
    168         String name = p->name();
    169         if (name.isEmpty())
    170             continue;
    171 
    172         uniqueParamNames.add(name.impl());
    173         paramNames.append(p->name());
    174         paramValues.append(p->value());
    175 
    176         // FIXME: url adjustment does not belong in this function.
    177         if (url.isEmpty() && urlParameter.isEmpty() && (equalIgnoringCase(name, "src") || equalIgnoringCase(name, "movie") || equalIgnoringCase(name, "code") || equalIgnoringCase(name, "url")))
    178             urlParameter = stripLeadingAndTrailingHTMLSpaces(p->value());
    179         // FIXME: serviceType calculation does not belong in this function.
    180         if (serviceType.isEmpty() && equalIgnoringCase(name, "type")) {
    181             serviceType = p->value();
    182             size_t pos = serviceType.find(";");
    183             if (pos != notFound)
    184                 serviceType = serviceType.left(pos);
    185         }
    186     }
    187 
    188     // When OBJECT is used for an applet via Sun's Java plugin, the CODEBASE attribute in the tag
    189     // points to the Java plugin itself (an ActiveX component) while the actual applet CODEBASE is
    190     // in a PARAM tag. See <http://java.sun.com/products/plugin/1.2/docs/tags.html>. This means
    191     // we have to explicitly suppress the tag's CODEBASE attribute if there is none in a PARAM,
    192     // else our Java plugin will misinterpret it. [4004531]
    193     String codebase;
    194     if (MIMETypeRegistry::isJavaAppletMIMEType(serviceType)) {
    195         codebase = "codebase";
    196         uniqueParamNames.add(codebase.impl()); // pretend we found it in a PARAM already
    197     }
    198 
    199     // Turn the attributes of the <object> element into arrays, but don't override <param> values.
    200     NamedNodeMap* attributes = this->attributes(true);
    201     if (attributes) {
    202         for (unsigned i = 0; i < attributes->length(); ++i) {
    203             Attribute* it = attributes->attributeItem(i);
    204             const AtomicString& name = it->name().localName();
    205             if (!uniqueParamNames.contains(name.impl())) {
    206                 paramNames.append(name.string());
    207                 paramValues.append(it->value().string());
    208             }
    209         }
    210     }
    211 
    212     mapDataParamToSrc(&paramNames, &paramValues);
    213 
    214     // HTML5 says that an object resource's URL is specified by the object's data
    215     // attribute, not by a param element. However, for compatibility, allow the
    216     // resource's URL to be given by a param named "src", "movie", "code" or "url"
    217     // if we know that resource points to a plug-in.
    218     if (url.isEmpty() && !urlParameter.isEmpty()) {
    219         SubframeLoader* loader = document()->frame()->loader()->subframeLoader();
    220         if (loader->resourceWillUsePlugin(urlParameter, serviceType, shouldPreferPlugInsForImages()))
    221             url = urlParameter;
    222     }
    223 }
    224 
    225 
    226 bool HTMLObjectElement::hasFallbackContent() const
    227 {
    228     for (Node* child = firstChild(); child; child = child->nextSibling()) {
    229         // Ignore whitespace-only text, and <param> tags, any other content is fallback content.
    230         if (child->isTextNode()) {
    231             if (!static_cast<Text*>(child)->containsOnlyWhitespace())
    232                 return true;
    233         } else if (!child->hasTagName(paramTag))
    234             return true;
    235     }
    236     return false;
    237 }
    238 
    239 bool HTMLObjectElement::hasValidClassId()
    240 {
    241 #if PLATFORM(QT)
    242     if (equalIgnoringCase(serviceType(), "application/x-qt-plugin") || equalIgnoringCase(serviceType(), "application/x-qt-styled-widget"))
    243         return true;
    244 #endif
    245 
    246     if (MIMETypeRegistry::isJavaAppletMIMEType(serviceType()) && classId().startsWith("java:", false))
    247         return true;
    248 
    249     // HTML5 says that fallback content should be rendered if a non-empty
    250     // classid is specified for which the UA can't find a suitable plug-in.
    251     return classId().isEmpty();
    252 }
    253 
    254 // FIXME: This should be unified with HTMLEmbedElement::updateWidget and
    255 // moved down into HTMLPluginImageElement.cpp
    256 void HTMLObjectElement::updateWidget(PluginCreationOption pluginCreationOption)
    257 {
    258     ASSERT(!renderEmbeddedObject()->pluginCrashedOrWasMissing());
    259     // FIXME: We should ASSERT(needsWidgetUpdate()), but currently
    260     // FrameView::updateWidget() calls updateWidget(false) without checking if
    261     // the widget actually needs updating!
    262     setNeedsWidgetUpdate(false);
    263     // FIXME: This should ASSERT isFinishedParsingChildren() instead.
    264     if (!isFinishedParsingChildren())
    265         return;
    266 
    267     String url = this->url();
    268     String serviceType = this->serviceType();
    269 
    270     // FIXME: These should be joined into a PluginParameters class.
    271     Vector<String> paramNames;
    272     Vector<String> paramValues;
    273     parametersForPlugin(paramNames, paramValues, url, serviceType);
    274 
    275     // Note: url is modified above by parametersForPlugin.
    276     if (!allowedToLoadFrameURL(url))
    277         return;
    278 
    279     bool fallbackContent = hasFallbackContent();
    280     renderEmbeddedObject()->setHasFallbackContent(fallbackContent);
    281 
    282     if (pluginCreationOption == CreateOnlyNonNetscapePlugins && wouldLoadAsNetscapePlugin(url, serviceType))
    283         return;
    284 
    285     ASSERT(!m_inBeforeLoadEventHandler);
    286     m_inBeforeLoadEventHandler = true;
    287     bool beforeLoadAllowedLoad = dispatchBeforeLoadEvent(url);
    288     m_inBeforeLoadEventHandler = false;
    289 
    290     // beforeload events can modify the DOM, potentially causing
    291     // RenderWidget::destroy() to be called.  Ensure we haven't been
    292     // destroyed before continuing.
    293     // FIXME: Should this render fallback content?
    294     if (!renderer())
    295         return;
    296 
    297     SubframeLoader* loader = document()->frame()->loader()->subframeLoader();
    298     bool success = beforeLoadAllowedLoad && hasValidClassId() && loader->requestObject(this, url, getAttribute(nameAttr), serviceType, paramNames, paramValues);
    299 
    300     if (!success && fallbackContent)
    301         renderFallbackContent();
    302 }
    303 
    304 bool HTMLObjectElement::rendererIsNeeded(RenderStyle* style)
    305 {
    306     // FIXME: This check should not be needed, detached documents never render!
    307     Frame* frame = document()->frame();
    308     if (!frame)
    309         return false;
    310 
    311     return HTMLPlugInImageElement::rendererIsNeeded(style);
    312 }
    313 
    314 void HTMLObjectElement::insertedIntoDocument()
    315 {
    316     HTMLPlugInImageElement::insertedIntoDocument();
    317     if (!inDocument())
    318         return;
    319 
    320     if (isDocNamedItem() && document()->isHTMLDocument()) {
    321         HTMLDocument* document = static_cast<HTMLDocument*>(this->document());
    322         document->addNamedItem(m_name);
    323         document->addExtraNamedItem(m_id);
    324     }
    325 
    326     FormAssociatedElement::insertedIntoDocument();
    327 }
    328 
    329 void HTMLObjectElement::removedFromDocument()
    330 {
    331     if (isDocNamedItem() && document()->isHTMLDocument()) {
    332         HTMLDocument* document = static_cast<HTMLDocument*>(this->document());
    333         document->removeNamedItem(m_name);
    334         document->removeExtraNamedItem(m_id);
    335     }
    336 
    337     HTMLPlugInImageElement::removedFromDocument();
    338     FormAssociatedElement::removedFromDocument();
    339 }
    340 
    341 void HTMLObjectElement::attributeChanged(Attribute* attr, bool preserveDecls)
    342 {
    343     if (attr->name() == formAttr)
    344         formAttributeChanged();
    345     else
    346         HTMLPlugInImageElement::attributeChanged(attr, preserveDecls);
    347 }
    348 
    349 void HTMLObjectElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta)
    350 {
    351     updateDocNamedItem();
    352     if (inDocument() && !useFallbackContent()) {
    353         setNeedsWidgetUpdate(true);
    354         setNeedsStyleRecalc();
    355     }
    356     HTMLPlugInImageElement::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta);
    357 }
    358 
    359 bool HTMLObjectElement::isURLAttribute(Attribute *attr) const
    360 {
    361     return (attr->name() == dataAttr || (attr->name() == usemapAttr && attr->value().string()[0] != '#'));
    362 }
    363 
    364 const QualifiedName& HTMLObjectElement::imageSourceAttributeName() const
    365 {
    366     return dataAttr;
    367 }
    368 
    369 void HTMLObjectElement::renderFallbackContent()
    370 {
    371     if (useFallbackContent())
    372         return;
    373 
    374     if (!inDocument())
    375         return;
    376 
    377     // Before we give up and use fallback content, check to see if this is a MIME type issue.
    378     if (m_imageLoader && m_imageLoader->image() && m_imageLoader->image()->status() != CachedResource::LoadError) {
    379         m_serviceType = m_imageLoader->image()->response().mimeType();
    380         if (!isImageType()) {
    381             // If we don't think we have an image type anymore, then clear the image from the loader.
    382             m_imageLoader->setImage(0);
    383             detach();
    384             attach();
    385             return;
    386         }
    387     }
    388 
    389     m_useFallbackContent = true;
    390 
    391     // FIXME: Style gets recalculated which is suboptimal.
    392     detach();
    393     attach();
    394 }
    395 
    396 // FIXME: This should be removed, all callers are almost certainly wrong.
    397 static bool isRecognizedTagName(const QualifiedName& tagName)
    398 {
    399     DEFINE_STATIC_LOCAL(HashSet<AtomicStringImpl*>, tagList, ());
    400     if (tagList.isEmpty()) {
    401         size_t tagCount = 0;
    402         QualifiedName** tags = HTMLNames::getHTMLTags(&tagCount);
    403         for (size_t i = 0; i < tagCount; i++) {
    404             if (*tags[i] == bgsoundTag
    405                 || *tags[i] == commandTag
    406                 || *tags[i] == detailsTag
    407                 || *tags[i] == figcaptionTag
    408                 || *tags[i] == figureTag
    409                 || *tags[i] == summaryTag
    410                 || *tags[i] == trackTag) {
    411                 // Even though we have atoms for these tags, we don't want to
    412                 // treat them as "recognized tags" for the purpose of parsing
    413                 // because that changes how we parse documents.
    414                 continue;
    415             }
    416             tagList.add(tags[i]->localName().impl());
    417         }
    418     }
    419     return tagList.contains(tagName.localName().impl());
    420 }
    421 
    422 void HTMLObjectElement::updateDocNamedItem()
    423 {
    424     // The rule is "<object> elements with no children other than
    425     // <param> elements, unknown elements and whitespace can be
    426     // found by name in a document, and other <object> elements cannot."
    427     bool wasNamedItem = m_docNamedItem;
    428     bool isNamedItem = true;
    429     Node* child = firstChild();
    430     while (child && isNamedItem) {
    431         if (child->isElementNode()) {
    432             Element* element = static_cast<Element*>(child);
    433             // FIXME: Use of isRecognizedTagName is almost certainly wrong here.
    434             if (isRecognizedTagName(element->tagQName()) && !element->hasTagName(paramTag))
    435                 isNamedItem = false;
    436         } else if (child->isTextNode()) {
    437             if (!static_cast<Text*>(child)->containsOnlyWhitespace())
    438                 isNamedItem = false;
    439         } else
    440             isNamedItem = false;
    441         child = child->nextSibling();
    442     }
    443     if (isNamedItem != wasNamedItem && document()->isHTMLDocument()) {
    444         HTMLDocument* document = static_cast<HTMLDocument*>(this->document());
    445         if (isNamedItem) {
    446             document->addNamedItem(m_name);
    447             document->addExtraNamedItem(m_id);
    448         } else {
    449             document->removeNamedItem(m_name);
    450             document->removeExtraNamedItem(m_id);
    451         }
    452     }
    453     m_docNamedItem = isNamedItem;
    454 }
    455 
    456 bool HTMLObjectElement::containsJavaApplet() const
    457 {
    458     if (MIMETypeRegistry::isJavaAppletMIMEType(getAttribute(typeAttr)))
    459         return true;
    460 
    461     for (Element* child = firstElementChild(); child; child = child->nextElementSibling()) {
    462         if (child->hasTagName(paramTag)
    463                 && equalIgnoringCase(child->getAttribute(nameAttr), "type")
    464                 && MIMETypeRegistry::isJavaAppletMIMEType(child->getAttribute(valueAttr).string()))
    465             return true;
    466         if (child->hasTagName(objectTag)
    467                 && static_cast<HTMLObjectElement*>(child)->containsJavaApplet())
    468             return true;
    469         if (child->hasTagName(appletTag))
    470             return true;
    471     }
    472 
    473     return false;
    474 }
    475 
    476 void HTMLObjectElement::addSubresourceAttributeURLs(ListHashSet<KURL>& urls) const
    477 {
    478     HTMLPlugInImageElement::addSubresourceAttributeURLs(urls);
    479 
    480     addSubresourceURL(urls, document()->completeURL(getAttribute(dataAttr)));
    481 
    482     // FIXME: Passing a string that starts with "#" to the completeURL function does
    483     // not seem like it would work. The image element has similar but not identical code.
    484     const AtomicString& useMap = getAttribute(usemapAttr);
    485     if (useMap.startsWith("#"))
    486         addSubresourceURL(urls, document()->completeURL(useMap));
    487 }
    488 
    489 void HTMLObjectElement::willMoveToNewOwnerDocument()
    490 {
    491     FormAssociatedElement::willMoveToNewOwnerDocument();
    492     HTMLPlugInImageElement::willMoveToNewOwnerDocument();
    493 }
    494 
    495 void HTMLObjectElement::insertedIntoTree(bool deep)
    496 {
    497     FormAssociatedElement::insertedIntoTree();
    498     HTMLPlugInImageElement::insertedIntoTree(deep);
    499 }
    500 
    501 void HTMLObjectElement::removedFromTree(bool deep)
    502 {
    503     FormAssociatedElement::removedFromTree();
    504     HTMLPlugInImageElement::removedFromTree(deep);
    505 }
    506 
    507 bool HTMLObjectElement::appendFormData(FormDataList&, bool)
    508 {
    509     // FIXME: Implements this function.
    510     return false;
    511 }
    512 
    513 const AtomicString& HTMLObjectElement::formControlName() const
    514 {
    515     return m_name.isNull() ? emptyAtom : m_name;
    516 }
    517 
    518 HTMLFormElement* HTMLObjectElement::virtualForm() const
    519 {
    520     return FormAssociatedElement::form();
    521 }
    522 
    523 }
    524