Make tabIndex IDL attribute reflect its content attribute
[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 int HTMLObjectElement::defaultTabIndex() const
90 {
91     return 0;
92 }
93
94 bool HTMLObjectElement::isPresentationAttribute(const QualifiedName& name) const
95 {
96     if (name == borderAttr)
97         return true;
98     return HTMLPlugInImageElement::isPresentationAttribute(name);
99 }
100
101 void HTMLObjectElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomString& value, MutableStyleProperties& style)
102 {
103     if (name == borderAttr)
104         applyBorderAttributeToStyle(value, style);
105     else
106         HTMLPlugInImageElement::collectStyleForPresentationAttribute(name, value, style);
107 }
108
109 void HTMLObjectElement::parseAttribute(const QualifiedName& name, const AtomString& value)
110 {
111     bool invalidateRenderer = false;
112
113     if (name == formAttr)
114         formAttributeChanged();
115     else if (name == typeAttr) {
116         m_serviceType = value.string().left(value.find(';')).convertToASCIILowercase();
117         invalidateRenderer = !hasAttributeWithoutSynchronization(classidAttr);
118         setNeedsWidgetUpdate(true);
119     } else if (name == dataAttr) {
120         m_url = stripLeadingAndTrailingHTMLSpaces(value);
121         invalidateRenderer = !hasAttributeWithoutSynchronization(classidAttr);
122         setNeedsWidgetUpdate(true);
123         updateImageLoaderWithNewURLSoon();
124     } else if (name == classidAttr) {
125         invalidateRenderer = true;
126         setNeedsWidgetUpdate(true);
127     } else
128         HTMLPlugInImageElement::parseAttribute(name, value);
129
130     if (!invalidateRenderer || !isConnected() || !renderer())
131         return;
132
133     m_useFallbackContent = false;
134     scheduleUpdateForAfterStyleResolution();
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("src"_s);
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 AtomString& 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 bool HTMLObjectElement::hasFallbackContent() const
221 {
222     for (RefPtr<Node> child = firstChild(); child; child = child->nextSibling()) {
223         // Ignore whitespace-only text, and <param> tags, any other content is fallback content.
224         if (is<Text>(*child)) {
225             if (!downcast<Text>(*child).data().isAllSpecialCharacters<isHTMLSpace>())
226                 return true;
227         } else if (!is<HTMLParamElement>(*child))
228             return true;
229     }
230     return false;
231 }
232
233 bool HTMLObjectElement::hasValidClassId()
234 {
235     if (MIMETypeRegistry::isJavaAppletMIMEType(serviceType()) && protocolIs(attributeWithoutSynchronization(classidAttr), "java"))
236         return true;
237
238     // HTML5 says that fallback content should be rendered if a non-empty
239     // classid is specified for which the UA can't find a suitable plug-in.
240     return attributeWithoutSynchronization(classidAttr).isEmpty();
241 }
242
243 // FIXME: This should be unified with HTMLEmbedElement::updateWidget and
244 // moved down into HTMLPluginImageElement.cpp
245 void HTMLObjectElement::updateWidget(CreatePlugins createPlugins)
246 {
247     ASSERT(!renderEmbeddedObject()->isPluginUnavailable());
248     ASSERT(needsWidgetUpdate());
249
250     // FIXME: This should ASSERT isFinishedParsingChildren() instead.
251     if (!isFinishedParsingChildren()) {
252         setNeedsWidgetUpdate(false);
253         return;
254     }
255
256     // FIXME: I'm not sure it's ever possible to get into updateWidget during a
257     // removal, but just in case we should avoid loading the frame to prevent
258     // security bugs.
259     if (!SubframeLoadingDisabler::canLoadFrame(*this)) {
260         setNeedsWidgetUpdate(false);
261         return;
262     }
263
264     String url = this->url();
265     String serviceType = this->serviceType();
266
267     // FIXME: These should be joined into a PluginParameters class.
268     Vector<String> paramNames;
269     Vector<String> paramValues;
270     parametersForPlugin(paramNames, paramValues, url, serviceType);
271
272     // Note: url is modified above by parametersForPlugin.
273     if (!canLoadURL(url)) {
274         setNeedsWidgetUpdate(false);
275         return;
276     }
277
278     // FIXME: It's unfortunate that we have this special case here.
279     // See http://trac.webkit.org/changeset/25128 and the plugins/netscape-plugin-setwindow-size.html test.
280     if (createPlugins == CreatePlugins::No && wouldLoadAsPlugIn(url, serviceType))
281         return;
282
283     setNeedsWidgetUpdate(false);
284
285     Ref<HTMLObjectElement> protectedThis(*this); // beforeload and plugin loading can make arbitrary DOM mutations.
286     bool beforeLoadAllowedLoad = guardedDispatchBeforeLoadEvent(url);
287     if (!renderer()) // Do not load the plugin if beforeload removed this element or its renderer.
288         return;
289
290     // Dispatching a beforeLoad event could have executed code that changed the document.
291     // Make sure the URL is still safe to load.
292     bool success = beforeLoadAllowedLoad && hasValidClassId() && canLoadURL(url);
293     if (success)
294         success = requestObject(url, serviceType, paramNames, paramValues);
295     if (!success && hasFallbackContent())
296         renderFallbackContent();
297 }
298
299 Node::InsertedIntoAncestorResult HTMLObjectElement::insertedIntoAncestor(InsertionType insertionType, ContainerNode& parentOfInsertedTree)
300 {
301     HTMLPlugInImageElement::insertedIntoAncestor(insertionType, parentOfInsertedTree);
302     FormAssociatedElement::insertedIntoAncestor(insertionType, parentOfInsertedTree);
303     return InsertedIntoAncestorResult::NeedsPostInsertionCallback;
304 }
305
306 void HTMLObjectElement::didFinishInsertingNode()
307 {
308     resetFormOwner();
309 }
310
311 void HTMLObjectElement::removedFromAncestor(RemovalType removalType, ContainerNode& oldParentOfRemovedTree)
312 {
313     HTMLPlugInImageElement::removedFromAncestor(removalType, oldParentOfRemovedTree);
314     FormAssociatedElement::removedFromAncestor(removalType, oldParentOfRemovedTree);
315 }
316
317 void HTMLObjectElement::childrenChanged(const ChildChange& change)
318 {
319     updateExposedState();
320     if (isConnected() && !m_useFallbackContent) {
321         setNeedsWidgetUpdate(true);
322         scheduleUpdateForAfterStyleResolution();
323         invalidateStyleForSubtree();
324     }
325     HTMLPlugInImageElement::childrenChanged(change);
326 }
327
328 bool HTMLObjectElement::isURLAttribute(const Attribute& attribute) const
329 {
330     return attribute.name() == dataAttr || attribute.name() == codebaseAttr || (attribute.name() == usemapAttr && attribute.value().string()[0] != '#') || HTMLPlugInImageElement::isURLAttribute(attribute);
331 }
332
333 bool HTMLObjectElement::isInteractiveContent() const
334 {
335     return hasAttributeWithoutSynchronization(usemapAttr);
336 }
337
338 const AtomString& HTMLObjectElement::imageSourceURL() const
339 {
340     return attributeWithoutSynchronization(dataAttr);
341 }
342
343 void HTMLObjectElement::renderFallbackContent()
344 {
345     if (m_useFallbackContent)
346         return;
347     
348     if (!isConnected())
349         return;
350
351     scheduleUpdateForAfterStyleResolution();
352     invalidateStyleAndRenderersForSubtree();
353
354     // Before we give up and use fallback content, check to see if this is a MIME type issue.
355     auto* loader = imageLoader();
356     if (loader && loader->image() && loader->image()->status() != CachedResource::LoadError) {
357         m_serviceType = loader->image()->response().mimeType();
358         if (!isImageType()) {
359             // If we don't think we have an image type anymore, then clear the image from the loader.
360             loader->clearImage();
361             return;
362         }
363     }
364
365     m_useFallbackContent = true;
366 }
367
368 static inline bool preventsParentObjectFromExposure(const Element& child)
369 {
370     static const auto mostKnownTags = makeNeverDestroyed([] {
371         HashSet<QualifiedName> set;
372         auto* tags = HTMLNames::getHTMLTags();
373         for (size_t i = 0; i < HTMLNames::HTMLTagsCount; i++) {
374             auto& tag = *tags[i];
375             // Only the param element was explicitly mentioned in the HTML specification rule
376             // we were trying to implement, but these are other known HTML elements that we
377             // have decided, over the years, to treat as children that do not prevent object
378             // names from being exposed.
379             if (tag == bgsoundTag
380                 || tag == commandTag
381                 || tag == detailsTag
382                 || tag == figcaptionTag
383                 || tag == figureTag
384                 || tag == paramTag
385                 || tag == summaryTag
386                 || tag == trackTag)
387                 continue;
388             set.add(tag);
389         }
390         return set;
391     }());
392     return mostKnownTags.get().contains(child.tagQName());
393 }
394
395 static inline bool preventsParentObjectFromExposure(const Node& child)
396 {
397     if (is<Element>(child))
398         return preventsParentObjectFromExposure(downcast<Element>(child));
399     if (is<Text>(child))
400         return !downcast<Text>(child).data().isAllSpecialCharacters<isHTMLSpace>();
401     return true;
402 }
403
404 static inline bool shouldBeExposed(const HTMLObjectElement& element)
405 {
406     // FIXME: This should be redone to use the concept of an exposed object element,
407     // as documented in the HTML specification section describing DOM tree accessors.
408
409     // The rule we try to implement here, from older HTML specifications, is "object elements
410     // with no children other than param elements, unknown elements and whitespace can be found
411     // by name in a document, and other object elements cannot".
412
413     for (auto child = makeRefPtr(element.firstChild()); child; child = child->nextSibling()) {
414         if (preventsParentObjectFromExposure(*child))
415             return false;
416     }
417     return true;
418 }
419
420 void HTMLObjectElement::updateExposedState()
421 {
422     bool wasExposed = std::exchange(m_isExposed, shouldBeExposed(*this));
423
424     if (m_isExposed != wasExposed && isConnected() && !isInShadowTree() && is<HTMLDocument>(document())) {
425         auto& document = downcast<HTMLDocument>(this->document());
426
427         auto& id = getIdAttribute();
428         if (!id.isEmpty()) {
429             if (m_isExposed)
430                 document.addDocumentNamedItem(*id.impl(), *this);
431             else
432                 document.removeDocumentNamedItem(*id.impl(), *this);
433         }
434
435         auto& name = getNameAttribute();
436         if (!name.isEmpty() && id != name) {
437             if (m_isExposed)
438                 document.addDocumentNamedItem(*name.impl(), *this);
439             else
440                 document.removeDocumentNamedItem(*name.impl(), *this);
441         }
442     }
443 }
444
445 bool HTMLObjectElement::containsJavaApplet() const
446 {
447     if (MIMETypeRegistry::isJavaAppletMIMEType(attributeWithoutSynchronization(typeAttr)))
448         return true;
449
450     for (auto& child : childrenOfType<Element>(*this)) {
451         if (child.hasTagName(paramTag) && equalLettersIgnoringASCIICase(child.getNameAttribute(), "type")
452             && MIMETypeRegistry::isJavaAppletMIMEType(child.attributeWithoutSynchronization(valueAttr).string()))
453             return true;
454         if (child.hasTagName(objectTag) && downcast<HTMLObjectElement>(child).containsJavaApplet())
455             return true;
456         if (child.hasTagName(appletTag))
457             return true;
458     }
459     
460     return false;
461 }
462
463 void HTMLObjectElement::addSubresourceAttributeURLs(ListHashSet<URL>& urls) const
464 {
465     HTMLPlugInImageElement::addSubresourceAttributeURLs(urls);
466
467     addSubresourceURL(urls, document().completeURL(attributeWithoutSynchronization(dataAttr)));
468
469     // FIXME: Passing a string that starts with "#" to the completeURL function does
470     // not seem like it would work. The image element has similar but not identical code.
471     const AtomString& useMap = attributeWithoutSynchronization(usemapAttr);
472     if (useMap.startsWith('#'))
473         addSubresourceURL(urls, document().completeURL(useMap));
474 }
475
476 void HTMLObjectElement::didMoveToNewDocument(Document& oldDocument, Document& newDocument)
477 {
478     FormAssociatedElement::didMoveToNewDocument(oldDocument);
479     HTMLPlugInImageElement::didMoveToNewDocument(oldDocument, newDocument);
480 }
481
482 bool HTMLObjectElement::appendFormData(DOMFormData& formData, bool)
483 {
484     if (name().isEmpty())
485         return false;
486
487     // Use PluginLoadingPolicy::DoNotLoad here or it would fire JS events synchronously
488     // which would not be safe here.
489     auto widget = makeRefPtr(pluginWidget(PluginLoadingPolicy::DoNotLoad));
490     if (!is<PluginViewBase>(widget))
491         return false;
492     String value;
493     if (!downcast<PluginViewBase>(*widget).getFormValue(value))
494         return false;
495     formData.append(name(), value);
496     return true;
497 }
498
499 bool HTMLObjectElement::canContainRangeEndPoint() const
500 {
501     // Call through to HTMLElement because HTMLPlugInElement::canContainRangeEndPoint
502     // returns false unconditionally. An object element using fallback content is
503     // treated like a generic HTML element.
504     return m_useFallbackContent && HTMLElement::canContainRangeEndPoint();
505 }
506
507 }