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 "bindings/v8/ScriptEventListener.h"
     28 #include "core/HTMLNames.h"
     29 #include "core/dom/Attribute.h"
     30 #include "core/dom/ElementTraversal.h"
     31 #include "core/dom/TagCollection.h"
     32 #include "core/dom/Text.h"
     33 #include "core/dom/shadow/ShadowRoot.h"
     34 #include "core/fetch/ImageResource.h"
     35 #include "core/html/FormDataList.h"
     36 #include "core/html/HTMLDocument.h"
     37 #include "core/html/HTMLImageLoader.h"
     38 #include "core/html/HTMLMetaElement.h"
     39 #include "core/html/HTMLParamElement.h"
     40 #include "core/html/parser/HTMLParserIdioms.h"
     41 #include "core/frame/Settings.h"
     42 #include "core/plugins/PluginView.h"
     43 #include "core/rendering/RenderEmbeddedObject.h"
     44 #include "platform/MIMETypeRegistry.h"
     45 #include "platform/Widget.h"
     46 
     47 namespace WebCore {
     48 
     49 using namespace HTMLNames;
     50 
     51 inline HTMLObjectElement::HTMLObjectElement(Document& document, HTMLFormElement* form, bool createdByParser)
     52     : HTMLPlugInElement(objectTag, document, createdByParser, ShouldNotPreferPlugInsForImages)
     53     , m_useFallbackContent(false)
     54 {
     55     ScriptWrappable::init(this);
     56     associateByParser(form);
     57 }
     58 
     59 inline HTMLObjectElement::~HTMLObjectElement()
     60 {
     61 #if !ENABLE(OILPAN)
     62     setForm(0);
     63 #endif
     64 }
     65 
     66 PassRefPtrWillBeRawPtr<HTMLObjectElement> HTMLObjectElement::create(Document& document, HTMLFormElement* form, bool createdByParser)
     67 {
     68     RefPtrWillBeRawPtr<HTMLObjectElement> element = adoptRefWillBeNoop(new HTMLObjectElement(document, form, createdByParser));
     69     element->ensureUserAgentShadowRoot();
     70     return element.release();
     71 }
     72 
     73 void HTMLObjectElement::trace(Visitor* visitor)
     74 {
     75     FormAssociatedElement::trace(visitor);
     76     HTMLPlugInElement::trace(visitor);
     77 }
     78 
     79 RenderWidget* HTMLObjectElement::existingRenderWidget() const
     80 {
     81     return renderPart(); // This will return 0 if the renderer is not a RenderPart.
     82 }
     83 
     84 bool HTMLObjectElement::isPresentationAttribute(const QualifiedName& name) const
     85 {
     86     if (name == borderAttr)
     87         return true;
     88     return HTMLPlugInElement::isPresentationAttribute(name);
     89 }
     90 
     91 void HTMLObjectElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStylePropertySet* style)
     92 {
     93     if (name == borderAttr)
     94         applyBorderAttributeToStyle(value, style);
     95     else
     96         HTMLPlugInElement::collectStyleForPresentationAttribute(name, value, style);
     97 }
     98 
     99 void HTMLObjectElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
    100 {
    101     if (name == formAttr)
    102         formAttributeChanged();
    103     else if (name == typeAttr) {
    104         m_serviceType = value.lower();
    105         size_t pos = m_serviceType.find(";");
    106         if (pos != kNotFound)
    107             m_serviceType = m_serviceType.left(pos);
    108         // FIXME: What is the right thing to do here? Should we supress the
    109         // reload stuff when a persistable widget-type is specified?
    110         reloadPluginOnAttributeChange(name);
    111         if (!renderer())
    112             requestPluginCreationWithoutRendererIfPossible();
    113     } else if (name == dataAttr) {
    114         m_url = stripLeadingAndTrailingHTMLSpaces(value);
    115         if (renderer() && isImageType()) {
    116             setNeedsWidgetUpdate(true);
    117             if (!m_imageLoader)
    118                 m_imageLoader = HTMLImageLoader::create(this);
    119             m_imageLoader->updateFromElementIgnoringPreviousError();
    120         } else {
    121             reloadPluginOnAttributeChange(name);
    122         }
    123     } else if (name == classidAttr) {
    124         m_classId = value;
    125         reloadPluginOnAttributeChange(name);
    126     } else {
    127         HTMLPlugInElement::parseAttribute(name, value);
    128     }
    129 }
    130 
    131 static void mapDataParamToSrc(Vector<String>* paramNames, Vector<String>* paramValues)
    132 {
    133     // Some plugins don't understand the "data" attribute of the OBJECT tag (i.e. Real and WMP
    134     // require "src" attribute).
    135     int srcIndex = -1, dataIndex = -1;
    136     for (unsigned i = 0; i < paramNames->size(); ++i) {
    137         if (equalIgnoringCase((*paramNames)[i], "src"))
    138             srcIndex = i;
    139         else if (equalIgnoringCase((*paramNames)[i], "data"))
    140             dataIndex = i;
    141     }
    142 
    143     if (srcIndex == -1 && dataIndex != -1) {
    144         paramNames->append("src");
    145         paramValues->append((*paramValues)[dataIndex]);
    146     }
    147 }
    148 
    149 // FIXME: This function should not deal with url or serviceType!
    150 void HTMLObjectElement::parametersForPlugin(Vector<String>& paramNames, Vector<String>& paramValues, String& url, String& serviceType)
    151 {
    152     HashSet<StringImpl*, CaseFoldingHash> uniqueParamNames;
    153     String urlParameter;
    154 
    155     // Scan the PARAM children and store their name/value pairs.
    156     // Get the URL and type from the params if we don't already have them.
    157     for (HTMLParamElement* p = Traversal<HTMLParamElement>::firstChild(*this); p; p = Traversal<HTMLParamElement>::nextSibling(*p)) {
    158         String name = p->name();
    159         if (name.isEmpty())
    160             continue;
    161 
    162         uniqueParamNames.add(name.impl());
    163         paramNames.append(p->name());
    164         paramValues.append(p->value());
    165 
    166         // FIXME: url adjustment does not belong in this function.
    167         if (url.isEmpty() && urlParameter.isEmpty() && (equalIgnoringCase(name, "src") || equalIgnoringCase(name, "movie") || equalIgnoringCase(name, "code") || equalIgnoringCase(name, "url")))
    168             urlParameter = stripLeadingAndTrailingHTMLSpaces(p->value());
    169         // FIXME: serviceType calculation does not belong in this function.
    170         if (serviceType.isEmpty() && equalIgnoringCase(name, "type")) {
    171             serviceType = p->value();
    172             size_t pos = serviceType.find(";");
    173             if (pos != kNotFound)
    174                 serviceType = serviceType.left(pos);
    175         }
    176     }
    177 
    178     // When OBJECT is used for an applet via Sun's Java plugin, the CODEBASE attribute in the tag
    179     // points to the Java plugin itself (an ActiveX component) while the actual applet CODEBASE is
    180     // in a PARAM tag. See <http://java.sun.com/products/plugin/1.2/docs/tags.html>. This means
    181     // we have to explicitly suppress the tag's CODEBASE attribute if there is none in a PARAM,
    182     // else our Java plugin will misinterpret it. [4004531]
    183     String codebase;
    184     if (MIMETypeRegistry::isJavaAppletMIMEType(serviceType)) {
    185         codebase = "codebase";
    186         uniqueParamNames.add(codebase.impl()); // pretend we found it in a PARAM already
    187     }
    188 
    189     // Turn the attributes of the <object> element into arrays, but don't override <param> values.
    190     if (hasAttributes()) {
    191         AttributeCollection attributes = this->attributes();
    192         AttributeCollection::const_iterator end = attributes.end();
    193         for (AttributeCollection::const_iterator it = attributes.begin(); it != end; ++it) {
    194             const AtomicString& name = it->name().localName();
    195             if (!uniqueParamNames.contains(name.impl())) {
    196                 paramNames.append(name.string());
    197                 paramValues.append(it->value().string());
    198             }
    199         }
    200     }
    201 
    202     mapDataParamToSrc(&paramNames, &paramValues);
    203 
    204     // HTML5 says that an object resource's URL is specified by the object's data
    205     // attribute, not by a param element. However, for compatibility, allow the
    206     // resource's URL to be given by a param named "src", "movie", "code" or "url"
    207     // if we know that resource points to a plug-in.
    208     if (url.isEmpty() && !urlParameter.isEmpty()) {
    209         KURL completedURL = document().completeURL(urlParameter);
    210         bool useFallback;
    211         if (shouldUsePlugin(completedURL, serviceType, false, useFallback))
    212             url = urlParameter;
    213     }
    214 }
    215 
    216 
    217 bool HTMLObjectElement::hasFallbackContent() const
    218 {
    219     for (Node* child = firstChild(); child; child = child->nextSibling()) {
    220         // Ignore whitespace-only text, and <param> tags, any other content is fallback content.
    221         if (child->isTextNode()) {
    222             if (!toText(child)->containsOnlyWhitespace())
    223                 return true;
    224         } else if (!isHTMLParamElement(*child)) {
    225             return true;
    226         }
    227     }
    228     return false;
    229 }
    230 
    231 bool HTMLObjectElement::shouldAllowQuickTimeClassIdQuirk()
    232 {
    233     // This site-specific hack maintains compatibility with Mac OS X Wiki Server,
    234     // which embeds QuickTime movies using an object tag containing QuickTime's
    235     // ActiveX classid. Treat this classid as valid only if OS X Server's unique
    236     // 'generator' meta tag is present. Only apply this quirk if there is no
    237     // fallback content, which ensures the quirk will disable itself if Wiki
    238     // Server is updated to generate an alternate embed tag as fallback content.
    239     if (!document().settings()
    240         || !document().settings()->needsSiteSpecificQuirks()
    241         || hasFallbackContent()
    242         || !equalIgnoringCase(classId(), "clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B"))
    243         return false;
    244 
    245     RefPtrWillBeRawPtr<TagCollection> metaElements = document().getElementsByTagName(HTMLNames::metaTag.localName());
    246     unsigned length = metaElements->length();
    247     for (unsigned i = 0; i < length; ++i) {
    248         ASSERT(metaElements->item(i)->isHTMLElement());
    249         HTMLMetaElement* metaElement = toHTMLMetaElement(metaElements->item(i));
    250         if (equalIgnoringCase(metaElement->name(), "generator") && metaElement->content().startsWith("Mac OS X Server Web Services Server", false))
    251             return true;
    252     }
    253 
    254     return false;
    255 }
    256 
    257 bool HTMLObjectElement::hasValidClassId()
    258 {
    259     if (MIMETypeRegistry::isJavaAppletMIMEType(m_serviceType) && classId().startsWith("java:", false))
    260         return true;
    261 
    262     if (shouldAllowQuickTimeClassIdQuirk())
    263         return true;
    264 
    265     // HTML5 says that fallback content should be rendered if a non-empty
    266     // classid is specified for which the UA can't find a suitable plug-in.
    267     return classId().isEmpty();
    268 }
    269 
    270 void HTMLObjectElement::reloadPluginOnAttributeChange(const QualifiedName& name)
    271 {
    272     // Following,
    273     //   http://www.whatwg.org/specs/web-apps/current-work/#the-object-element
    274     //   (Enumerated list below "Whenever one of the following conditions occur:")
    275     //
    276     // the updating of certain attributes should bring about "redetermination"
    277     // of what the element contains.
    278     bool needsInvalidation;
    279     if (name == typeAttr) {
    280         needsInvalidation = !fastHasAttribute(classidAttr) && !fastHasAttribute(dataAttr);
    281     } else if (name == dataAttr) {
    282         needsInvalidation = !fastHasAttribute(classidAttr);
    283     } else if (name == classidAttr) {
    284         needsInvalidation = true;
    285     } else {
    286         ASSERT_NOT_REACHED();
    287         needsInvalidation = false;
    288     }
    289     setNeedsWidgetUpdate(true);
    290     if (needsInvalidation)
    291         setNeedsStyleRecalc(SubtreeStyleChange);
    292 }
    293 
    294 // FIXME: This should be unified with HTMLEmbedElement::updateWidget and
    295 // moved down into HTMLPluginElement.cpp
    296 void HTMLObjectElement::updateWidgetInternal()
    297 {
    298     ASSERT(!renderEmbeddedObject()->showsUnavailablePluginIndicator());
    299     ASSERT(needsWidgetUpdate());
    300     setNeedsWidgetUpdate(false);
    301     // FIXME: This should ASSERT isFinishedParsingChildren() instead.
    302     if (!isFinishedParsingChildren()) {
    303         dispatchErrorEvent();
    304         return;
    305     }
    306 
    307     // FIXME: I'm not sure it's ever possible to get into updateWidget during a
    308     // removal, but just in case we should avoid loading the frame to prevent
    309     // security bugs.
    310     if (!SubframeLoadingDisabler::canLoadFrame(*this)) {
    311         dispatchErrorEvent();
    312         return;
    313     }
    314 
    315     String url = this->url();
    316     String serviceType = m_serviceType;
    317 
    318     // FIXME: These should be joined into a PluginParameters class.
    319     Vector<String> paramNames;
    320     Vector<String> paramValues;
    321     parametersForPlugin(paramNames, paramValues, url, serviceType);
    322 
    323     // Note: url is modified above by parametersForPlugin.
    324     if (!allowedToLoadFrameURL(url)) {
    325         dispatchErrorEvent();
    326         return;
    327     }
    328 
    329     // FIXME: Is it possible to get here without a renderer now that we don't have beforeload events?
    330     if (!renderer())
    331         return;
    332 
    333     if (!hasValidClassId() || !requestObject(url, serviceType, paramNames, paramValues)) {
    334         if (!url.isEmpty())
    335             dispatchErrorEvent();
    336         if (hasFallbackContent())
    337             renderFallbackContent();
    338     }
    339 }
    340 
    341 bool HTMLObjectElement::rendererIsNeeded(const RenderStyle& style)
    342 {
    343     // FIXME: This check should not be needed, detached documents never render!
    344     if (!document().frame())
    345         return false;
    346     return HTMLPlugInElement::rendererIsNeeded(style);
    347 }
    348 
    349 Node::InsertionNotificationRequest HTMLObjectElement::insertedInto(ContainerNode* insertionPoint)
    350 {
    351     HTMLPlugInElement::insertedInto(insertionPoint);
    352     FormAssociatedElement::insertedInto(insertionPoint);
    353     return InsertionDone;
    354 }
    355 
    356 void HTMLObjectElement::removedFrom(ContainerNode* insertionPoint)
    357 {
    358     HTMLPlugInElement::removedFrom(insertionPoint);
    359     FormAssociatedElement::removedFrom(insertionPoint);
    360 }
    361 
    362 void HTMLObjectElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta)
    363 {
    364     if (inDocument() && !useFallbackContent()) {
    365         setNeedsWidgetUpdate(true);
    366         setNeedsStyleRecalc(SubtreeStyleChange);
    367     }
    368     HTMLPlugInElement::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta);
    369 }
    370 
    371 bool HTMLObjectElement::isURLAttribute(const Attribute& attribute) const
    372 {
    373     return attribute.name() == codebaseAttr || attribute.name() == dataAttr
    374         || (attribute.name() == usemapAttr && attribute.value().string()[0] != '#')
    375         || HTMLPlugInElement::isURLAttribute(attribute);
    376 }
    377 
    378 bool HTMLObjectElement::hasLegalLinkAttribute(const QualifiedName& name) const
    379 {
    380     return name == classidAttr || name == dataAttr || name == codebaseAttr || HTMLPlugInElement::hasLegalLinkAttribute(name);
    381 }
    382 
    383 const QualifiedName& HTMLObjectElement::subResourceAttributeName() const
    384 {
    385     return dataAttr;
    386 }
    387 
    388 const AtomicString HTMLObjectElement::imageSourceURL() const
    389 {
    390     return getAttribute(dataAttr);
    391 }
    392 
    393 // FIXME: Remove this hack.
    394 void HTMLObjectElement::reattachFallbackContent()
    395 {
    396     // This can happen inside of attach() in the middle of a recalcStyle so we need to
    397     // reattach synchronously here.
    398     if (document().inStyleRecalc())
    399         reattach();
    400     else
    401         lazyReattachIfAttached();
    402 }
    403 
    404 void HTMLObjectElement::renderFallbackContent()
    405 {
    406     if (useFallbackContent())
    407         return;
    408 
    409     if (!inDocument())
    410         return;
    411 
    412     // Before we give up and use fallback content, check to see if this is a MIME type issue.
    413     if (m_imageLoader && m_imageLoader->image() && m_imageLoader->image()->status() != Resource::LoadError) {
    414         m_serviceType = m_imageLoader->image()->response().mimeType();
    415         if (!isImageType()) {
    416             // If we don't think we have an image type anymore, then clear the image from the loader.
    417             m_imageLoader->setImage(0);
    418             reattachFallbackContent();
    419             return;
    420         }
    421     }
    422 
    423     m_useFallbackContent = true;
    424 
    425     // FIXME: Style gets recalculated which is suboptimal.
    426     reattachFallbackContent();
    427 }
    428 
    429 bool HTMLObjectElement::isExposed() const
    430 {
    431     // http://www.whatwg.org/specs/web-apps/current-work/#exposed
    432     for (HTMLObjectElement* ancestor = Traversal<HTMLObjectElement>::firstAncestor(*this); ancestor; ancestor = Traversal<HTMLObjectElement>::firstAncestor(*ancestor)) {
    433         if (ancestor->isExposed())
    434             return false;
    435     }
    436     for (HTMLElement* element = Traversal<HTMLElement>::firstWithin(*this); element; element = Traversal<HTMLElement>::next(*element, this)) {
    437         if (isHTMLObjectElement(*element) || isHTMLEmbedElement(*element))
    438             return false;
    439     }
    440     return true;
    441 }
    442 
    443 bool HTMLObjectElement::containsJavaApplet() const
    444 {
    445     if (MIMETypeRegistry::isJavaAppletMIMEType(getAttribute(typeAttr)))
    446         return true;
    447 
    448     for (HTMLElement* child = Traversal<HTMLElement>::firstChild(*this); child; child = Traversal<HTMLElement>::nextSibling(*child)) {
    449         if (isHTMLParamElement(*child)
    450                 && equalIgnoringCase(child->getNameAttribute(), "type")
    451                 && MIMETypeRegistry::isJavaAppletMIMEType(child->getAttribute(valueAttr).string()))
    452             return true;
    453         if (isHTMLObjectElement(*child) && toHTMLObjectElement(*child).containsJavaApplet())
    454             return true;
    455         if (isHTMLAppletElement(*child))
    456             return true;
    457     }
    458 
    459     return false;
    460 }
    461 
    462 void HTMLObjectElement::didMoveToNewDocument(Document& oldDocument)
    463 {
    464     FormAssociatedElement::didMoveToNewDocument(oldDocument);
    465     HTMLPlugInElement::didMoveToNewDocument(oldDocument);
    466 }
    467 
    468 bool HTMLObjectElement::appendFormData(FormDataList& encoding, bool)
    469 {
    470     if (name().isEmpty())
    471         return false;
    472 
    473     Widget* widget = pluginWidget();
    474     if (!widget || !widget->isPluginView())
    475         return false;
    476     String value;
    477     if (!toPluginView(widget)->getFormValue(value))
    478         return false;
    479     encoding.appendData(name(), value);
    480     return true;
    481 }
    482 
    483 HTMLFormElement* HTMLObjectElement::formOwner() const
    484 {
    485     return FormAssociatedElement::form();
    486 }
    487 
    488 bool HTMLObjectElement::isInteractiveContent() const
    489 {
    490     return fastHasAttribute(usemapAttr);
    491 }
    492 
    493 bool HTMLObjectElement::useFallbackContent() const
    494 {
    495     return HTMLPlugInElement::useFallbackContent() || m_useFallbackContent;
    496 }
    497 
    498 }
    499