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 "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
46namespace WebCore {
47
48using namespace HTMLNames;
49
50inline 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
63inline HTMLObjectElement::~HTMLObjectElement()
64{
65    if (form())
66        form()->removeFormElement(this);
67}
68
69PassRefPtr<HTMLObjectElement> HTMLObjectElement::create(const QualifiedName& tagName, Document* document, HTMLFormElement* form, bool createdByParser)
70{
71    return adoptRef(new HTMLObjectElement(tagName, document, form, createdByParser));
72}
73
74RenderWidget* HTMLObjectElement::renderWidgetForJSBindings() const
75{
76    document()->updateLayoutIgnorePendingStylesheets();
77    return renderPart(); // This will return 0 if the renderer is not a RenderPart.
78}
79
80void 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
137static 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!
156void 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
226bool 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
239bool 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
256void 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
304bool 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
314void 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
329void 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
341void HTMLObjectElement::attributeChanged(Attribute* attr, bool preserveDecls)
342{
343    if (attr->name() == formAttr)
344        formAttributeChanged();
345    else
346        HTMLPlugInImageElement::attributeChanged(attr, preserveDecls);
347}
348
349void 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
359bool HTMLObjectElement::isURLAttribute(Attribute *attr) const
360{
361    return (attr->name() == dataAttr || (attr->name() == usemapAttr && attr->value().string()[0] != '#'));
362}
363
364const QualifiedName& HTMLObjectElement::imageSourceAttributeName() const
365{
366    return dataAttr;
367}
368
369void 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.
397static 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
422void 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
456bool 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
476void 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
489void HTMLObjectElement::willMoveToNewOwnerDocument()
490{
491    FormAssociatedElement::willMoveToNewOwnerDocument();
492    HTMLPlugInImageElement::willMoveToNewOwnerDocument();
493}
494
495void HTMLObjectElement::insertedIntoTree(bool deep)
496{
497    FormAssociatedElement::insertedIntoTree();
498    HTMLPlugInImageElement::insertedIntoTree(deep);
499}
500
501void HTMLObjectElement::removedFromTree(bool deep)
502{
503    FormAssociatedElement::removedFromTree();
504    HTMLPlugInImageElement::removedFromTree(deep);
505}
506
507bool HTMLObjectElement::appendFormData(FormDataList&, bool)
508{
509    // FIXME: Implements this function.
510    return false;
511}
512
513const AtomicString& HTMLObjectElement::formControlName() const
514{
515    return m_name.isNull() ? emptyAtom : m_name;
516}
517
518HTMLFormElement* HTMLObjectElement::virtualForm() const
519{
520    return FormAssociatedElement::form();
521}
522
523}
524