c80d37402ed5ac7c62ccb2cd657f2301154d2041
[WebKit-https.git] / WebCore / html / HTMLObjectElement.cpp
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 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 "EventNames.h"
29 #include "ExceptionCode.h"
30 #include "Frame.h"
31 #include "HTMLDocument.h"
32 #include "HTMLFormElement.h"
33 #include "HTMLImageLoader.h"
34 #include "HTMLNames.h"
35 #include "HTMLParamElement.h"
36 #include "HTMLParserIdioms.h"
37 #include "MIMETypeRegistry.h"
38 #include "RenderEmbeddedObject.h"
39 #include "RenderImage.h"
40 #include "RenderWidget.h"
41 #include "ScriptController.h"
42 #include "ScriptEventListener.h"
43 #include "Text.h"
44
45 namespace WebCore {
46
47 using namespace HTMLNames;
48
49 inline HTMLObjectElement::HTMLObjectElement(const QualifiedName& tagName, Document* document, HTMLFormElement* form, bool createdByParser) 
50     : HTMLPlugInImageElement(tagName, document, createdByParser)
51     , FormAssociatedElement(form)
52     , m_docNamedItem(true)
53     , m_useFallbackContent(false)
54 {
55     ASSERT(hasTagName(objectTag));
56     if (!this->form())
57         setForm(findFormAncestor());
58     if (this->form())
59         this->form()->registerFormElement(this);
60 }
61
62 inline HTMLObjectElement::~HTMLObjectElement()
63 {
64     if (form())
65         form()->removeFormElement(this);
66 }
67
68 PassRefPtr<HTMLObjectElement> HTMLObjectElement::create(const QualifiedName& tagName, Document* document, HTMLFormElement* form, bool createdByParser)
69 {
70     return adoptRef(new HTMLObjectElement(tagName, document, form, createdByParser));
71 }
72
73 RenderWidget* HTMLObjectElement::renderWidgetForJSBindings() const
74 {
75     document()->updateLayoutIgnorePendingStylesheets();
76     return renderPart(); // This will return 0 if the renderer is not a RenderPart.
77 }
78
79 void HTMLObjectElement::parseMappedAttribute(Attribute* attr)
80 {
81     if (attr->name() == typeAttr) {
82         m_serviceType = attr->value().lower();
83         size_t pos = m_serviceType.find(";");
84         if (pos != notFound)
85             m_serviceType = m_serviceType.left(pos);
86         if (renderer())
87             setNeedsWidgetUpdate(true);
88         if (!isImageType() && m_imageLoader)
89             m_imageLoader.clear();
90     } else if (attr->name() == dataAttr) {
91         m_url = stripLeadingAndTrailingHTMLSpaces(attr->value());
92         if (renderer()) {
93             setNeedsWidgetUpdate(true);
94             if (isImageType()) {
95                 if (!m_imageLoader)
96                     m_imageLoader = adoptPtr(new HTMLImageLoader(this));
97                 m_imageLoader->updateFromElementIgnoringPreviousError();
98             }
99         }
100     } else if (attr->name() == classidAttr) {
101         m_classId = attr->value();
102         if (renderer())
103             setNeedsWidgetUpdate(true);
104     } else if (attr->name() == onloadAttr)
105         setAttributeEventListener(eventNames().loadEvent, createAttributeEventListener(this, attr));
106     else if (attr->name() == onbeforeloadAttr)
107         setAttributeEventListener(eventNames().beforeloadEvent, createAttributeEventListener(this, attr));
108     else if (attr->name() == nameAttr) {
109         const AtomicString& newName = attr->value();
110         if (isDocNamedItem() && inDocument() && document()->isHTMLDocument()) {
111             HTMLDocument* document = static_cast<HTMLDocument*>(this->document());
112             document->removeNamedItem(m_name);
113             document->addNamedItem(newName);
114         }
115         m_name = newName;
116     } else if (isIdAttributeName(attr->name())) {
117         const AtomicString& newId = attr->value();
118         if (isDocNamedItem() && inDocument() && document()->isHTMLDocument()) {
119             HTMLDocument* document = static_cast<HTMLDocument*>(this->document());
120             document->removeExtraNamedItem(m_id);
121             document->addExtraNamedItem(newId);
122         }
123         m_id = newId;
124         // also call superclass
125         HTMLPlugInImageElement::parseMappedAttribute(attr);
126     } else
127         HTMLPlugInImageElement::parseMappedAttribute(attr);
128 }
129
130 static 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 int 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!
149 void 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 (Node* child = firstChild(); child; child = child->nextSibling()) {
157         if (!child->hasTagName(paramTag))
158             continue;
159
160         HTMLParamElement* p = static_cast<HTMLParamElement*>(child);
161         String name = p->name();
162         if (name.isEmpty())
163             continue;
164
165         uniqueParamNames.add(name.impl());
166         paramNames.append(p->name());
167         paramValues.append(p->value());
168
169         // FIXME: url adjustment does not belong in this function.
170         if (url.isEmpty() && urlParameter.isEmpty() && (equalIgnoringCase(name, "src") || equalIgnoringCase(name, "movie") || equalIgnoringCase(name, "code") || equalIgnoringCase(name, "url")))
171             urlParameter = stripLeadingAndTrailingHTMLSpaces(p->value());
172         // FIXME: serviceType calculation does not belong in this function.
173         if (serviceType.isEmpty() && equalIgnoringCase(name, "type")) {
174             serviceType = p->value();
175             size_t pos = serviceType.find(";");
176             if (pos != notFound)
177                 serviceType = serviceType.left(pos);
178         }
179     }
180     
181     // When OBJECT is used for an applet via Sun's Java plugin, the CODEBASE attribute in the tag
182     // points to the Java plugin itself (an ActiveX component) while the actual applet CODEBASE is
183     // in a PARAM tag. See <http://java.sun.com/products/plugin/1.2/docs/tags.html>. This means
184     // we have to explicitly suppress the tag's CODEBASE attribute if there is none in a PARAM,
185     // else our Java plugin will misinterpret it. [4004531]
186     String codebase;
187     if (MIMETypeRegistry::isJavaAppletMIMEType(serviceType)) {
188         codebase = "codebase";
189         uniqueParamNames.add(codebase.impl()); // pretend we found it in a PARAM already
190     }
191     
192     // Turn the attributes of the <object> element into arrays, but don't override <param> values.
193     NamedNodeMap* attributes = this->attributes(true);
194     if (attributes) {
195         for (unsigned i = 0; i < attributes->length(); ++i) {
196             Attribute* it = attributes->attributeItem(i);
197             const AtomicString& name = it->name().localName();
198             if (!uniqueParamNames.contains(name.impl())) {
199                 paramNames.append(name.string());
200                 paramValues.append(it->value().string());
201             }
202         }
203     }
204     
205     mapDataParamToSrc(&paramNames, &paramValues);
206     
207     // HTML5 says that an object resource's URL is specified by the object's data
208     // attribute, not by a param element. However, for compatibility, allow the
209     // resource's URL to be given by a param named "src", "movie", "code" or "url"
210     // if we know that resource points to a plug-in.
211     if (url.isEmpty() && !urlParameter.isEmpty()) {
212         SubframeLoader* loader = document()->frame()->loader()->subframeLoader();
213         if (loader->resourceWillUsePlugin(urlParameter, serviceType))
214             url = urlParameter;
215     }
216 }
217
218     
219 bool HTMLObjectElement::hasFallbackContent() const
220 {
221     for (Node* child = firstChild(); child; child = child->nextSibling()) {
222         // Ignore whitespace-only text, and <param> tags, any other content is fallback content.
223         if (child->isTextNode()) {
224             if (!static_cast<Text*>(child)->containsOnlyWhitespace())
225                 return true;
226         } else if (!child->hasTagName(paramTag))
227             return true;
228     }
229     return false;
230 }
231     
232 bool HTMLObjectElement::hasValidClassId()
233 {
234     // HTML5 says that fallback content should be rendered if a non-empty
235     // classid is specified for which the UA can't find a suitable plug-in.
236     // WebKit supports no classids, with the exception of Qt plug-ins, which use
237     // classid to specify which QObject to load.
238 #if PLATFORM(QT)
239     return classId().isEmpty() || equalIgnoringCase(serviceType(), "application/x-qt-plugin");
240 #else
241     return classId().isEmpty();
242 #endif
243 }
244
245 // FIXME: This should be unified with HTMLEmbedElement::updateWidget and
246 // moved down into HTMLPluginImageElement.cpp
247 void HTMLObjectElement::updateWidget(bool onlyCreateNonNetscapePlugins)
248 {
249     ASSERT(!renderEmbeddedObject()->pluginCrashedOrWasMissing());
250     // FIXME: We should ASSERT(needsWidgetUpdate()), but currently
251     // FrameView::updateWidget() calls updateWidget(false) without checking if
252     // the widget actually needs updating!
253     setNeedsWidgetUpdate(false);
254     // FIXME: This should ASSERT isFinishedParsingChildren() instead.
255     if (!isFinishedParsingChildren())
256         return;
257     
258     String url = this->url();
259     String serviceType = this->serviceType();
260
261     // FIXME: These should be joined into a PluginParameters class.
262     Vector<String> paramNames;
263     Vector<String> paramValues;
264     parametersForPlugin(paramNames, paramValues, url, serviceType);
265
266     // Note: url is modified above by parametersForPlugin.
267     if (!allowedToLoadFrameURL(url))
268         return;
269
270     bool fallbackContent = hasFallbackContent();
271     renderEmbeddedObject()->setHasFallbackContent(fallbackContent);
272
273     if (onlyCreateNonNetscapePlugins && wouldLoadAsNetscapePlugin(url, serviceType))
274         return;
275
276     ASSERT(!m_inBeforeLoadEventHandler);
277     m_inBeforeLoadEventHandler = true;
278     bool beforeLoadAllowedLoad = dispatchBeforeLoadEvent(url);
279     m_inBeforeLoadEventHandler = false;
280
281     // beforeload events can modify the DOM, potentially causing
282     // RenderWidget::destroy() to be called.  Ensure we haven't been
283     // destroyed before continuing.
284     // FIXME: Should this render fallback content?
285     if (!renderer())
286         return;
287
288     SubframeLoader* loader = document()->frame()->loader()->subframeLoader();
289     bool success = beforeLoadAllowedLoad && hasValidClassId() && loader->requestObject(this, url, getAttribute(nameAttr), serviceType, paramNames, paramValues);
290
291     if (!success && fallbackContent)
292         renderFallbackContent();
293 }
294
295 bool HTMLObjectElement::rendererIsNeeded(RenderStyle* style)
296 {
297     // FIXME: This check should not be needed, detached documents never render!
298     Frame* frame = document()->frame();
299     if (!frame)
300         return false;
301
302     return HTMLPlugInImageElement::rendererIsNeeded(style);
303 }
304
305 void HTMLObjectElement::insertedIntoDocument()
306 {
307     if (isDocNamedItem() && document()->isHTMLDocument()) {
308         HTMLDocument* document = static_cast<HTMLDocument*>(this->document());
309         document->addNamedItem(m_name);
310         document->addExtraNamedItem(m_id);
311     }
312
313     HTMLPlugInImageElement::insertedIntoDocument();
314 }
315
316 void HTMLObjectElement::removedFromDocument()
317 {
318     if (isDocNamedItem() && document()->isHTMLDocument()) {
319         HTMLDocument* document = static_cast<HTMLDocument*>(this->document());
320         document->removeNamedItem(m_name);
321         document->removeExtraNamedItem(m_id);
322     }
323
324     HTMLPlugInImageElement::removedFromDocument();
325 }
326
327 void HTMLObjectElement::attributeChanged(Attribute* attr, bool preserveDecls)
328 {
329     if (attr->name() == formAttr)
330         formAttributeChanged();
331     else
332         HTMLPlugInImageElement::attributeChanged(attr, preserveDecls);
333 }
334
335 void HTMLObjectElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta)
336 {
337     updateDocNamedItem();
338     if (inDocument() && !useFallbackContent()) {
339         setNeedsWidgetUpdate(true);
340         setNeedsStyleRecalc();
341     }
342     HTMLPlugInImageElement::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta);
343 }
344
345 bool HTMLObjectElement::isURLAttribute(Attribute *attr) const
346 {
347     return (attr->name() == dataAttr || (attr->name() == usemapAttr && attr->value().string()[0] != '#'));
348 }
349
350 const QualifiedName& HTMLObjectElement::imageSourceAttributeName() const
351 {
352     return dataAttr;
353 }
354
355 void HTMLObjectElement::renderFallbackContent()
356 {
357     if (useFallbackContent())
358         return;
359     
360     if (!inDocument())
361         return;
362
363     // Before we give up and use fallback content, check to see if this is a MIME type issue.
364     if (m_imageLoader && m_imageLoader->image() && m_imageLoader->image()->status() != CachedResource::LoadError) {
365         m_serviceType = m_imageLoader->image()->response().mimeType();
366         if (!isImageType()) {
367             // If we don't think we have an image type anymore, then ditch the image loader.
368             m_imageLoader.clear();        
369             detach();
370             attach();
371             return;
372         }
373     }
374
375     m_useFallbackContent = true;
376
377     // FIXME: Style gets recalculated which is suboptimal.
378     detach();
379     attach();
380 }
381
382 // FIXME: This should be removed, all callers are almost certainly wrong.
383 static bool isRecognizedTagName(const QualifiedName& tagName)
384 {
385     DEFINE_STATIC_LOCAL(HashSet<AtomicStringImpl*>, tagList, ());
386     if (tagList.isEmpty()) {
387         size_t tagCount = 0;
388         QualifiedName** tags = HTMLNames::getHTMLTags(&tagCount);
389         for (size_t i = 0; i < tagCount; i++) {
390             if (*tags[i] == bgsoundTag
391                 || *tags[i] == commandTag
392                 || *tags[i] == detailsTag
393                 || *tags[i] == figcaptionTag
394                 || *tags[i] == figureTag
395                 || *tags[i] == summaryTag
396                 || *tags[i] == trackTag) {
397                 // Even though we have atoms for these tags, we don't want to
398                 // treat them as "recognized tags" for the purpose of parsing
399                 // because that changes how we parse documents.
400                 continue;
401             }
402             tagList.add(tags[i]->localName().impl());
403         }
404     }
405     return tagList.contains(tagName.localName().impl());
406 }
407
408 void HTMLObjectElement::updateDocNamedItem()
409 {
410     // The rule is "<object> elements with no children other than
411     // <param> elements, unknown elements and whitespace can be
412     // found by name in a document, and other <object> elements cannot."
413     bool wasNamedItem = m_docNamedItem;
414     bool isNamedItem = true;
415     Node* child = firstChild();
416     while (child && isNamedItem) {
417         if (child->isElementNode()) {
418             Element* element = static_cast<Element*>(child);
419             // FIXME: Use of isRecognizedTagName is almost certainly wrong here.
420             if (isRecognizedTagName(element->tagQName()) && !element->hasTagName(paramTag))
421                 isNamedItem = false;
422         } else if (child->isTextNode()) {
423             if (!static_cast<Text*>(child)->containsOnlyWhitespace())
424                 isNamedItem = false;
425         } else
426             isNamedItem = false;
427         child = child->nextSibling();
428     }
429     if (isNamedItem != wasNamedItem && document()->isHTMLDocument()) {
430         HTMLDocument* document = static_cast<HTMLDocument*>(this->document());
431         if (isNamedItem) {
432             document->addNamedItem(m_name);
433             document->addExtraNamedItem(m_id);
434         } else {
435             document->removeNamedItem(m_name);
436             document->removeExtraNamedItem(m_id);
437         }
438     }
439     m_docNamedItem = isNamedItem;
440 }
441
442 bool HTMLObjectElement::containsJavaApplet() const
443 {
444     if (MIMETypeRegistry::isJavaAppletMIMEType(getAttribute(typeAttr)))
445         return true;
446         
447     for (Element* child = firstElementChild(); child; child = child->nextElementSibling()) {
448         if (child->hasTagName(paramTag)
449                 && equalIgnoringCase(child->getAttribute(nameAttr), "type")
450                 && MIMETypeRegistry::isJavaAppletMIMEType(child->getAttribute(valueAttr).string()))
451             return true;
452         if (child->hasTagName(objectTag)
453                 && static_cast<HTMLObjectElement*>(child)->containsJavaApplet())
454             return true;
455         if (child->hasTagName(appletTag))
456             return true;
457     }
458     
459     return false;
460 }
461
462 void HTMLObjectElement::addSubresourceAttributeURLs(ListHashSet<KURL>& urls) const
463 {
464     HTMLPlugInImageElement::addSubresourceAttributeURLs(urls);
465
466     addSubresourceURL(urls, document()->completeURL(getAttribute(dataAttr)));
467
468     // FIXME: Passing a string that starts with "#" to the completeURL function does
469     // not seem like it would work. The image element has similar but not identical code.
470     const AtomicString& useMap = getAttribute(usemapAttr);
471     if (useMap.startsWith("#"))
472         addSubresourceURL(urls, document()->completeURL(useMap));
473 }
474
475 void HTMLObjectElement::insertedIntoTree(bool deep)
476 {
477     FormAssociatedElement::insertedIntoTree();
478     HTMLPlugInImageElement::insertedIntoTree(deep);
479 }
480
481 void HTMLObjectElement::removedFromTree(bool deep)
482 {
483     FormAssociatedElement::removedFromTree();
484     HTMLPlugInImageElement::removedFromTree(deep);
485 }
486
487 bool HTMLObjectElement::appendFormData(FormDataList&, bool)
488 {
489     // FIXME: Implements this function.
490     return false;
491 }
492
493 const AtomicString& HTMLObjectElement::formControlName() const
494 {
495     return m_name.isNull() ? emptyAtom : m_name;
496 }
497
498 }