2010-09-10 Dirk Pranke <dpranke@chromium.org>
[WebKit.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 "CSSHelper.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 "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, bool createdByParser) 
50     : HTMLPlugInImageElement(tagName, document, createdByParser)
51     , m_docNamedItem(true)
52     , m_useFallbackContent(false)
53 {
54     ASSERT(hasTagName(objectTag));
55 }
56
57 PassRefPtr<HTMLObjectElement> HTMLObjectElement::create(const QualifiedName& tagName, Document* document, bool createdByParser)
58 {
59     return adoptRef(new HTMLObjectElement(tagName, document, createdByParser));
60 }
61
62 RenderWidget* HTMLObjectElement::renderWidgetForJSBindings() const
63 {
64     document()->updateLayoutIgnorePendingStylesheets();
65     return renderPart(); // This will return 0 if the renderer is not a RenderPart.
66 }
67
68 void HTMLObjectElement::parseMappedAttribute(Attribute* attr)
69 {
70     if (attr->name() == typeAttr) {
71         m_serviceType = attr->value().lower();
72         size_t pos = m_serviceType.find(";");
73         if (pos != notFound)
74             m_serviceType = m_serviceType.left(pos);
75         if (renderer())
76             setNeedsWidgetUpdate(true);
77         if (!isImageType() && m_imageLoader)
78             m_imageLoader.clear();
79     } else if (attr->name() == dataAttr) {
80         m_url = deprecatedParseURL(attr->value());
81         if (renderer()) {
82             setNeedsWidgetUpdate(true);
83             if (isImageType()) {
84                 if (!m_imageLoader)
85                     m_imageLoader = adoptPtr(new HTMLImageLoader(this));
86                 m_imageLoader->updateFromElementIgnoringPreviousError();
87             }
88         }
89     } else if (attr->name() == classidAttr) {
90         m_classId = attr->value();
91         if (renderer())
92             setNeedsWidgetUpdate(true);
93     } else if (attr->name() == onloadAttr)
94         setAttributeEventListener(eventNames().loadEvent, createAttributeEventListener(this, attr));
95     else if (attr->name() == onbeforeloadAttr)
96         setAttributeEventListener(eventNames().beforeloadEvent, createAttributeEventListener(this, attr));
97     else if (attr->name() == nameAttr) {
98         const AtomicString& newName = attr->value();
99         if (isDocNamedItem() && inDocument() && document()->isHTMLDocument()) {
100             HTMLDocument* document = static_cast<HTMLDocument*>(this->document());
101             document->removeNamedItem(m_name);
102             document->addNamedItem(newName);
103         }
104         m_name = newName;
105     } else if (isIdAttributeName(attr->name())) {
106         const AtomicString& newId = attr->value();
107         if (isDocNamedItem() && inDocument() && document()->isHTMLDocument()) {
108             HTMLDocument* document = static_cast<HTMLDocument*>(this->document());
109             document->removeExtraNamedItem(m_id);
110             document->addExtraNamedItem(newId);
111         }
112         m_id = newId;
113         // also call superclass
114         HTMLPlugInImageElement::parseMappedAttribute(attr);
115     } else
116         HTMLPlugInImageElement::parseMappedAttribute(attr);
117 }
118
119 typedef HashMap<String, String, CaseFoldingHash> ClassIdToTypeMap;
120
121 static ClassIdToTypeMap* createClassIdToTypeMap()
122 {
123     ClassIdToTypeMap* map = new ClassIdToTypeMap;
124     map->add("clsid:D27CDB6E-AE6D-11CF-96B8-444553540000", "application/x-shockwave-flash");
125     map->add("clsid:CFCDAA03-8BE4-11CF-B84B-0020AFBBCCFA", "audio/x-pn-realaudio-plugin");
126     map->add("clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B", "video/quicktime");
127     map->add("clsid:166B1BCA-3F9C-11CF-8075-444553540000", "application/x-director");
128     map->add("clsid:6BF52A52-394A-11D3-B153-00C04F79FAA6", "application/x-mplayer2");
129     map->add("clsid:22D6F312-B0F6-11D0-94AB-0080C74C7E95", "application/x-mplayer2");
130     return map;
131 }
132
133 static String serviceTypeForClassId(const String& classId)
134 {
135     // Return early if classId is empty (since we won't do anything below).
136     // Furthermore, if classId is null, calling get() below will crash.
137     if (classId.isEmpty())
138         return String();
139     
140     static ClassIdToTypeMap* map = createClassIdToTypeMap();
141     return map->get(classId);
142 }
143
144 static void mapDataParamToSrc(Vector<String>* paramNames, Vector<String>* paramValues)
145 {
146     // Some plugins don't understand the "data" attribute of the OBJECT tag (i.e. Real and WMP
147     // require "src" attribute).
148     int srcIndex = -1, dataIndex = -1;
149     for (unsigned int i = 0; i < paramNames->size(); ++i) {
150         if (equalIgnoringCase((*paramNames)[i], "src"))
151             srcIndex = i;
152         else if (equalIgnoringCase((*paramNames)[i], "data"))
153             dataIndex = i;
154     }
155     
156     if (srcIndex == -1 && dataIndex != -1) {
157         paramNames->append("src");
158         paramValues->append((*paramValues)[dataIndex]);
159     }
160 }
161
162 // FIXME: This function should not deal with url or serviceType!
163 void HTMLObjectElement::parametersForPlugin(Vector<String>& paramNames, Vector<String>& paramValues, String& url, String& serviceType)
164 {
165     HashSet<StringImpl*, CaseFoldingHash> uniqueParamNames;
166     String urlParameter;
167     
168     // Scan the PARAM children and store their name/value pairs.
169     // Get the URL and type from the params if we don't already have them.
170     for (Node* child = firstChild(); child; child = child->nextSibling()) {
171         if (!child->hasTagName(paramTag))
172             continue;
173
174         HTMLParamElement* p = static_cast<HTMLParamElement*>(child);
175         String name = p->name();
176         if (name.isEmpty())
177             continue;
178
179         uniqueParamNames.add(name.impl());
180         paramNames.append(p->name());
181         paramValues.append(p->value());
182
183         // FIXME: url adjustment does not belong in this function.
184         if (url.isEmpty() && urlParameter.isEmpty() && (equalIgnoringCase(name, "src") || equalIgnoringCase(name, "movie") || equalIgnoringCase(name, "code") || equalIgnoringCase(name, "url")))
185             urlParameter = deprecatedParseURL(p->value());
186         // FIXME: serviceType calculation does not belong in this function.
187         if (serviceType.isEmpty() && equalIgnoringCase(name, "type")) {
188             serviceType = p->value();
189             size_t pos = serviceType.find(";");
190             if (pos != notFound)
191                 serviceType = serviceType.left(pos);
192         }
193     }
194     
195     // When OBJECT is used for an applet via Sun's Java plugin, the CODEBASE attribute in the tag
196     // points to the Java plugin itself (an ActiveX component) while the actual applet CODEBASE is
197     // in a PARAM tag. See <http://java.sun.com/products/plugin/1.2/docs/tags.html>. This means
198     // we have to explicitly suppress the tag's CODEBASE attribute if there is none in a PARAM,
199     // else our Java plugin will misinterpret it. [4004531]
200     String codebase;
201     if (MIMETypeRegistry::isJavaAppletMIMEType(serviceType)) {
202         codebase = "codebase";
203         uniqueParamNames.add(codebase.impl()); // pretend we found it in a PARAM already
204     }
205     
206     // Turn the attributes of the <object> element into arrays, but don't override <param> values.
207     NamedNodeMap* attributes = this->attributes(true);
208     if (attributes) {
209         for (unsigned i = 0; i < attributes->length(); ++i) {
210             Attribute* it = attributes->attributeItem(i);
211             const AtomicString& name = it->name().localName();
212             if (!uniqueParamNames.contains(name.impl())) {
213                 paramNames.append(name.string());
214                 paramValues.append(it->value().string());
215             }
216         }
217     }
218     
219     mapDataParamToSrc(&paramNames, &paramValues);
220     
221     // HTML5 says that an object resource's URL is specified by the object's data
222     // attribute, not by a param element. However, for compatibility, allow the
223     // resource's URL to be given by a param named "src", "movie", "code" or "url"
224     // if we know that resource points to a plug-in.
225     if (url.isEmpty() && !urlParameter.isEmpty()) {
226         SubframeLoader* loader = document()->frame()->loader()->subframeLoader();
227         if (loader->resourceWillUsePlugin(urlParameter, serviceType))
228             url = urlParameter;
229     }
230 }
231
232     
233 bool HTMLObjectElement::hasFallbackContent() const
234 {
235     for (Node* child = firstChild(); child; child = child->nextSibling()) {
236         // Ignore whitespace-only text, and <param> tags, any other content is fallback content.
237         if (child->isTextNode()) {
238             if (!static_cast<Text*>(child)->containsOnlyWhitespace())
239                 return true;
240         } else if (!child->hasTagName(paramTag))
241             return true;
242     }
243     return false;
244 }
245
246 // FIXME: This should be unified with HTMLEmbedElement::updateWidget and
247 // moved down into HTMLPluginImageElement.cpp
248 void HTMLObjectElement::updateWidget(bool onlyCreateNonNetscapePlugins)
249 {
250     ASSERT(!renderEmbeddedObject()->pluginCrashedOrWasMissing());
251     // FIXME: We should ASSERT(needsWidgetUpdate()), but currently
252     // FrameView::updateWidget() calls updateWidget(false) without checking if
253     // the widget actually needs updating!
254     setNeedsWidgetUpdate(false);
255     // FIXME: This should ASSERT isFinishedParsingChildren() instead.
256     if (!isFinishedParsingChildren())
257         return;
258
259     String url = this->url();
260     
261     // If the object does not specify a MIME type via a type attribute, but does
262     // contain a classid attribute, try to map the classid to a MIME type.
263     String serviceType = this->serviceType();
264     if (serviceType.isEmpty())
265         serviceType = serviceTypeForClassId(classId());
266
267     // FIXME: These should be joined into a PluginParameters class.
268     Vector<String> paramNames;
269     Vector<String> paramValues;
270     parametersForPlugin(paramNames, paramValues, url, serviceType);
271
272     // Note: url is modified above by parametersForPlugin.
273     if (!allowedToLoadFrameURL(url))
274         return;
275
276     bool fallbackContent = hasFallbackContent();
277     renderEmbeddedObject()->setHasFallbackContent(fallbackContent);
278
279     if (onlyCreateNonNetscapePlugins && wouldLoadAsNetscapePlugin(url, serviceType))
280         return;
281
282     bool beforeLoadAllowedLoad = dispatchBeforeLoadEvent(url);
283
284     // beforeload events can modify the DOM, potentially causing
285     // RenderWidget::destroy() to be called.  Ensure we haven't been
286     // destroyed before continuing.
287     // FIXME: Should this render fallback content?
288     if (!renderer())
289         return;
290
291     SubframeLoader* loader = document()->frame()->loader()->subframeLoader();
292     bool success = beforeLoadAllowedLoad && loader->requestObject(this, url, getAttribute(nameAttr), serviceType, paramNames, paramValues);
293
294     if (!success && fallbackContent)
295         renderFallbackContent();
296 }
297
298 bool HTMLObjectElement::rendererIsNeeded(RenderStyle* style)
299 {
300     // FIXME: This check should not be needed, detached documents never render!
301     Frame* frame = document()->frame();
302     if (!frame)
303         return false;
304     
305     // Temporary Workaround for Gears plugin - see bug 24215 for details and bug 24346 to track removal.
306     // Gears expects the plugin to be instantiated even if display:none is set
307     // for the object element.
308     bool isGearsPlugin = equalIgnoringCase(getAttribute(typeAttr), "application/x-googlegears");
309     return isGearsPlugin || HTMLPlugInImageElement::rendererIsNeeded(style);
310 }
311
312 void HTMLObjectElement::insertedIntoDocument()
313 {
314     if (isDocNamedItem() && document()->isHTMLDocument()) {
315         HTMLDocument* document = static_cast<HTMLDocument*>(this->document());
316         document->addNamedItem(m_name);
317         document->addExtraNamedItem(m_id);
318     }
319
320     HTMLPlugInImageElement::insertedIntoDocument();
321 }
322
323 void HTMLObjectElement::removedFromDocument()
324 {
325     if (isDocNamedItem() && document()->isHTMLDocument()) {
326         HTMLDocument* document = static_cast<HTMLDocument*>(this->document());
327         document->removeNamedItem(m_name);
328         document->removeExtraNamedItem(m_id);
329     }
330
331     HTMLPlugInImageElement::removedFromDocument();
332 }
333
334 void HTMLObjectElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta)
335 {
336     updateDocNamedItem();
337     if (inDocument() && !useFallbackContent()) {
338         setNeedsWidgetUpdate(true);
339         setNeedsStyleRecalc();
340     }
341     HTMLPlugInImageElement::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta);
342 }
343
344 bool HTMLObjectElement::isURLAttribute(Attribute *attr) const
345 {
346     return (attr->name() == dataAttr || (attr->name() == usemapAttr && attr->value().string()[0] != '#'));
347 }
348
349 const QualifiedName& HTMLObjectElement::imageSourceAttributeName() const
350 {
351     return dataAttr;
352 }
353
354 void HTMLObjectElement::renderFallbackContent()
355 {
356     if (useFallbackContent())
357         return;
358     
359     if (!inDocument())
360         return;
361
362     // Before we give up and use fallback content, check to see if this is a MIME type issue.
363     if (m_imageLoader && m_imageLoader->image()) {
364         m_serviceType = m_imageLoader->image()->response().mimeType();
365         if (!isImageType()) {
366             // If we don't think we have an image type anymore, then ditch the image loader.
367             m_imageLoader.clear();        
368             detach();
369             attach();
370             return;
371         }
372     }
373
374     m_useFallbackContent = true;
375
376     // FIXME: Style gets recalculated which is suboptimal.
377     detach();
378     attach();
379 }
380
381 // FIXME: This should be removed, all callers are almost certainly wrong.
382 static bool isRecognizedTagName(const QualifiedName& tagName)
383 {
384     DEFINE_STATIC_LOCAL(HashSet<AtomicStringImpl*>, tagList, ());
385     if (tagList.isEmpty()) {
386         size_t tagCount = 0;
387         QualifiedName** tags = HTMLNames::getHTMLTags(&tagCount);
388         for (size_t i = 0; i < tagCount; i++) {
389             if (*tags[i] == bgsoundTag
390                 || *tags[i] == commandTag
391                 || *tags[i] == detailsTag
392                 || *tags[i] == figcaptionTag
393                 || *tags[i] == figureTag
394                 || *tags[i] == summaryTag
395                 || *tags[i] == trackTag) {
396                 // Even though we have atoms for these tags, we don't want to
397                 // treat them as "recognized tags" for the purpose of parsing
398                 // because that changes how we parse documents.
399                 continue;
400             }
401             tagList.add(tags[i]->localName().impl());
402         }
403     }
404     return tagList.contains(tagName.localName().impl());
405 }
406
407 void HTMLObjectElement::updateDocNamedItem()
408 {
409     // The rule is "<object> elements with no children other than
410     // <param> elements, unknown elements and whitespace can be
411     // found by name in a document, and other <object> elements cannot."
412     bool wasNamedItem = m_docNamedItem;
413     bool isNamedItem = true;
414     Node* child = firstChild();
415     while (child && isNamedItem) {
416         if (child->isElementNode()) {
417             Element* element = static_cast<Element*>(child);
418             // FIXME: Use of isRecognizedTagName is almost certainly wrong here.
419             if (isRecognizedTagName(element->tagQName()) && !element->hasTagName(paramTag))
420                 isNamedItem = false;
421         } else if (child->isTextNode()) {
422             if (!static_cast<Text*>(child)->containsOnlyWhitespace())
423                 isNamedItem = false;
424         } else
425             isNamedItem = false;
426         child = child->nextSibling();
427     }
428     if (isNamedItem != wasNamedItem && document()->isHTMLDocument()) {
429         HTMLDocument* document = static_cast<HTMLDocument*>(this->document());
430         if (isNamedItem) {
431             document->addNamedItem(m_name);
432             document->addExtraNamedItem(m_id);
433         } else {
434             document->removeNamedItem(m_name);
435             document->removeExtraNamedItem(m_id);
436         }
437     }
438     m_docNamedItem = isNamedItem;
439 }
440
441 bool HTMLObjectElement::containsJavaApplet() const
442 {
443     if (MIMETypeRegistry::isJavaAppletMIMEType(getAttribute(typeAttr)))
444         return true;
445         
446     for (Element* child = firstElementChild(); child; child = child->nextElementSibling()) {
447         if (child->hasTagName(paramTag)
448                 && equalIgnoringCase(child->getAttribute(nameAttr), "type")
449                 && MIMETypeRegistry::isJavaAppletMIMEType(child->getAttribute(valueAttr).string()))
450             return true;
451         if (child->hasTagName(objectTag)
452                 && static_cast<HTMLObjectElement*>(child)->containsJavaApplet())
453             return true;
454         if (child->hasTagName(appletTag))
455             return true;
456     }
457     
458     return false;
459 }
460
461 void HTMLObjectElement::addSubresourceAttributeURLs(ListHashSet<KURL>& urls) const
462 {
463     HTMLPlugInImageElement::addSubresourceAttributeURLs(urls);
464
465     addSubresourceURL(urls, document()->completeURL(getAttribute(dataAttr)));
466
467     // FIXME: Passing a string that starts with "#" to the completeURL function does
468     // not seem like it would work. The image element has similar but not identical code.
469     const AtomicString& useMap = getAttribute(usemapAttr);
470     if (useMap.startsWith("#"))
471         addSubresourceURL(urls, document()->completeURL(useMap));
472 }
473
474 }