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