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