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