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