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)
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.
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.
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.
25 #include "HTMLObjectElement.h"
27 #include "Attribute.h"
28 #include "CSSValueKeywords.h"
29 #include "CachedImage.h"
31 #include "ChromeClient.h"
32 #include "ElementIterator.h"
33 #include "EventNames.h"
34 #include "ExceptionCode.h"
35 #include "FormDataList.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"
47 #include "PluginViewBase.h"
48 #include "RenderEmbeddedObject.h"
49 #include "RenderImage.h"
50 #include "RenderWidget.h"
52 #include "SubframeLoader.h"
58 #include "RuntimeApplicationChecksIOS.h"
59 #include "WebCoreSystemInterface.h"
64 using namespace HTMLNames;
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)
71 ASSERT(hasTagName(objectTag));
75 inline HTMLObjectElement::~HTMLObjectElement()
79 Ref<HTMLObjectElement> HTMLObjectElement::create(const QualifiedName& tagName, Document& document, HTMLFormElement* form, bool createdByParser)
81 return adoptRef(*new HTMLObjectElement(tagName, document, form, createdByParser));
84 RenderWidget* HTMLObjectElement::renderWidgetLoadingPlugin() const
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.
93 bool HTMLObjectElement::isPresentationAttribute(const QualifiedName& name) const
95 if (name == borderAttr)
97 return HTMLPlugInImageElement::isPresentationAttribute(name);
100 void HTMLObjectElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStyleProperties& style)
102 if (name == borderAttr)
103 applyBorderAttributeToStyle(value, style);
105 HTMLPlugInImageElement::collectStyleForPresentationAttribute(name, value, style);
108 void HTMLObjectElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
110 bool invalidateRenderer = false;
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()) {
123 m_imageLoader = std::make_unique<HTMLImageLoader>(*this);
124 m_imageLoader->updateFromElementIgnoringPreviousError();
126 invalidateRenderer = !fastHasAttribute(classidAttr);
127 setNeedsWidgetUpdate(true);
128 } else if (name == classidAttr) {
129 invalidateRenderer = true;
130 setNeedsWidgetUpdate(true);
131 } else if (name == onbeforeloadAttr)
132 setAttributeEventListener(eventNames().beforeloadEvent, name, value);
134 HTMLPlugInImageElement::parseAttribute(name, value);
136 if (!invalidateRenderer || !inDocument() || !renderer())
139 clearUseFallbackContent();
140 setNeedsStyleRecalc(ReconstructRenderTree);
143 static void mapDataParamToSrc(Vector<String>* paramNames, Vector<String>* paramValues)
145 // Some plugins don't understand the "data" attribute of the OBJECT tag (i.e. Real and WMP
146 // require "src" attribute).
147 int srcIndex = -1, dataIndex = -1;
148 for (unsigned int i = 0; i < paramNames->size(); ++i) {
149 if (equalIgnoringCase((*paramNames)[i], "src"))
151 else if (equalIgnoringCase((*paramNames)[i], "data"))
155 if (srcIndex == -1 && dataIndex != -1) {
156 paramNames->append("src");
157 paramValues->append((*paramValues)[dataIndex]);
162 static bool shouldNotPerformURLAdjustment()
164 static bool shouldNotPerformURLAdjustment = applicationIsNASAHD() && !iosExecutableWasLinkedOnOrAfterVersion(wkIOSSystemVersion_5_0);
165 return shouldNotPerformURLAdjustment;
169 // FIXME: This function should not deal with url or serviceType!
170 void HTMLObjectElement::parametersForPlugin(Vector<String>& paramNames, Vector<String>& paramValues, String& url, String& serviceType)
172 HashSet<StringImpl*, CaseFoldingHash> uniqueParamNames;
175 // Scan the PARAM children and store their name/value pairs.
176 // Get the URL and type from the params if we don't already have them.
177 for (auto& param : childrenOfType<HTMLParamElement>(*this)) {
178 String name = param.name();
182 uniqueParamNames.add(name.impl());
183 paramNames.append(param.name());
184 paramValues.append(param.value());
186 // FIXME: url adjustment does not belong in this function.
187 if (url.isEmpty() && urlParameter.isEmpty() && (equalIgnoringCase(name, "src") || equalIgnoringCase(name, "movie") || equalIgnoringCase(name, "code") || equalIgnoringCase(name, "url")))
188 urlParameter = stripLeadingAndTrailingHTMLSpaces(param.value());
189 // FIXME: serviceType calculation does not belong in this function.
190 if (serviceType.isEmpty() && equalIgnoringCase(name, "type")) {
191 serviceType = param.value();
192 size_t pos = serviceType.find(';');
194 serviceType = serviceType.left(pos);
198 // When OBJECT is used for an applet via Sun's Java plugin, the CODEBASE attribute in the tag
199 // points to the Java plugin itself (an ActiveX component) while the actual applet CODEBASE is
200 // in a PARAM tag. See <http://java.sun.com/products/plugin/1.2/docs/tags.html>. This means
201 // we have to explicitly suppress the tag's CODEBASE attribute if there is none in a PARAM,
202 // else our Java plugin will misinterpret it. [4004531]
204 if (MIMETypeRegistry::isJavaAppletMIMEType(serviceType)) {
205 codebase = "codebase";
206 uniqueParamNames.add(codebase.impl()); // pretend we found it in a PARAM already
209 // Turn the attributes of the <object> element into arrays, but don't override <param> values.
210 if (hasAttributes()) {
211 for (const Attribute& attribute : attributesIterator()) {
212 const AtomicString& name = attribute.name().localName();
213 if (!uniqueParamNames.contains(name.impl())) {
214 paramNames.append(name.string());
215 paramValues.append(attribute.value().string());
220 mapDataParamToSrc(¶mNames, ¶mValues);
222 // HTML5 says that an object resource's URL is specified by the object's data
223 // attribute, not by a param element. However, for compatibility, allow the
224 // resource's URL to be given by a param named "src", "movie", "code" or "url"
225 // if we know that resource points to a plug-in.
227 if (shouldNotPerformURLAdjustment())
231 if (url.isEmpty() && !urlParameter.isEmpty()) {
232 SubframeLoader& loader = document().frame()->loader().subframeLoader();
233 if (loader.resourceWillUsePlugin(urlParameter, serviceType, shouldPreferPlugInsForImages()))
239 bool HTMLObjectElement::hasFallbackContent() const
241 for (Node* child = firstChild(); child; child = child->nextSibling()) {
242 // Ignore whitespace-only text, and <param> tags, any other content is fallback content.
243 if (is<Text>(*child)) {
244 if (!downcast<Text>(*child).containsOnlyWhitespace())
246 } else if (!is<HTMLParamElement>(*child))
252 bool HTMLObjectElement::shouldAllowQuickTimeClassIdQuirk()
254 // This site-specific hack maintains compatibility with Mac OS X Wiki Server,
255 // which embeds QuickTime movies using an object tag containing QuickTime's
256 // ActiveX classid. Treat this classid as valid only if OS X Server's unique
257 // 'generator' meta tag is present. Only apply this quirk if there is no
258 // fallback content, which ensures the quirk will disable itself if Wiki
259 // Server is updated to generate an alternate embed tag as fallback content.
261 if (!document().page()
262 || !document().page()->settings().needsSiteSpecificQuirks()
263 || hasFallbackContent()
264 || !equalIgnoringCase(fastGetAttribute(classidAttr), "clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B"))
267 for (auto& metaElement : descendantsOfType<HTMLMetaElement>(document())) {
268 if (equalIgnoringCase(metaElement.name(), "generator") && metaElement.content().startsWith("Mac OS X Server Web Services Server", false))
275 bool HTMLObjectElement::hasValidClassId()
277 if (MIMETypeRegistry::isJavaAppletMIMEType(serviceType()) && fastGetAttribute(classidAttr).startsWith("java:", false))
280 if (shouldAllowQuickTimeClassIdQuirk())
283 // HTML5 says that fallback content should be rendered if a non-empty
284 // classid is specified for which the UA can't find a suitable plug-in.
285 return fastGetAttribute(classidAttr).isEmpty();
288 // FIXME: This should be unified with HTMLEmbedElement::updateWidget and
289 // moved down into HTMLPluginImageElement.cpp
290 void HTMLObjectElement::updateWidget(PluginCreationOption pluginCreationOption)
292 ASSERT(!renderEmbeddedObject()->isPluginUnavailable());
293 ASSERT(needsWidgetUpdate());
294 setNeedsWidgetUpdate(false);
295 // FIXME: This should ASSERT isFinishedParsingChildren() instead.
296 if (!isFinishedParsingChildren())
299 // FIXME: I'm not sure it's ever possible to get into updateWidget during a
300 // removal, but just in case we should avoid loading the frame to prevent
302 if (!SubframeLoadingDisabler::canLoadFrame(*this))
305 String url = this->url();
306 String serviceType = this->serviceType();
308 // FIXME: These should be joined into a PluginParameters class.
309 Vector<String> paramNames;
310 Vector<String> paramValues;
311 parametersForPlugin(paramNames, paramValues, url, serviceType);
313 // Note: url is modified above by parametersForPlugin.
314 if (!allowedToLoadFrameURL(url))
317 // FIXME: It's sadness that we have this special case here.
318 // See http://trac.webkit.org/changeset/25128 and
319 // plugins/netscape-plugin-setwindow-size.html
320 if (pluginCreationOption == CreateOnlyNonNetscapePlugins && wouldLoadAsNetscapePlugin(url, serviceType)) {
321 // Ensure updateWidget() is called again during layout to create the Netscape plug-in.
322 setNeedsWidgetUpdate(true);
326 Ref<HTMLObjectElement> protect(*this); // beforeload and plugin loading can make arbitrary DOM mutations.
327 bool beforeLoadAllowedLoad = guardedDispatchBeforeLoadEvent(url);
328 if (!renderer()) // Do not load the plugin if beforeload removed this element or its renderer.
331 bool success = beforeLoadAllowedLoad && hasValidClassId();
333 success = requestObject(url, serviceType, paramNames, paramValues);
334 if (!success && hasFallbackContent())
335 renderFallbackContent();
338 Node::InsertionNotificationRequest HTMLObjectElement::insertedInto(ContainerNode& insertionPoint)
340 HTMLPlugInImageElement::insertedInto(insertionPoint);
341 FormAssociatedElement::insertedInto(insertionPoint);
342 return InsertionDone;
345 void HTMLObjectElement::removedFrom(ContainerNode& insertionPoint)
347 HTMLPlugInImageElement::removedFrom(insertionPoint);
348 FormAssociatedElement::removedFrom(insertionPoint);
351 void HTMLObjectElement::childrenChanged(const ChildChange& change)
353 updateDocNamedItem();
354 if (inDocument() && !useFallbackContent()) {
355 setNeedsWidgetUpdate(true);
356 setNeedsStyleRecalc();
358 HTMLPlugInImageElement::childrenChanged(change);
361 bool HTMLObjectElement::isURLAttribute(const Attribute& attribute) const
363 return attribute.name() == dataAttr || (attribute.name() == usemapAttr && attribute.value().string()[0] != '#') || HTMLPlugInImageElement::isURLAttribute(attribute);
366 const AtomicString& HTMLObjectElement::imageSourceURL() const
368 return fastGetAttribute(dataAttr);
371 void HTMLObjectElement::renderFallbackContent()
373 if (useFallbackContent())
379 setNeedsStyleRecalc(ReconstructRenderTree);
381 // Before we give up and use fallback content, check to see if this is a MIME type issue.
382 auto* loader = imageLoader();
383 if (loader && loader->image() && loader->image()->status() != CachedResource::LoadError) {
384 m_serviceType = loader->image()->response().mimeType();
385 if (!isImageType()) {
386 // If we don't think we have an image type anymore, then clear the image from the loader.
387 loader->setImage(nullptr);
392 m_useFallbackContent = true;
394 // This was added to keep Acid 2 non-flaky. A style recalc is required to make fallback resources load.
395 // Without forcing, this may happen after all the other resources have been loaded and the document is already
396 // considered complete. FIXME: Would be better to address this with incrementLoadEventDelayCount instead
397 // or disentangle loading from style entirely.
398 document().updateStyleIfNeeded();
401 // FIXME: This should be removed, all callers are almost certainly wrong.
402 static bool isRecognizedTagName(const QualifiedName& tagName)
404 DEPRECATED_DEFINE_STATIC_LOCAL(HashSet<AtomicStringImpl*>, tagList, ());
405 if (tagList.isEmpty()) {
406 auto* tags = HTMLNames::getHTMLTags();
407 for (size_t i = 0; i < HTMLNames::HTMLTagsCount; i++) {
408 if (*tags[i] == bgsoundTag
409 || *tags[i] == commandTag
410 || *tags[i] == detailsTag
411 || *tags[i] == figcaptionTag
412 || *tags[i] == figureTag
413 || *tags[i] == summaryTag
414 || *tags[i] == trackTag) {
415 // Even though we have atoms for these tags, we don't want to
416 // treat them as "recognized tags" for the purpose of parsing
417 // because that changes how we parse documents.
420 tagList.add(tags[i]->localName().impl());
423 return tagList.contains(tagName.localName().impl());
426 void HTMLObjectElement::updateDocNamedItem()
428 // The rule is "<object> elements with no children other than
429 // <param> elements, unknown elements and whitespace can be
430 // found by name in a document, and other <object> elements cannot."
431 bool wasNamedItem = m_docNamedItem;
432 bool isNamedItem = true;
433 Node* child = firstChild();
434 while (child && isNamedItem) {
435 if (is<Element>(*child)) {
436 Element& element = downcast<Element>(*child);
437 // FIXME: Use of isRecognizedTagName is almost certainly wrong here.
438 if (isRecognizedTagName(element.tagQName()) && !element.hasTagName(paramTag))
440 } else if (is<Text>(*child)) {
441 if (!downcast<Text>(*child).containsOnlyWhitespace())
445 child = child->nextSibling();
447 if (isNamedItem != wasNamedItem && inDocument() && is<HTMLDocument>(document())) {
448 HTMLDocument& document = downcast<HTMLDocument>(this->document());
450 const AtomicString& id = getIdAttribute();
453 document.addDocumentNamedItem(*id.impl(), *this);
455 document.removeDocumentNamedItem(*id.impl(), *this);
458 const AtomicString& name = getNameAttribute();
459 if (!name.isEmpty() && id != name) {
461 document.addDocumentNamedItem(*name.impl(), *this);
463 document.removeDocumentNamedItem(*name.impl(), *this);
466 m_docNamedItem = isNamedItem;
469 bool HTMLObjectElement::containsJavaApplet() const
471 if (MIMETypeRegistry::isJavaAppletMIMEType(getAttribute(typeAttr)))
474 for (auto& child : childrenOfType<Element>(*this)) {
475 if (child.hasTagName(paramTag) && equalIgnoringCase(child.getNameAttribute(), "type")
476 && MIMETypeRegistry::isJavaAppletMIMEType(child.getAttribute(valueAttr).string()))
478 if (child.hasTagName(objectTag) && downcast<HTMLObjectElement>(child).containsJavaApplet())
480 if (child.hasTagName(appletTag))
487 void HTMLObjectElement::addSubresourceAttributeURLs(ListHashSet<URL>& urls) const
489 HTMLPlugInImageElement::addSubresourceAttributeURLs(urls);
491 addSubresourceURL(urls, document().completeURL(fastGetAttribute(dataAttr)));
493 // FIXME: Passing a string that starts with "#" to the completeURL function does
494 // not seem like it would work. The image element has similar but not identical code.
495 const AtomicString& useMap = fastGetAttribute(usemapAttr);
496 if (useMap.startsWith('#'))
497 addSubresourceURL(urls, document().completeURL(useMap));
500 void HTMLObjectElement::didMoveToNewDocument(Document* oldDocument)
502 FormAssociatedElement::didMoveToNewDocument(oldDocument);
503 HTMLPlugInImageElement::didMoveToNewDocument(oldDocument);
506 bool HTMLObjectElement::appendFormData(FormDataList& encoding, bool)
508 if (name().isEmpty())
511 Widget* widget = pluginWidget();
512 if (!is<PluginViewBase>(widget))
515 if (!downcast<PluginViewBase>(*widget).getFormValue(value))
517 encoding.appendData(name(), value);
521 HTMLFormElement* HTMLObjectElement::virtualForm() const
523 return FormAssociatedElement::form();
526 bool HTMLObjectElement::canContainRangeEndPoint() const
528 // Call through to HTMLElement because we need to skip HTMLPlugInElement
529 // when calling through to the derived class since returns false unconditionally.
530 // An object element with fallback content should basically be treated like
531 // a generic HTML element.
532 return m_useFallbackContent && HTMLElement::canContainRangeEndPoint();