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