Improve use of NeverDestroyed
[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-2017 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 "ElementIterator.h"
31 #include "FormDataList.h"
32 #include "Frame.h"
33 #include "FrameLoader.h"
34 #include "HTMLDocument.h"
35 #include "HTMLFormElement.h"
36 #include "HTMLImageLoader.h"
37 #include "HTMLMetaElement.h"
38 #include "HTMLNames.h"
39 #include "HTMLParamElement.h"
40 #include "HTMLParserIdioms.h"
41 #include "MIMETypeRegistry.h"
42 #include "NodeList.h"
43 #include "Page.h"
44 #include "PluginViewBase.h"
45 #include "RenderEmbeddedObject.h"
46 #include "RenderImage.h"
47 #include "RenderWidget.h"
48 #include "Settings.h"
49 #include "SubframeLoader.h"
50 #include "Text.h"
51 #include "Widget.h"
52 #include <wtf/Ref.h>
53
54 #if PLATFORM(IOS)
55 #include "RuntimeApplicationChecks.h"
56 #include <wtf/spi/darwin/dyldSPI.h>
57 #endif
58
59 namespace WebCore {
60
61 using namespace HTMLNames;
62
63 inline HTMLObjectElement::HTMLObjectElement(const QualifiedName& tagName, Document& document, HTMLFormElement* form, bool createdByParser)
64     : HTMLPlugInImageElement(tagName, document, createdByParser)
65     , FormAssociatedElement(form)
66     , m_docNamedItem(true)
67     , m_useFallbackContent(false)
68 {
69     ASSERT(hasTagName(objectTag));
70 }
71
72 inline HTMLObjectElement::~HTMLObjectElement()
73 {
74 }
75
76 Ref<HTMLObjectElement> HTMLObjectElement::create(const QualifiedName& tagName, Document& document, HTMLFormElement* form, bool createdByParser)
77 {
78     return adoptRef(*new HTMLObjectElement(tagName, document, form, createdByParser));
79 }
80
81 RenderWidget* HTMLObjectElement::renderWidgetLoadingPlugin() const
82 {
83     // Needs to load the plugin immediatedly because this function is called
84     // when JavaScript code accesses the plugin.
85     // FIXME: <rdar://16893708> Check if dispatching events here is safe.
86     document().updateLayoutIgnorePendingStylesheets(Document::RunPostLayoutTasks::Synchronously);
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     bool invalidateRenderer = false;
108
109     if (name == formAttr)
110         formAttributeChanged();
111     else if (name == typeAttr) {
112         m_serviceType = value.string().left(value.find(';')).convertToASCIILowercase();
113         invalidateRenderer = !hasAttributeWithoutSynchronization(classidAttr);
114         setNeedsWidgetUpdate(true);
115     } else if (name == dataAttr) {
116         m_url = stripLeadingAndTrailingHTMLSpaces(value);
117         document().updateStyleIfNeeded();
118         if (isImageType() && renderer()) {
119             if (!m_imageLoader)
120                 m_imageLoader = std::make_unique<HTMLImageLoader>(*this);
121             m_imageLoader->updateFromElementIgnoringPreviousError();
122         }
123         invalidateRenderer = !hasAttributeWithoutSynchronization(classidAttr);
124         setNeedsWidgetUpdate(true);
125     } else if (name == classidAttr) {
126         invalidateRenderer = true;
127         setNeedsWidgetUpdate(true);
128     } else
129         HTMLPlugInImageElement::parseAttribute(name, value);
130
131     if (!invalidateRenderer || !isConnected() || !renderer())
132         return;
133
134     clearUseFallbackContent();
135     invalidateStyleAndRenderersForSubtree();
136 }
137
138 static void mapDataParamToSrc(Vector<String>& paramNames, Vector<String>& paramValues)
139 {
140     // Some plugins don't understand the "data" attribute of the OBJECT tag (i.e. Real and WMP require "src" attribute).
141     bool foundSrcParam = false;
142     String dataParamValue;
143     for (unsigned i = 0; i < paramNames.size(); ++i) {
144         if (equalLettersIgnoringASCIICase(paramNames[i], "src"))
145             foundSrcParam = true;
146         else if (equalLettersIgnoringASCIICase(paramNames[i], "data"))
147             dataParamValue = paramValues[i];
148     }
149     if (!foundSrcParam && !dataParamValue.isNull()) {
150         paramNames.append(ASCIILiteral("src"));
151         paramValues.append(WTFMove(dataParamValue));
152     }
153 }
154
155 // FIXME: This function should not deal with url or serviceType!
156 void HTMLObjectElement::parametersForPlugin(Vector<String>& paramNames, Vector<String>& paramValues, String& url, String& serviceType)
157 {
158     HashSet<StringImpl*, ASCIICaseInsensitiveHash> uniqueParamNames;
159     String urlParameter;
160     
161     // Scan the PARAM children and store their name/value pairs.
162     // Get the URL and type from the params if we don't already have them.
163     for (auto& param : childrenOfType<HTMLParamElement>(*this)) {
164         String name = param.name();
165         if (name.isEmpty())
166             continue;
167
168         uniqueParamNames.add(name.impl());
169         paramNames.append(param.name());
170         paramValues.append(param.value());
171
172         // FIXME: url adjustment does not belong in this function.
173         if (url.isEmpty() && urlParameter.isEmpty() && (equalLettersIgnoringASCIICase(name, "src") || equalLettersIgnoringASCIICase(name, "movie") || equalLettersIgnoringASCIICase(name, "code") || equalLettersIgnoringASCIICase(name, "url")))
174             urlParameter = stripLeadingAndTrailingHTMLSpaces(param.value());
175         // FIXME: serviceType calculation does not belong in this function.
176         if (serviceType.isEmpty() && equalLettersIgnoringASCIICase(name, "type")) {
177             serviceType = param.value();
178             size_t pos = serviceType.find(';');
179             if (pos != notFound)
180                 serviceType = serviceType.left(pos);
181         }
182     }
183     
184     // When OBJECT is used for an applet via Sun's Java plugin, the CODEBASE attribute in the tag
185     // points to the Java plugin itself (an ActiveX component) while the actual applet CODEBASE is
186     // in a PARAM tag. See <http://java.sun.com/products/plugin/1.2/docs/tags.html>. This means
187     // we have to explicitly suppress the tag's CODEBASE attribute if there is none in a PARAM,
188     // else our Java plugin will misinterpret it. [4004531]
189     String codebase;
190     if (MIMETypeRegistry::isJavaAppletMIMEType(serviceType)) {
191         codebase = "codebase";
192         uniqueParamNames.add(codebase.impl()); // pretend we found it in a PARAM already
193     }
194     
195     // Turn the attributes of the <object> element into arrays, but don't override <param> values.
196     if (hasAttributes()) {
197         for (const Attribute& attribute : attributesIterator()) {
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
213     if (url.isEmpty() && !urlParameter.isEmpty()) {
214         SubframeLoader& loader = document().frame()->loader().subframeLoader();
215         if (loader.resourceWillUsePlugin(urlParameter, serviceType))
216             url = urlParameter;
217     }
218 }
219
220     
221 bool HTMLObjectElement::hasFallbackContent() const
222 {
223     for (Node* child = firstChild(); child; child = child->nextSibling()) {
224         // Ignore whitespace-only text, and <param> tags, any other content is fallback content.
225         if (is<Text>(*child)) {
226             if (!downcast<Text>(*child).containsOnlyWhitespace())
227                 return true;
228         } else if (!is<HTMLParamElement>(*child))
229             return true;
230     }
231     return false;
232 }
233     
234 bool HTMLObjectElement::shouldAllowQuickTimeClassIdQuirk()
235 {
236     // This site-specific hack maintains compatibility with Mac OS X Wiki Server,
237     // which embeds QuickTime movies using an object tag containing QuickTime's
238     // ActiveX classid. Treat this classid as valid only if OS X Server's unique
239     // 'generator' meta tag is present. Only apply this quirk if there is no
240     // fallback content, which ensures the quirk will disable itself if Wiki
241     // Server is updated to generate an alternate embed tag as fallback content.
242
243     if (!document().page()
244         || !document().page()->settings().needsSiteSpecificQuirks()
245         || hasFallbackContent()
246         || !equalLettersIgnoringASCIICase(attributeWithoutSynchronization(classidAttr), "clsid:02bf25d5-8c17-4b23-bc80-d3488abddc6b"))
247         return false;
248
249     for (auto& metaElement : descendantsOfType<HTMLMetaElement>(document())) {
250         if (equalLettersIgnoringASCIICase(metaElement.name(), "generator") && metaElement.content().startsWith("Mac OS X Server Web Services Server", false))
251             return true;
252     }
253
254     return false;
255 }
256     
257 bool HTMLObjectElement::hasValidClassId()
258 {
259     if (MIMETypeRegistry::isJavaAppletMIMEType(serviceType()) && attributeWithoutSynchronization(classidAttr).startsWith("java:", false))
260         return true;
261     
262     if (shouldAllowQuickTimeClassIdQuirk())
263         return true;
264
265     // HTML5 says that fallback content should be rendered if a non-empty
266     // classid is specified for which the UA can't find a suitable plug-in.
267     return attributeWithoutSynchronization(classidAttr).isEmpty();
268 }
269
270 // FIXME: This should be unified with HTMLEmbedElement::updateWidget and
271 // moved down into HTMLPluginImageElement.cpp
272 void HTMLObjectElement::updateWidget(CreatePlugins createPlugins)
273 {
274     ASSERT(!renderEmbeddedObject()->isPluginUnavailable());
275     ASSERT(needsWidgetUpdate());
276     setNeedsWidgetUpdate(false);
277     // FIXME: This should ASSERT isFinishedParsingChildren() instead.
278     if (!isFinishedParsingChildren())
279         return;
280
281     // FIXME: I'm not sure it's ever possible to get into updateWidget during a
282     // removal, but just in case we should avoid loading the frame to prevent
283     // security bugs.
284     if (!SubframeLoadingDisabler::canLoadFrame(*this))
285         return;
286
287     String url = this->url();
288     String serviceType = this->serviceType();
289
290     // FIXME: These should be joined into a PluginParameters class.
291     Vector<String> paramNames;
292     Vector<String> paramValues;
293     parametersForPlugin(paramNames, paramValues, url, serviceType);
294
295     // Note: url is modified above by parametersForPlugin.
296     if (!allowedToLoadFrameURL(url))
297         return;
298
299     // FIXME: It's sadness that we have this special case here.
300     //        See http://trac.webkit.org/changeset/25128 and
301     //        plugins/netscape-plugin-setwindow-size.html
302     if (createPlugins == CreatePlugins::No && wouldLoadAsPlugIn(url, serviceType)) {
303         // Ensure updateWidget() is called again during layout to create the Netscape plug-in.
304         setNeedsWidgetUpdate(true);
305         return;
306     }
307
308     Ref<HTMLObjectElement> protectedThis(*this); // beforeload and plugin loading can make arbitrary DOM mutations.
309     bool beforeLoadAllowedLoad = guardedDispatchBeforeLoadEvent(url);
310     if (!renderer()) // Do not load the plugin if beforeload removed this element or its renderer.
311         return;
312
313     bool success = beforeLoadAllowedLoad && hasValidClassId() && allowedToLoadFrameURL(url);
314     if (success)
315         success = requestObject(url, serviceType, paramNames, paramValues);
316     if (!success && hasFallbackContent())
317         renderFallbackContent();
318 }
319
320 Node::InsertionNotificationRequest HTMLObjectElement::insertedInto(ContainerNode& insertionPoint)
321 {
322     HTMLPlugInImageElement::insertedInto(insertionPoint);
323     FormAssociatedElement::insertedInto(insertionPoint);
324     return InsertionShouldCallFinishedInsertingSubtree;
325 }
326
327 void HTMLObjectElement::finishedInsertingSubtree()
328 {
329     resetFormOwner();
330 }
331
332 void HTMLObjectElement::removedFrom(ContainerNode& insertionPoint)
333 {
334     HTMLPlugInImageElement::removedFrom(insertionPoint);
335     FormAssociatedElement::removedFrom(insertionPoint);
336 }
337
338 void HTMLObjectElement::childrenChanged(const ChildChange& change)
339 {
340     updateDocNamedItem();
341     if (isConnected() && !useFallbackContent()) {
342         setNeedsWidgetUpdate(true);
343         invalidateStyleForSubtree();
344     }
345     HTMLPlugInImageElement::childrenChanged(change);
346 }
347
348 bool HTMLObjectElement::isURLAttribute(const Attribute& attribute) const
349 {
350     return attribute.name() == dataAttr || attribute.name() == codebaseAttr || (attribute.name() == usemapAttr && attribute.value().string()[0] != '#') || HTMLPlugInImageElement::isURLAttribute(attribute);
351 }
352
353 const AtomicString& HTMLObjectElement::imageSourceURL() const
354 {
355     return attributeWithoutSynchronization(dataAttr);
356 }
357
358 void HTMLObjectElement::renderFallbackContent()
359 {
360     if (useFallbackContent())
361         return;
362     
363     if (!isConnected())
364         return;
365
366     invalidateStyleAndRenderersForSubtree();
367
368     // Before we give up and use fallback content, check to see if this is a MIME type issue.
369     auto* loader = imageLoader();
370     if (loader && loader->image() && loader->image()->status() != CachedResource::LoadError) {
371         m_serviceType = loader->image()->response().mimeType();
372         if (!isImageType()) {
373             // If we don't think we have an image type anymore, then clear the image from the loader.
374             loader->clearImage();
375             return;
376         }
377     }
378
379     m_useFallbackContent = true;
380
381     // This was added to keep Acid 2 non-flaky. A style recalc is required to make fallback resources load.
382     // Without forcing, this may happen after all the other resources have been loaded and the document is already
383     // considered complete. FIXME: Would be better to address this with incrementLoadEventDelayCount instead
384     // or disentangle loading from style entirely.
385     document().updateStyleIfNeeded();
386 }
387
388 // FIXME: This should be removed, all callers are almost certainly wrong.
389 static bool isRecognizedTagName(const QualifiedName& tagName)
390 {
391     static const auto tagList = makeNeverDestroyed([] {
392         HashSet<AtomicStringImpl*> map;
393         auto* tags = HTMLNames::getHTMLTags();
394         for (size_t i = 0; i < HTMLNames::HTMLTagsCount; i++) {
395             if (*tags[i] == bgsoundTag
396                 || *tags[i] == commandTag
397                 || *tags[i] == detailsTag
398                 || *tags[i] == figcaptionTag
399                 || *tags[i] == figureTag
400                 || *tags[i] == summaryTag
401                 || *tags[i] == trackTag) {
402                 // Even though we have atoms for these tags, we don't want to
403                 // treat them as "recognized tags" for the purpose of parsing
404                 // because that changes how we parse documents.
405                 continue;
406             }
407             map.add(tags[i]->localName().impl());
408         }
409         return map;
410     }());
411     return tagList.get().contains(tagName.localName().impl());
412 }
413
414 void HTMLObjectElement::updateDocNamedItem()
415 {
416     // The rule is "<object> elements with no children other than
417     // <param> elements, unknown elements and whitespace can be
418     // found by name in a document, and other <object> elements cannot."
419     bool wasNamedItem = m_docNamedItem;
420     bool isNamedItem = true;
421     Node* child = firstChild();
422     while (child && isNamedItem) {
423         if (is<Element>(*child)) {
424             Element& element = downcast<Element>(*child);
425             // FIXME: Use of isRecognizedTagName is almost certainly wrong here.
426             if (isRecognizedTagName(element.tagQName()) && !element.hasTagName(paramTag))
427                 isNamedItem = false;
428         } else if (is<Text>(*child)) {
429             if (!downcast<Text>(*child).containsOnlyWhitespace())
430                 isNamedItem = false;
431         } else
432             isNamedItem = false;
433         child = child->nextSibling();
434     }
435     if (isNamedItem != wasNamedItem && isConnected() && !isInShadowTree() && is<HTMLDocument>(document())) {
436         HTMLDocument& document = downcast<HTMLDocument>(this->document());
437
438         const AtomicString& id = getIdAttribute();
439         if (!id.isEmpty()) {
440             if (isNamedItem)
441                 document.addDocumentNamedItem(*id.impl(), *this);
442             else
443                 document.removeDocumentNamedItem(*id.impl(), *this);
444         }
445
446         const AtomicString& name = getNameAttribute();
447         if (!name.isEmpty() && id != name) {
448             if (isNamedItem)
449                 document.addDocumentNamedItem(*name.impl(), *this);
450             else
451                 document.removeDocumentNamedItem(*name.impl(), *this);
452         }
453     }
454     m_docNamedItem = isNamedItem;
455 }
456
457 bool HTMLObjectElement::containsJavaApplet() const
458 {
459     if (MIMETypeRegistry::isJavaAppletMIMEType(attributeWithoutSynchronization(typeAttr)))
460         return true;
461
462     for (auto& child : childrenOfType<Element>(*this)) {
463         if (child.hasTagName(paramTag) && equalLettersIgnoringASCIICase(child.getNameAttribute(), "type")
464             && MIMETypeRegistry::isJavaAppletMIMEType(child.attributeWithoutSynchronization(valueAttr).string()))
465             return true;
466         if (child.hasTagName(objectTag) && downcast<HTMLObjectElement>(child).containsJavaApplet())
467             return true;
468         if (child.hasTagName(appletTag))
469             return true;
470     }
471     
472     return false;
473 }
474
475 void HTMLObjectElement::addSubresourceAttributeURLs(ListHashSet<URL>& urls) const
476 {
477     HTMLPlugInImageElement::addSubresourceAttributeURLs(urls);
478
479     addSubresourceURL(urls, document().completeURL(attributeWithoutSynchronization(dataAttr)));
480
481     // FIXME: Passing a string that starts with "#" to the completeURL function does
482     // not seem like it would work. The image element has similar but not identical code.
483     const AtomicString& useMap = attributeWithoutSynchronization(usemapAttr);
484     if (useMap.startsWith('#'))
485         addSubresourceURL(urls, document().completeURL(useMap));
486 }
487
488 void HTMLObjectElement::didMoveToNewDocument(Document& oldDocument, Document& newDocument)
489 {
490     FormAssociatedElement::didMoveToNewDocument(oldDocument);
491     HTMLPlugInImageElement::didMoveToNewDocument(oldDocument, newDocument);
492 }
493
494 bool HTMLObjectElement::appendFormData(FormDataList& encoding, bool)
495 {
496     if (name().isEmpty())
497         return false;
498
499     // Use PluginLoadingPolicy::DoNotLoad here or it would fire JS events synchronously
500     // which would not be safe here.
501     auto* widget = pluginWidget(PluginLoadingPolicy::DoNotLoad);
502     if (!is<PluginViewBase>(widget))
503         return false;
504     String value;
505     if (!downcast<PluginViewBase>(*widget).getFormValue(value))
506         return false;
507     encoding.appendData(name(), value);
508     return true;
509 }
510
511 bool HTMLObjectElement::canContainRangeEndPoint() const
512 {
513     // Call through to HTMLElement because we need to skip HTMLPlugInElement
514     // when calling through to the derived class since returns false unconditionally.
515     // An object element with fallback content should basically be treated like
516     // a generic HTML element.
517     return m_useFallbackContent && HTMLElement::canContainRangeEndPoint();
518 }
519
520 }