Use ElementTraversal some more
[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 "ElementTraversal.h"
33 #include "EventNames.h"
34 #include "ExceptionCode.h"
35 #include "FormDataList.h"
36 #include "Frame.h"
37 #include "HTMLDocument.h"
38 #include "HTMLFormElement.h"
39 #include "HTMLImageLoader.h"
40 #include "HTMLMetaElement.h"
41 #include "HTMLNames.h"
42 #include "HTMLParamElement.h"
43 #include "HTMLParserIdioms.h"
44 #include "MIMETypeRegistry.h"
45 #include "NodeList.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 == onbeforeloadAttr)
125         setAttributeEventListener(eventNames().beforeloadEvent, createAttributeEventListener(this, name, value));
126     else
127         HTMLPlugInImageElement::parseAttribute(name, value);
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 (auto param = Traversal<HTMLParamElement>::firstChild(this); param; param = Traversal<HTMLParamElement>::nextSibling(param)) {
157         String name = param->name();
158         if (name.isEmpty())
159             continue;
160
161         uniqueParamNames.add(name.impl());
162         paramNames.append(param->name());
163         paramValues.append(param->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(param->value());
168         // FIXME: serviceType calculation does not belong in this function.
169         if (serviceType.isEmpty() && equalIgnoringCase(name, "type")) {
170             serviceType = param->value();
171             size_t pos = serviceType.find(";");
172             if (pos != notFound)
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     if (hasAttributes()) {
190         for (unsigned i = 0; i < attributeCount(); ++i) {
191             const Attribute& attribute = attributeAt(i);
192             const AtomicString& name = attribute.name().localName();
193             if (!uniqueParamNames.contains(name.impl())) {
194                 paramNames.append(name.string());
195                 paramValues.append(attribute.value().string());
196             }
197         }
198     }
199     
200     mapDataParamToSrc(&paramNames, &paramValues);
201     
202     // HTML5 says that an object resource's URL is specified by the object's data
203     // attribute, not by a param element. However, for compatibility, allow the
204     // resource's URL to be given by a param named "src", "movie", "code" or "url"
205     // if we know that resource points to a plug-in.
206     if (url.isEmpty() && !urlParameter.isEmpty()) {
207         SubframeLoader* loader = document()->frame()->loader().subframeLoader();
208         if (loader->resourceWillUsePlugin(urlParameter, serviceType, shouldPreferPlugInsForImages()))
209             url = urlParameter;
210     }
211 }
212
213     
214 bool 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 (!child->hasTagName(paramTag))
222             return true;
223     }
224     return false;
225 }
226     
227 bool HTMLObjectElement::shouldAllowQuickTimeClassIdQuirk()
228 {
229     // This site-specific hack maintains compatibility with Mac OS X Wiki Server,
230     // which embeds QuickTime movies using an object tag containing QuickTime's
231     // ActiveX classid. Treat this classid as valid only if OS X Server's unique
232     // 'generator' meta tag is present. Only apply this quirk if there is no
233     // fallback content, which ensures the quirk will disable itself if Wiki
234     // Server is updated to generate an alternate embed tag as fallback content.
235     if (!document()->page()
236         || !document()->page()->settings().needsSiteSpecificQuirks()
237         || hasFallbackContent()
238         || !equalIgnoringCase(classId(), "clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B"))
239         return false;
240
241     RefPtr<NodeList> metaElements = document()->getElementsByTagName(HTMLNames::metaTag.localName());
242     unsigned length = metaElements->length();
243     for (unsigned i = 0; i < length; ++i) {
244         ASSERT(metaElements->item(i)->isHTMLElement());
245         HTMLMetaElement* metaElement = static_cast<HTMLMetaElement*>(metaElements->item(i));
246         if (equalIgnoringCase(metaElement->name(), "generator") && metaElement->content().startsWith("Mac OS X Server Web Services Server", false))
247             return true;
248     }
249     
250     return false;
251 }
252     
253 bool HTMLObjectElement::hasValidClassId()
254 {
255 #if PLATFORM(QT)
256     if (equalIgnoringCase(serviceType(), "application/x-qt-plugin") || equalIgnoringCase(serviceType(), "application/x-qt-styled-widget"))
257         return true;
258 #endif
259
260     if (MIMETypeRegistry::isJavaAppletMIMEType(serviceType()) && classId().startsWith("java:", false))
261         return true;
262     
263     if (shouldAllowQuickTimeClassIdQuirk())
264         return true;
265
266     // HTML5 says that fallback content should be rendered if a non-empty
267     // classid is specified for which the UA can't find a suitable plug-in.
268     return classId().isEmpty();
269 }
270
271 // FIXME: This should be unified with HTMLEmbedElement::updateWidget and
272 // moved down into HTMLPluginImageElement.cpp
273 void HTMLObjectElement::updateWidget(PluginCreationOption pluginCreationOption)
274 {
275     ASSERT(!renderEmbeddedObject()->isPluginUnavailable());
276     ASSERT(needsWidgetUpdate());
277     setNeedsWidgetUpdate(false);
278     // FIXME: This should ASSERT isFinishedParsingChildren() instead.
279     if (!isFinishedParsingChildren())
280         return;
281
282     // FIXME: I'm not sure it's ever possible to get into updateWidget during a
283     // removal, but just in case we should avoid loading the frame to prevent
284     // security bugs.
285     if (!SubframeLoadingDisabler::canLoadFrame(this))
286         return;
287
288     String url = this->url();
289     String serviceType = this->serviceType();
290
291     // FIXME: These should be joined into a PluginParameters class.
292     Vector<String> paramNames;
293     Vector<String> paramValues;
294     parametersForPlugin(paramNames, paramValues, url, serviceType);
295
296     // Note: url is modified above by parametersForPlugin.
297     if (!allowedToLoadFrameURL(url))
298         return;
299
300     bool fallbackContent = hasFallbackContent();
301     renderEmbeddedObject()->setHasFallbackContent(fallbackContent);
302
303     // FIXME: It's sadness that we have this special case here.
304     //        See http://trac.webkit.org/changeset/25128 and
305     //        plugins/netscape-plugin-setwindow-size.html
306     if (pluginCreationOption == CreateOnlyNonNetscapePlugins && wouldLoadAsNetscapePlugin(url, serviceType)) {
307         // Ensure updateWidget() is called again during layout to create the Netscape plug-in.
308         setNeedsWidgetUpdate(true);
309         return;
310     }
311
312     RefPtr<HTMLObjectElement> protect(this); // beforeload and plugin loading can make arbitrary DOM mutations.
313     bool beforeLoadAllowedLoad = guardedDispatchBeforeLoadEvent(url);
314     if (!renderer()) // Do not load the plugin if beforeload removed this element or its renderer.
315         return;
316
317     SubframeLoader* loader = document()->frame()->loader().subframeLoader();
318     bool success = beforeLoadAllowedLoad && hasValidClassId() && loader->requestObject(this, url, getNameAttribute(), serviceType, paramNames, paramValues);
319     if (!success && fallbackContent)
320         renderFallbackContent();
321 }
322
323 bool HTMLObjectElement::rendererIsNeeded(const RenderStyle& style)
324 {
325     // FIXME: This check should not be needed, detached documents never render!
326     Frame* frame = document()->frame();
327     if (!frame)
328         return false;
329
330     return HTMLPlugInImageElement::rendererIsNeeded(style);
331 }
332
333 Node::InsertionNotificationRequest HTMLObjectElement::insertedInto(ContainerNode* insertionPoint)
334 {
335     HTMLPlugInImageElement::insertedInto(insertionPoint);
336     FormAssociatedElement::insertedInto(insertionPoint);
337     return InsertionDone;
338 }
339
340 void HTMLObjectElement::removedFrom(ContainerNode* insertionPoint)
341 {
342     HTMLPlugInImageElement::removedFrom(insertionPoint);
343     FormAssociatedElement::removedFrom(insertionPoint);
344 }
345
346 void HTMLObjectElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta)
347 {
348     updateDocNamedItem();
349     if (inDocument() && !useFallbackContent()) {
350         setNeedsWidgetUpdate(true);
351         setNeedsStyleRecalc();
352     }
353     HTMLPlugInImageElement::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta);
354 }
355
356 bool HTMLObjectElement::isURLAttribute(const Attribute& attribute) const
357 {
358     return attribute.name() == dataAttr || (attribute.name() == usemapAttr && attribute.value().string()[0] != '#') || HTMLPlugInImageElement::isURLAttribute(attribute);
359 }
360
361 const AtomicString& HTMLObjectElement::imageSourceURL() const
362 {
363     return getAttribute(dataAttr);
364 }
365
366 void HTMLObjectElement::renderFallbackContent()
367 {
368     if (useFallbackContent())
369         return;
370     
371     if (!inDocument())
372         return;
373
374     // Before we give up and use fallback content, check to see if this is a MIME type issue.
375     if (m_imageLoader && m_imageLoader->image() && m_imageLoader->image()->status() != CachedResource::LoadError) {
376         m_serviceType = m_imageLoader->image()->response().mimeType();
377         if (!isImageType()) {
378             // If we don't think we have an image type anymore, then clear the image from the loader.
379             m_imageLoader->setImage(0);
380             Style::reattachRenderTree(this);
381             return;
382         }
383     }
384
385     m_useFallbackContent = true;
386
387     // FIXME: Style gets recalculated which is suboptimal.
388     Style::reattachRenderTree(this);
389 }
390
391 // FIXME: This should be removed, all callers are almost certainly wrong.
392 static bool isRecognizedTagName(const QualifiedName& tagName)
393 {
394     DEFINE_STATIC_LOCAL(HashSet<AtomicStringImpl*>, tagList, ());
395     if (tagList.isEmpty()) {
396         QualifiedName** tags = HTMLNames::getHTMLTags();
397         for (size_t i = 0; i < HTMLNames::HTMLTagsCount; i++) {
398             if (*tags[i] == bgsoundTag
399                 || *tags[i] == commandTag
400                 || *tags[i] == detailsTag
401                 || *tags[i] == figcaptionTag
402                 || *tags[i] == figureTag
403                 || *tags[i] == summaryTag
404                 || *tags[i] == trackTag) {
405                 // Even though we have atoms for these tags, we don't want to
406                 // treat them as "recognized tags" for the purpose of parsing
407                 // because that changes how we parse documents.
408                 continue;
409             }
410             tagList.add(tags[i]->localName().impl());
411         }
412     }
413     return tagList.contains(tagName.localName().impl());
414 }
415
416 void HTMLObjectElement::updateDocNamedItem()
417 {
418     // The rule is "<object> elements with no children other than
419     // <param> elements, unknown elements and whitespace can be
420     // found by name in a document, and other <object> elements cannot."
421     bool wasNamedItem = m_docNamedItem;
422     bool isNamedItem = true;
423     Node* child = firstChild();
424     while (child && isNamedItem) {
425         if (child->isElementNode()) {
426             Element* element = toElement(child);
427             // FIXME: Use of isRecognizedTagName is almost certainly wrong here.
428             if (isRecognizedTagName(element->tagQName()) && !element->hasTagName(paramTag))
429                 isNamedItem = false;
430         } else if (child->isTextNode()) {
431             if (!toText(child)->containsOnlyWhitespace())
432                 isNamedItem = false;
433         } else
434             isNamedItem = false;
435         child = child->nextSibling();
436     }
437     if (isNamedItem != wasNamedItem && inDocument() && document()->isHTMLDocument()) {
438         HTMLDocument* document = toHTMLDocument(this->document());
439
440         const AtomicString& id = getIdAttribute();
441         if (!id.isEmpty()) {
442             if (isNamedItem)
443                 document->addDocumentNamedItem(id, this);
444             else
445                 document->removeDocumentNamedItem(id, this);
446         }
447
448         const AtomicString& name = getNameAttribute();
449         if (!name.isEmpty() && id != name) {
450             if (isNamedItem)
451                 document->addDocumentNamedItem(name, this);
452             else
453                 document->removeDocumentNamedItem(name, this);
454         }
455     }
456     m_docNamedItem = isNamedItem;
457 }
458
459 bool HTMLObjectElement::containsJavaApplet() const
460 {
461     if (MIMETypeRegistry::isJavaAppletMIMEType(getAttribute(typeAttr)))
462         return true;
463         
464     for (auto child = ElementTraversal::firstChild(this); child; child = ElementTraversal::nextSibling(child)) {
465         if (child->hasTagName(paramTag)
466                 && equalIgnoringCase(child->getNameAttribute(), "type")
467                 && MIMETypeRegistry::isJavaAppletMIMEType(child->getAttribute(valueAttr).string()))
468             return true;
469         if (child->hasTagName(objectTag)
470                 && static_cast<HTMLObjectElement*>(child)->containsJavaApplet())
471             return true;
472         if (child->hasTagName(appletTag))
473             return true;
474     }
475     
476     return false;
477 }
478
479 void HTMLObjectElement::addSubresourceAttributeURLs(ListHashSet<KURL>& urls) const
480 {
481     HTMLPlugInImageElement::addSubresourceAttributeURLs(urls);
482
483     addSubresourceURL(urls, document()->completeURL(getAttribute(dataAttr)));
484
485     // FIXME: Passing a string that starts with "#" to the completeURL function does
486     // not seem like it would work. The image element has similar but not identical code.
487     const AtomicString& useMap = getAttribute(usemapAttr);
488     if (useMap.startsWith('#'))
489         addSubresourceURL(urls, document()->completeURL(useMap));
490 }
491
492 void HTMLObjectElement::didMoveToNewDocument(Document* oldDocument)
493 {
494     FormAssociatedElement::didMoveToNewDocument(oldDocument);
495     HTMLPlugInImageElement::didMoveToNewDocument(oldDocument);
496 }
497
498 bool HTMLObjectElement::appendFormData(FormDataList& encoding, bool)
499 {
500     if (name().isEmpty())
501         return false;
502
503     Widget* widget = pluginWidget();
504     if (!widget || !widget->isPluginViewBase())
505         return false;
506     String value;
507     if (!toPluginViewBase(widget)->getFormValue(value))
508         return false;
509     encoding.appendData(name(), value);
510     return true;
511 }
512
513 HTMLFormElement* HTMLObjectElement::virtualForm() const
514 {
515     return FormAssociatedElement::form();
516 }
517
518 }