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 "core/html/HTMLObjectElement.h"
     26 
     27 #include "HTMLNames.h"
     28 #include "bindings/v8/ScriptEventListener.h"
     29 #include "core/dom/Attribute.h"
     30 #include "core/dom/EventNames.h"
     31 #include "core/dom/NodeList.h"
     32 #include "core/dom/NodeTraversal.h"
     33 #include "core/dom/Text.h"
     34 #include "core/html/FormDataList.h"
     35 #include "core/html/HTMLDocument.h"
     36 #include "core/html/HTMLImageLoader.h"
     37 #include "core/html/HTMLMetaElement.h"
     38 #include "core/html/HTMLParamElement.h"
     39 #include "core/html/parser/HTMLParserIdioms.h"
     40 #include "core/loader/cache/ImageResource.h"
     41 #include "core/page/Frame.h"
     42 #include "core/page/Page.h"
     43 #include "core/page/Settings.h"
     44 #include "core/platform/MIMETypeRegistry.h"
     45 #include "core/platform/Widget.h"
     46 #include "core/plugins/PluginView.h"
     47 #include "core/rendering/RenderEmbeddedObject.h"
     48 
     49 namespace WebCore {
     50 
     51 using namespace HTMLNames;
     52 
     53 inline HTMLObjectElement::HTMLObjectElement(const QualifiedName& tagName, Document* document, HTMLFormElement* form, bool createdByParser)
     54     : HTMLPlugInImageElement(tagName, document, createdByParser, ShouldNotPreferPlugInsForImages)
     55     , m_docNamedItem(true)
     56     , m_useFallbackContent(false)
     57 {
     58     ASSERT(hasTagName(objectTag));
     59     setForm(form ? form : findFormAncestor());
     60     ScriptWrappable::init(this);
     61 }
     62 
     63 inline HTMLObjectElement::~HTMLObjectElement()
     64 {
     65 }
     66 
     67 PassRefPtr<HTMLObjectElement> HTMLObjectElement::create(const QualifiedName& tagName, Document* document, HTMLFormElement* form, bool createdByParser)
     68 {
     69     return adoptRef(new HTMLObjectElement(tagName, document, form, createdByParser));
     70 }
     71 
     72 RenderWidget* HTMLObjectElement::renderWidgetForJSBindings() const
     73 {
     74     document()->updateLayoutIgnorePendingStylesheets();
     75     return renderPart(); // This will return 0 if the renderer is not a RenderPart.
     76 }
     77 
     78 bool HTMLObjectElement::isPresentationAttribute(const QualifiedName& name) const
     79 {
     80     if (name == borderAttr)
     81         return true;
     82     return HTMLPlugInImageElement::isPresentationAttribute(name);
     83 }
     84 
     85 void HTMLObjectElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStylePropertySet* style)
     86 {
     87     if (name == borderAttr)
     88         applyBorderAttributeToStyle(value, style);
     89     else
     90         HTMLPlugInImageElement::collectStyleForPresentationAttribute(name, value, style);
     91 }
     92 
     93 void HTMLObjectElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
     94 {
     95     if (name == formAttr)
     96         formAttributeChanged();
     97     else if (name == typeAttr) {
     98         m_serviceType = value.lower();
     99         size_t pos = m_serviceType.find(";");
    100         if (pos != notFound)
    101             m_serviceType = m_serviceType.left(pos);
    102         if (renderer())
    103             setNeedsWidgetUpdate(true);
    104     } else if (name == dataAttr) {
    105         m_url = stripLeadingAndTrailingHTMLSpaces(value);
    106         if (renderer()) {
    107             setNeedsWidgetUpdate(true);
    108             if (isImageType()) {
    109                 if (!m_imageLoader)
    110                     m_imageLoader = adoptPtr(new HTMLImageLoader(this));
    111                 m_imageLoader->updateFromElementIgnoringPreviousError();
    112             }
    113         }
    114     } else if (name == classidAttr) {
    115         m_classId = value;
    116         if (renderer())
    117             setNeedsWidgetUpdate(true);
    118     } else if (name == onbeforeloadAttr)
    119         setAttributeEventListener(eventNames().beforeloadEvent, createAttributeEventListener(this, name, value));
    120     else
    121         HTMLPlugInImageElement::parseAttribute(name, value);
    122 }
    123 
    124 static void mapDataParamToSrc(Vector<String>* paramNames, Vector<String>* paramValues)
    125 {
    126     // Some plugins don't understand the "data" attribute of the OBJECT tag (i.e. Real and WMP
    127     // require "src" attribute).
    128     int srcIndex = -1, dataIndex = -1;
    129     for (unsigned int i = 0; i < paramNames->size(); ++i) {
    130         if (equalIgnoringCase((*paramNames)[i], "src"))
    131             srcIndex = i;
    132         else if (equalIgnoringCase((*paramNames)[i], "data"))
    133             dataIndex = i;
    134     }
    135 
    136     if (srcIndex == -1 && dataIndex != -1) {
    137         paramNames->append("src");
    138         paramValues->append((*paramValues)[dataIndex]);
    139     }
    140 }
    141 
    142 // FIXME: This function should not deal with url or serviceType!
    143 void HTMLObjectElement::parametersForPlugin(Vector<String>& paramNames, Vector<String>& paramValues, String& url, String& serviceType)
    144 {
    145     HashSet<StringImpl*, CaseFoldingHash> uniqueParamNames;
    146     String urlParameter;
    147 
    148     // Scan the PARAM children and store their name/value pairs.
    149     // Get the URL and type from the params if we don't already have them.
    150     for (Node* child = firstChild(); child; child = child->nextSibling()) {
    151         if (!child->hasTagName(paramTag))
    152             continue;
    153 
    154         HTMLParamElement* p = static_cast<HTMLParamElement*>(child);
    155         String name = p->name();
    156         if (name.isEmpty())
    157             continue;
    158 
    159         uniqueParamNames.add(name.impl());
    160         paramNames.append(p->name());
    161         paramValues.append(p->value());
    162 
    163         // FIXME: url adjustment does not belong in this function.
    164         if (url.isEmpty() && urlParameter.isEmpty() && (equalIgnoringCase(name, "src") || equalIgnoringCase(name, "movie") || equalIgnoringCase(name, "code") || equalIgnoringCase(name, "url")))
    165             urlParameter = stripLeadingAndTrailingHTMLSpaces(p->value());
    166         // FIXME: serviceType calculation does not belong in this function.
    167         if (serviceType.isEmpty() && equalIgnoringCase(name, "type")) {
    168             serviceType = p->value();
    169             size_t pos = serviceType.find(";");
    170             if (pos != notFound)
    171                 serviceType = serviceType.left(pos);
    172         }
    173     }
    174 
    175     // When OBJECT is used for an applet via Sun's Java plugin, the CODEBASE attribute in the tag
    176     // points to the Java plugin itself (an ActiveX component) while the actual applet CODEBASE is
    177     // in a PARAM tag. See <http://java.sun.com/products/plugin/1.2/docs/tags.html>. This means
    178     // we have to explicitly suppress the tag's CODEBASE attribute if there is none in a PARAM,
    179     // else our Java plugin will misinterpret it. [4004531]
    180     String codebase;
    181     if (MIMETypeRegistry::isJavaAppletMIMEType(serviceType)) {
    182         codebase = "codebase";
    183         uniqueParamNames.add(codebase.impl()); // pretend we found it in a PARAM already
    184     }
    185 
    186     // Turn the attributes of the <object> element into arrays, but don't override <param> values.
    187     if (hasAttributes()) {
    188         for (unsigned i = 0; i < attributeCount(); ++i) {
    189             const Attribute* attribute = attributeItem(i);
    190             const AtomicString& name = attribute->name().localName();
    191             if (!uniqueParamNames.contains(name.impl())) {
    192                 paramNames.append(name.string());
    193                 paramValues.append(attribute->value().string());
    194             }
    195         }
    196     }
    197 
    198     mapDataParamToSrc(&paramNames, &paramValues);
    199 
    200     // HTML5 says that an object resource's URL is specified by the object's data
    201     // attribute, not by a param element. However, for compatibility, allow the
    202     // resource's URL to be given by a param named "src", "movie", "code" or "url"
    203     // if we know that resource points to a plug-in.
    204     if (url.isEmpty() && !urlParameter.isEmpty()) {
    205         KURL completedURL = document()->completeURL(urlParameter);
    206         bool useFallback;
    207         if (shouldUsePlugin(completedURL, serviceType, false, useFallback))
    208             url = urlParameter;
    209     }
    210 }
    211 
    212 
    213 bool HTMLObjectElement::hasFallbackContent() const
    214 {
    215     for (Node* child = firstChild(); child; child = child->nextSibling()) {
    216         // Ignore whitespace-only text, and <param> tags, any other content is fallback content.
    217         if (child->isTextNode()) {
    218             if (!toText(child)->containsOnlyWhitespace())
    219                 return true;
    220         } else if (!child->hasTagName(paramTag))
    221             return true;
    222     }
    223     return false;
    224 }
    225 
    226 bool HTMLObjectElement::shouldAllowQuickTimeClassIdQuirk()
    227 {
    228     // This site-specific hack maintains compatibility with Mac OS X Wiki Server,
    229     // which embeds QuickTime movies using an object tag containing QuickTime's
    230     // ActiveX classid. Treat this classid as valid only if OS X Server's unique
    231     // 'generator' meta tag is present. Only apply this quirk if there is no
    232     // fallback content, which ensures the quirk will disable itself if Wiki
    233     // Server is updated to generate an alternate embed tag as fallback content.
    234     if (!document()->page()
    235         || !document()->page()->settings()->needsSiteSpecificQuirks()
    236         || hasFallbackContent()
    237         || !equalIgnoringCase(classId(), "clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B"))
    238         return false;
    239 
    240     RefPtr<NodeList> metaElements = document()->getElementsByTagName(HTMLNames::metaTag.localName());
    241     unsigned length = metaElements->length();
    242     for (unsigned i = 0; i < length; ++i) {
    243         ASSERT(metaElements->item(i)->isHTMLElement());
    244         HTMLMetaElement* metaElement = static_cast<HTMLMetaElement*>(metaElements->item(i));
    245         if (equalIgnoringCase(metaElement->name(), "generator") && metaElement->content().startsWith("Mac OS X Server Web Services Server", false))
    246             return true;
    247     }
    248 
    249     return false;
    250 }
    251 
    252 bool HTMLObjectElement::hasValidClassId()
    253 {
    254     if (MIMETypeRegistry::isJavaAppletMIMEType(serviceType()) && classId().startsWith("java:", false))
    255         return true;
    256 
    257     if (shouldAllowQuickTimeClassIdQuirk())
    258         return true;
    259 
    260     // HTML5 says that fallback content should be rendered if a non-empty
    261     // classid is specified for which the UA can't find a suitable plug-in.
    262     return classId().isEmpty();
    263 }
    264 
    265 // FIXME: This should be unified with HTMLEmbedElement::updateWidget and
    266 // moved down into HTMLPluginImageElement.cpp
    267 void HTMLObjectElement::updateWidget(PluginCreationOption pluginCreationOption)
    268 {
    269     ASSERT(!renderEmbeddedObject()->showsUnavailablePluginIndicator());
    270     ASSERT(needsWidgetUpdate());
    271     setNeedsWidgetUpdate(false);
    272     // FIXME: This should ASSERT isFinishedParsingChildren() instead.
    273     if (!isFinishedParsingChildren())
    274         return;
    275 
    276     // FIXME: I'm not sure it's ever possible to get into updateWidget during a
    277     // removal, but just in case we should avoid loading the frame to prevent
    278     // security bugs.
    279     if (!SubframeLoadingDisabler::canLoadFrame(this))
    280         return;
    281 
    282     String url = this->url();
    283     String serviceType = this->serviceType();
    284 
    285     // FIXME: These should be joined into a PluginParameters class.
    286     Vector<String> paramNames;
    287     Vector<String> paramValues;
    288     parametersForPlugin(paramNames, paramValues, url, serviceType);
    289 
    290     // Note: url is modified above by parametersForPlugin.
    291     if (!allowedToLoadFrameURL(url))
    292         return;
    293 
    294     bool fallbackContent = hasFallbackContent();
    295     renderEmbeddedObject()->setHasFallbackContent(fallbackContent);
    296 
    297     // FIXME: It's sadness that we have this special case here.
    298     //        See http://trac.webkit.org/changeset/25128 and
    299     //        plugins/netscape-plugin-setwindow-size.html
    300     if (pluginCreationOption == CreateOnlyNonNetscapePlugins && wouldLoadAsNetscapePlugin(url, serviceType)) {
    301         // Ensure updateWidget() is called again during layout to create the Netscape plug-in.
    302         setNeedsWidgetUpdate(true);
    303         return;
    304     }
    305 
    306     RefPtr<HTMLObjectElement> protect(this); // beforeload and plugin loading can make arbitrary DOM mutations.
    307     bool beforeLoadAllowedLoad = dispatchBeforeLoadEvent(url);
    308     if (!renderer()) // Do not load the plugin if beforeload removed this element or its renderer.
    309         return;
    310 
    311     bool success = beforeLoadAllowedLoad && hasValidClassId() && requestObject(url, serviceType, paramNames, paramValues);
    312     if (!success && fallbackContent)
    313         renderFallbackContent();
    314 }
    315 
    316 bool HTMLObjectElement::rendererIsNeeded(const NodeRenderingContext& context)
    317 {
    318     // FIXME: This check should not be needed, detached documents never render!
    319     Frame* frame = document()->frame();
    320     if (!frame)
    321         return false;
    322 
    323     return HTMLPlugInImageElement::rendererIsNeeded(context);
    324 }
    325 
    326 Node::InsertionNotificationRequest HTMLObjectElement::insertedInto(ContainerNode* insertionPoint)
    327 {
    328     HTMLPlugInImageElement::insertedInto(insertionPoint);
    329     FormAssociatedElement::insertedInto(insertionPoint);
    330     return InsertionDone;
    331 }
    332 
    333 void HTMLObjectElement::removedFrom(ContainerNode* insertionPoint)
    334 {
    335     HTMLPlugInImageElement::removedFrom(insertionPoint);
    336     FormAssociatedElement::removedFrom(insertionPoint);
    337 }
    338 
    339 void HTMLObjectElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta)
    340 {
    341     updateDocNamedItem();
    342     if (inDocument() && !useFallbackContent()) {
    343         setNeedsWidgetUpdate(true);
    344         setNeedsStyleRecalc();
    345     }
    346     HTMLPlugInImageElement::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta);
    347 }
    348 
    349 bool HTMLObjectElement::isURLAttribute(const Attribute& attribute) const
    350 {
    351     return attribute.name() == dataAttr || (attribute.name() == usemapAttr && attribute.value().string()[0] != '#') || HTMLPlugInImageElement::isURLAttribute(attribute);
    352 }
    353 
    354 const AtomicString& HTMLObjectElement::imageSourceURL() const
    355 {
    356     return getAttribute(dataAttr);
    357 }
    358 
    359 // FIXME: Remove this hack.
    360 void HTMLObjectElement::reattachFallbackContent()
    361 {
    362     // This can happen inside of attach() in the middle of a recalcStyle so we need to
    363     // reattach synchronously here.
    364     if (document()->inStyleRecalc())
    365         reattach();
    366     else
    367         lazyReattach();
    368 }
    369 
    370 void HTMLObjectElement::renderFallbackContent()
    371 {
    372     if (useFallbackContent())
    373         return;
    374 
    375     if (!inDocument())
    376         return;
    377 
    378     // Before we give up and use fallback content, check to see if this is a MIME type issue.
    379     if (m_imageLoader && m_imageLoader->image() && m_imageLoader->image()->status() != Resource::LoadError) {
    380         m_serviceType = m_imageLoader->image()->response().mimeType();
    381         if (!isImageType()) {
    382             // If we don't think we have an image type anymore, then clear the image from the loader.
    383             m_imageLoader->setImage(0);
    384             reattachFallbackContent();
    385             return;
    386         }
    387     }
    388 
    389     m_useFallbackContent = true;
    390 
    391     // FIXME: Style gets recalculated which is suboptimal.
    392     reattachFallbackContent();
    393 }
    394 
    395 // FIXME: This should be removed, all callers are almost certainly wrong.
    396 static bool isRecognizedTagName(const QualifiedName& tagName)
    397 {
    398     DEFINE_STATIC_LOCAL(HashSet<StringImpl*>, tagList, ());
    399     if (tagList.isEmpty()) {
    400         QualifiedName** tags = HTMLNames::getHTMLTags();
    401         for (size_t i = 0; i < HTMLNames::HTMLTagsCount; i++) {
    402             if (*tags[i] == bgsoundTag
    403                 || *tags[i] == commandTag
    404                 || *tags[i] == detailsTag
    405                 || *tags[i] == figcaptionTag
    406                 || *tags[i] == figureTag
    407                 || *tags[i] == summaryTag
    408                 || *tags[i] == trackTag) {
    409                 // Even though we have atoms for these tags, we don't want to
    410                 // treat them as "recognized tags" for the purpose of parsing
    411                 // because that changes how we parse documents.
    412                 continue;
    413             }
    414             tagList.add(tags[i]->localName().impl());
    415         }
    416     }
    417     return tagList.contains(tagName.localName().impl());
    418 }
    419 
    420 void HTMLObjectElement::updateDocNamedItem()
    421 {
    422     // The rule is "<object> elements with no children other than
    423     // <param> elements, unknown elements and whitespace can be
    424     // found by name in a document, and other <object> elements cannot."
    425     bool wasNamedItem = m_docNamedItem;
    426     bool isNamedItem = true;
    427     Node* child = firstChild();
    428     while (child && isNamedItem) {
    429         if (child->isElementNode()) {
    430             Element* element = toElement(child);
    431             // FIXME: Use of isRecognizedTagName is almost certainly wrong here.
    432             if (isRecognizedTagName(element->tagQName()) && !element->hasTagName(paramTag))
    433                 isNamedItem = false;
    434         } else if (child->isTextNode()) {
    435             if (!toText(child)->containsOnlyWhitespace())
    436                 isNamedItem = false;
    437         } else
    438             isNamedItem = false;
    439         child = child->nextSibling();
    440     }
    441     if (isNamedItem != wasNamedItem && document()->isHTMLDocument()) {
    442         HTMLDocument* document = toHTMLDocument(this->document());
    443         if (isNamedItem) {
    444             document->addNamedItem(getNameAttribute());
    445             document->addExtraNamedItem(getIdAttribute());
    446         } else {
    447             document->removeNamedItem(getNameAttribute());
    448             document->removeExtraNamedItem(getIdAttribute());
    449         }
    450     }
    451     m_docNamedItem = isNamedItem;
    452 }
    453 
    454 bool HTMLObjectElement::containsJavaApplet() const
    455 {
    456     if (MIMETypeRegistry::isJavaAppletMIMEType(getAttribute(typeAttr)))
    457         return true;
    458 
    459     for (Element* child = ElementTraversal::firstWithin(this); child; child = ElementTraversal::nextSkippingChildren(child, this)) {
    460         if (child->hasTagName(paramTag)
    461                 && equalIgnoringCase(child->getNameAttribute(), "type")
    462                 && MIMETypeRegistry::isJavaAppletMIMEType(child->getAttribute(valueAttr).string()))
    463             return true;
    464         if (child->hasTagName(objectTag)
    465                 && static_cast<HTMLObjectElement*>(child)->containsJavaApplet())
    466             return true;
    467         if (child->hasTagName(appletTag))
    468             return true;
    469     }
    470 
    471     return false;
    472 }
    473 
    474 void HTMLObjectElement::addSubresourceAttributeURLs(ListHashSet<KURL>& urls) const
    475 {
    476     HTMLPlugInImageElement::addSubresourceAttributeURLs(urls);
    477 
    478     addSubresourceURL(urls, document()->completeURL(getAttribute(dataAttr)));
    479 
    480     // FIXME: Passing a string that starts with "#" to the completeURL function does
    481     // not seem like it would work. The image element has similar but not identical code.
    482     const AtomicString& useMap = getAttribute(usemapAttr);
    483     if (useMap.startsWith('#'))
    484         addSubresourceURL(urls, document()->completeURL(useMap));
    485 }
    486 
    487 void HTMLObjectElement::didMoveToNewDocument(Document* oldDocument)
    488 {
    489     FormAssociatedElement::didMoveToNewDocument(oldDocument);
    490     HTMLPlugInImageElement::didMoveToNewDocument(oldDocument);
    491 }
    492 
    493 bool HTMLObjectElement::appendFormData(FormDataList& encoding, bool)
    494 {
    495     if (name().isEmpty())
    496         return false;
    497 
    498     Widget* widget = pluginWidget();
    499     if (!widget || !widget->isPluginView())
    500         return false;
    501     String value;
    502     if (!toPluginView(widget)->getFormValue(value))
    503         return false;
    504     encoding.appendData(name(), value);
    505     return true;
    506 }
    507 
    508 HTMLFormElement* HTMLObjectElement::virtualForm() const
    509 {
    510     return FormAssociatedElement::form();
    511 }
    512 
    513 }
    514