e0cfc17c60f72b70b2f35c948769c5d6c401c850
[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 "DOMFormData.h"
31 #include "ElementIterator.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/IsoMallocInlines.h>
53 #include <wtf/Ref.h>
54
55 #if PLATFORM(IOS_FAMILY)
56 #include "RuntimeApplicationChecks.h"
57 #include <wtf/spi/darwin/dyldSPI.h>
58 #endif
59
60 namespace WebCore {
61
62 WTF_MAKE_ISO_ALLOCATED_IMPL(HTMLObjectElement);
63
64 using namespace HTMLNames;
65
66 inline HTMLObjectElement::HTMLObjectElement(const QualifiedName& tagName, Document& document, HTMLFormElement* form)
67     : HTMLPlugInImageElement(tagName, document)
68     , FormAssociatedElement(form)
69 {
70     ASSERT(hasTagName(objectTag));
71 }
72
73 Ref<HTMLObjectElement> HTMLObjectElement::create(const QualifiedName& tagName, Document& document, HTMLFormElement* form)
74 {
75     auto result = adoptRef(*new HTMLObjectElement(tagName, document, form));
76     result->finishCreating();
77     return result;
78 }
79
80 RenderWidget* HTMLObjectElement::renderWidgetLoadingPlugin() const
81 {
82     // Needs to load the plugin immediatedly because this function is called
83     // when JavaScript code accesses the plugin.
84     // FIXME: <rdar://16893708> Check if dispatching events here is safe.
85     document().updateLayoutIgnorePendingStylesheets(Document::RunPostLayoutTasks::Synchronously);
86     return renderWidget(); // This will return 0 if the renderer is not a RenderWidget.
87 }
88
89 bool HTMLObjectElement::isPresentationAttribute(const QualifiedName& name) const
90 {
91     if (name == borderAttr)
92         return true;
93     return HTMLPlugInImageElement::isPresentationAttribute(name);
94 }
95
96 void HTMLObjectElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomString& value, MutableStyleProperties& style)
97 {
98     if (name == borderAttr)
99         applyBorderAttributeToStyle(value, style);
100     else
101         HTMLPlugInImageElement::collectStyleForPresentationAttribute(name, value, style);
102 }
103
104 void HTMLObjectElement::parseAttribute(const QualifiedName& name, const AtomString& value)
105 {
106     bool invalidateRenderer = false;
107
108     if (name == formAttr)
109         formAttributeChanged();
110     else if (name == typeAttr) {
111         m_serviceType = value.string().left(value.find(';')).convertToASCIILowercase();
112         invalidateRenderer = !hasAttributeWithoutSynchronization(classidAttr);
113         setNeedsWidgetUpdate(true);
114     } else if (name == dataAttr) {
115         m_url = stripLeadingAndTrailingHTMLSpaces(value);
116         invalidateRenderer = !hasAttributeWithoutSynchronization(classidAttr);
117         setNeedsWidgetUpdate(true);
118         updateImageLoaderWithNewURLSoon();
119     } else if (name == classidAttr) {
120         invalidateRenderer = true;
121         setNeedsWidgetUpdate(true);
122     } else
123         HTMLPlugInImageElement::parseAttribute(name, value);
124
125     if (!invalidateRenderer || !isConnected() || !renderer())
126         return;
127
128     m_useFallbackContent = false;
129     scheduleUpdateForAfterStyleResolution();
130     invalidateStyleAndRenderersForSubtree();
131 }
132
133 static void mapDataParamToSrc(Vector<String>& paramNames, Vector<String>& paramValues)
134 {
135     // Some plugins don't understand the "data" attribute of the OBJECT tag (i.e. Real and WMP require "src" attribute).
136     bool foundSrcParam = false;
137     String dataParamValue;
138     for (unsigned i = 0; i < paramNames.size(); ++i) {
139         if (equalLettersIgnoringASCIICase(paramNames[i], "src"))
140             foundSrcParam = true;
141         else if (equalLettersIgnoringASCIICase(paramNames[i], "data"))
142             dataParamValue = paramValues[i];
143     }
144     if (!foundSrcParam && !dataParamValue.isNull()) {
145         paramNames.append("src"_s);
146         paramValues.append(WTFMove(dataParamValue));
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*, ASCIICaseInsensitiveHash> 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 (auto& param : childrenOfType<HTMLParamElement>(*this)) {
159         String name = param.name();
160         if (name.isEmpty())
161             continue;
162
163         uniqueParamNames.add(name.impl());
164         paramNames.append(param.name());
165         paramValues.append(param.value());
166
167         // FIXME: url adjustment does not belong in this function.
168         if (url.isEmpty() && urlParameter.isEmpty() && (equalLettersIgnoringASCIICase(name, "src") || equalLettersIgnoringASCIICase(name, "movie") || equalLettersIgnoringASCIICase(name, "code") || equalLettersIgnoringASCIICase(name, "url")))
169             urlParameter = stripLeadingAndTrailingHTMLSpaces(param.value());
170         // FIXME: serviceType calculation does not belong in this function.
171         if (serviceType.isEmpty() && equalLettersIgnoringASCIICase(name, "type")) {
172             serviceType = param.value();
173             size_t pos = serviceType.find(';');
174             if (pos != notFound)
175                 serviceType = serviceType.left(pos);
176         }
177     }
178     
179     // When OBJECT is used for an applet via Sun's Java plugin, the CODEBASE attribute in the tag
180     // points to the Java plugin itself (an ActiveX component) while the actual applet CODEBASE is
181     // in a PARAM tag. See <http://java.sun.com/products/plugin/1.2/docs/tags.html>. This means
182     // we have to explicitly suppress the tag's CODEBASE attribute if there is none in a PARAM,
183     // else our Java plugin will misinterpret it. [4004531]
184     String codebase;
185     if (MIMETypeRegistry::isJavaAppletMIMEType(serviceType)) {
186         codebase = "codebase";
187         uniqueParamNames.add(codebase.impl()); // pretend we found it in a PARAM already
188     }
189     
190     // Turn the attributes of the <object> element into arrays, but don't override <param> values.
191     if (hasAttributes()) {
192         for (const Attribute& attribute : attributesIterator()) {
193             const AtomString& name = attribute.name().localName();
194             if (!uniqueParamNames.contains(name.impl())) {
195                 paramNames.append(name.string());
196                 paramValues.append(attribute.value().string());
197             }
198         }
199     }
200     
201     mapDataParamToSrc(paramNames, paramValues);
202     
203     // HTML5 says that an object resource's URL is specified by the object's data
204     // attribute, not by a param element. However, for compatibility, allow the
205     // resource's URL to be given by a param named "src", "movie", "code" or "url"
206     // if we know that resource points to a plug-in.
207
208     if (url.isEmpty() && !urlParameter.isEmpty()) {
209         SubframeLoader& loader = document().frame()->loader().subframeLoader();
210         if (loader.resourceWillUsePlugin(urlParameter, serviceType))
211             url = urlParameter;
212     }
213 }
214
215 bool HTMLObjectElement::hasFallbackContent() const
216 {
217     for (RefPtr<Node> child = firstChild(); child; child = child->nextSibling()) {
218         // Ignore whitespace-only text, and <param> tags, any other content is fallback content.
219         if (is<Text>(*child)) {
220             if (!downcast<Text>(*child).data().isAllSpecialCharacters<isHTMLSpace>())
221                 return true;
222         } else if (!is<HTMLParamElement>(*child))
223             return true;
224     }
225     return false;
226 }
227
228 bool HTMLObjectElement::hasValidClassId()
229 {
230     if (MIMETypeRegistry::isJavaAppletMIMEType(serviceType()) && protocolIs(attributeWithoutSynchronization(classidAttr), "java"))
231         return true;
232
233     // HTML5 says that fallback content should be rendered if a non-empty
234     // classid is specified for which the UA can't find a suitable plug-in.
235     return attributeWithoutSynchronization(classidAttr).isEmpty();
236 }
237
238 // FIXME: This should be unified with HTMLEmbedElement::updateWidget and
239 // moved down into HTMLPluginImageElement.cpp
240 void HTMLObjectElement::updateWidget(CreatePlugins createPlugins)
241 {
242     ASSERT(!renderEmbeddedObject()->isPluginUnavailable());
243     ASSERT(needsWidgetUpdate());
244
245     // FIXME: This should ASSERT isFinishedParsingChildren() instead.
246     if (!isFinishedParsingChildren()) {
247         setNeedsWidgetUpdate(false);
248         return;
249     }
250
251     // FIXME: I'm not sure it's ever possible to get into updateWidget during a
252     // removal, but just in case we should avoid loading the frame to prevent
253     // security bugs.
254     if (!SubframeLoadingDisabler::canLoadFrame(*this)) {
255         setNeedsWidgetUpdate(false);
256         return;
257     }
258
259     String url = this->url();
260     String serviceType = this->serviceType();
261
262     // FIXME: These should be joined into a PluginParameters class.
263     Vector<String> paramNames;
264     Vector<String> paramValues;
265     parametersForPlugin(paramNames, paramValues, url, serviceType);
266
267     // Note: url is modified above by parametersForPlugin.
268     if (!canLoadURL(url)) {
269         setNeedsWidgetUpdate(false);
270         return;
271     }
272
273     // FIXME: It's unfortunate that we have this special case here.
274     // See http://trac.webkit.org/changeset/25128 and the plugins/netscape-plugin-setwindow-size.html test.
275     if (createPlugins == CreatePlugins::No && wouldLoadAsPlugIn(url, serviceType))
276         return;
277
278     setNeedsWidgetUpdate(false);
279
280     Ref<HTMLObjectElement> protectedThis(*this); // beforeload and plugin loading can make arbitrary DOM mutations.
281     bool beforeLoadAllowedLoad = guardedDispatchBeforeLoadEvent(url);
282     if (!renderer()) // Do not load the plugin if beforeload removed this element or its renderer.
283         return;
284
285     // Dispatching a beforeLoad event could have executed code that changed the document.
286     // Make sure the URL is still safe to load.
287     bool success = beforeLoadAllowedLoad && hasValidClassId() && canLoadURL(url);
288     if (success)
289         success = requestObject(url, serviceType, paramNames, paramValues);
290     if (!success && hasFallbackContent())
291         renderFallbackContent();
292 }
293
294 Node::InsertedIntoAncestorResult HTMLObjectElement::insertedIntoAncestor(InsertionType insertionType, ContainerNode& parentOfInsertedTree)
295 {
296     HTMLPlugInImageElement::insertedIntoAncestor(insertionType, parentOfInsertedTree);
297     FormAssociatedElement::insertedIntoAncestor(insertionType, parentOfInsertedTree);
298     return InsertedIntoAncestorResult::NeedsPostInsertionCallback;
299 }
300
301 void HTMLObjectElement::didFinishInsertingNode()
302 {
303     resetFormOwner();
304 }
305
306 void HTMLObjectElement::removedFromAncestor(RemovalType removalType, ContainerNode& oldParentOfRemovedTree)
307 {
308     HTMLPlugInImageElement::removedFromAncestor(removalType, oldParentOfRemovedTree);
309     FormAssociatedElement::removedFromAncestor(removalType, oldParentOfRemovedTree);
310 }
311
312 void HTMLObjectElement::childrenChanged(const ChildChange& change)
313 {
314     updateExposedState();
315     if (isConnected() && !m_useFallbackContent) {
316         setNeedsWidgetUpdate(true);
317         scheduleUpdateForAfterStyleResolution();
318         invalidateStyleForSubtree();
319     }
320     HTMLPlugInImageElement::childrenChanged(change);
321 }
322
323 bool HTMLObjectElement::isURLAttribute(const Attribute& attribute) const
324 {
325     return attribute.name() == dataAttr || attribute.name() == codebaseAttr || (attribute.name() == usemapAttr && attribute.value().string()[0] != '#') || HTMLPlugInImageElement::isURLAttribute(attribute);
326 }
327
328 bool HTMLObjectElement::isInteractiveContent() const
329 {
330     return hasAttributeWithoutSynchronization(usemapAttr);
331 }
332
333 const AtomString& HTMLObjectElement::imageSourceURL() const
334 {
335     return attributeWithoutSynchronization(dataAttr);
336 }
337
338 void HTMLObjectElement::renderFallbackContent()
339 {
340     if (m_useFallbackContent)
341         return;
342     
343     if (!isConnected())
344         return;
345
346     scheduleUpdateForAfterStyleResolution();
347     invalidateStyleAndRenderersForSubtree();
348
349     // Before we give up and use fallback content, check to see if this is a MIME type issue.
350     auto* loader = imageLoader();
351     if (loader && loader->image() && loader->image()->status() != CachedResource::LoadError) {
352         m_serviceType = loader->image()->response().mimeType();
353         if (!isImageType()) {
354             // If we don't think we have an image type anymore, then clear the image from the loader.
355             loader->clearImage();
356             return;
357         }
358     }
359
360     m_useFallbackContent = true;
361 }
362
363 static inline bool preventsParentObjectFromExposure(const Element& child)
364 {
365     static const auto mostKnownTags = makeNeverDestroyed([] {
366         HashSet<QualifiedName> set;
367         auto* tags = HTMLNames::getHTMLTags();
368         for (size_t i = 0; i < HTMLNames::HTMLTagsCount; i++) {
369             auto& tag = *tags[i];
370             // Only the param element was explicitly mentioned in the HTML specification rule
371             // we were trying to implement, but these are other known HTML elements that we
372             // have decided, over the years, to treat as children that do not prevent object
373             // names from being exposed.
374             if (tag == bgsoundTag
375                 || tag == commandTag
376                 || tag == detailsTag
377                 || tag == figcaptionTag
378                 || tag == figureTag
379                 || tag == paramTag
380                 || tag == summaryTag
381                 || tag == trackTag)
382                 continue;
383             set.add(tag);
384         }
385         return set;
386     }());
387     return mostKnownTags.get().contains(child.tagQName());
388 }
389
390 static inline bool preventsParentObjectFromExposure(const Node& child)
391 {
392     if (is<Element>(child))
393         return preventsParentObjectFromExposure(downcast<Element>(child));
394     if (is<Text>(child))
395         return !downcast<Text>(child).data().isAllSpecialCharacters<isHTMLSpace>();
396     return true;
397 }
398
399 static inline bool shouldBeExposed(const HTMLObjectElement& element)
400 {
401     // FIXME: This should be redone to use the concept of an exposed object element,
402     // as documented in the HTML specification section describing DOM tree accessors.
403
404     // The rule we try to implement here, from older HTML specifications, is "object elements
405     // with no children other than param elements, unknown elements and whitespace can be found
406     // by name in a document, and other object elements cannot".
407
408     for (auto child = makeRefPtr(element.firstChild()); child; child = child->nextSibling()) {
409         if (preventsParentObjectFromExposure(*child))
410             return false;
411     }
412     return true;
413 }
414
415 void HTMLObjectElement::updateExposedState()
416 {
417     bool wasExposed = std::exchange(m_isExposed, shouldBeExposed(*this));
418
419     if (m_isExposed != wasExposed && isConnected() && !isInShadowTree() && is<HTMLDocument>(document())) {
420         auto& document = downcast<HTMLDocument>(this->document());
421
422         auto& id = getIdAttribute();
423         if (!id.isEmpty()) {
424             if (m_isExposed)
425                 document.addDocumentNamedItem(*id.impl(), *this);
426             else
427                 document.removeDocumentNamedItem(*id.impl(), *this);
428         }
429
430         auto& name = getNameAttribute();
431         if (!name.isEmpty() && id != name) {
432             if (m_isExposed)
433                 document.addDocumentNamedItem(*name.impl(), *this);
434             else
435                 document.removeDocumentNamedItem(*name.impl(), *this);
436         }
437     }
438 }
439
440 bool HTMLObjectElement::containsJavaApplet() const
441 {
442     if (MIMETypeRegistry::isJavaAppletMIMEType(attributeWithoutSynchronization(typeAttr)))
443         return true;
444
445     for (auto& child : childrenOfType<Element>(*this)) {
446         if (child.hasTagName(paramTag) && equalLettersIgnoringASCIICase(child.getNameAttribute(), "type")
447             && MIMETypeRegistry::isJavaAppletMIMEType(child.attributeWithoutSynchronization(valueAttr).string()))
448             return true;
449         if (child.hasTagName(objectTag) && downcast<HTMLObjectElement>(child).containsJavaApplet())
450             return true;
451         if (child.hasTagName(appletTag))
452             return true;
453     }
454     
455     return false;
456 }
457
458 void HTMLObjectElement::addSubresourceAttributeURLs(ListHashSet<URL>& urls) const
459 {
460     HTMLPlugInImageElement::addSubresourceAttributeURLs(urls);
461
462     addSubresourceURL(urls, document().completeURL(attributeWithoutSynchronization(dataAttr)));
463
464     // FIXME: Passing a string that starts with "#" to the completeURL function does
465     // not seem like it would work. The image element has similar but not identical code.
466     const AtomString& useMap = attributeWithoutSynchronization(usemapAttr);
467     if (useMap.startsWith('#'))
468         addSubresourceURL(urls, document().completeURL(useMap));
469 }
470
471 void HTMLObjectElement::didMoveToNewDocument(Document& oldDocument, Document& newDocument)
472 {
473     FormAssociatedElement::didMoveToNewDocument(oldDocument);
474     HTMLPlugInImageElement::didMoveToNewDocument(oldDocument, newDocument);
475 }
476
477 bool HTMLObjectElement::appendFormData(DOMFormData& formData, bool)
478 {
479     if (name().isEmpty())
480         return false;
481
482     // Use PluginLoadingPolicy::DoNotLoad here or it would fire JS events synchronously
483     // which would not be safe here.
484     auto widget = makeRefPtr(pluginWidget(PluginLoadingPolicy::DoNotLoad));
485     if (!is<PluginViewBase>(widget))
486         return false;
487     String value;
488     if (!downcast<PluginViewBase>(*widget).getFormValue(value))
489         return false;
490     formData.append(name(), value);
491     return true;
492 }
493
494 bool HTMLObjectElement::canContainRangeEndPoint() const
495 {
496     // Call through to HTMLElement because HTMLPlugInElement::canContainRangeEndPoint
497     // returns false unconditionally. An object element using fallback content is
498     // treated like a generic HTML element.
499     return m_useFallbackContent && HTMLElement::canContainRangeEndPoint();
500 }
501
502 }