/* * Copyright (C) 1999 Lars Knoll (knoll@kde.org) * (C) 1999 Antti Koivisto (koivisto@kde.org) * (C) 2000 Stefan Schimanski (1Stein@gmx.de) * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2011 Apple Inc. All rights reserved. * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "config.h" #include "HTMLObjectElement.h" #include "Attribute.h" #include "CSSValueKeywords.h" #include "CachedImage.h" #include "Chrome.h" #include "ChromeClient.h" #include "ElementIterator.h" #include "EventNames.h" #include "ExceptionCode.h" #include "FormDataList.h" #include "Frame.h" #include "HTMLDocument.h" #include "HTMLFormElement.h" #include "HTMLImageLoader.h" #include "HTMLMetaElement.h" #include "HTMLNames.h" #include "HTMLParamElement.h" #include "HTMLParserIdioms.h" #include "MIMETypeRegistry.h" #include "NodeList.h" #include "Page.h" #include "PluginViewBase.h" #include "RenderEmbeddedObject.h" #include "RenderImage.h" #include "RenderWidget.h" #include "Settings.h" #include "SubframeLoader.h" #include "Text.h" #include "Widget.h" #include #if PLATFORM(IOS) #include "DynamicLinkerSPI.h" #include "RuntimeApplicationChecksIOS.h" #endif namespace WebCore { using namespace HTMLNames; inline HTMLObjectElement::HTMLObjectElement(const QualifiedName& tagName, Document& document, HTMLFormElement* form, bool createdByParser) : HTMLPlugInImageElement(tagName, document, createdByParser) , FormAssociatedElement(form) , m_docNamedItem(true) , m_useFallbackContent(false) { ASSERT(hasTagName(objectTag)); } inline HTMLObjectElement::~HTMLObjectElement() { } Ref HTMLObjectElement::create(const QualifiedName& tagName, Document& document, HTMLFormElement* form, bool createdByParser) { return adoptRef(*new HTMLObjectElement(tagName, document, form, createdByParser)); } RenderWidget* HTMLObjectElement::renderWidgetLoadingPlugin() const { // Needs to load the plugin immediatedly because this function is called // when JavaScript code accesses the plugin. // FIXME: Check if dispatching events here is safe. document().updateLayoutIgnorePendingStylesheets(Document::RunPostLayoutTasks::Synchronously); return renderWidget(); // This will return 0 if the renderer is not a RenderWidget. } #if PLATFORM(QT) static bool isQtPluginServiceType(const String& serviceType) { return equalLettersIgnoringASCIICase(serviceType, "application/x-qt-plugin") || equalLettersIgnoringASCIICase(serviceType, "application/x-qt-styled-widget"); } #endif bool HTMLObjectElement::isPresentationAttribute(const QualifiedName& name) const { if (name == borderAttr) return true; return HTMLPlugInImageElement::isPresentationAttribute(name); } void HTMLObjectElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStyleProperties& style) { if (name == borderAttr) applyBorderAttributeToStyle(value, style); else HTMLPlugInImageElement::collectStyleForPresentationAttribute(name, value, style); } void HTMLObjectElement::parseAttribute(const QualifiedName& name, const AtomicString& value) { bool invalidateRenderer = false; if (name == formAttr) formAttributeChanged(); else if (name == typeAttr) { m_serviceType = value.string().left(value.find(';')).convertToASCIILowercase(); invalidateRenderer = !fastHasAttribute(classidAttr); setNeedsWidgetUpdate(true); } else if (name == dataAttr) { m_url = stripLeadingAndTrailingHTMLSpaces(value); document().updateStyleIfNeeded(); if (isImageType() && renderer()) { if (!m_imageLoader) m_imageLoader = std::make_unique(*this); m_imageLoader->updateFromElementIgnoringPreviousError(); } invalidateRenderer = !fastHasAttribute(classidAttr); setNeedsWidgetUpdate(true); } else if (name == classidAttr) { invalidateRenderer = true; setNeedsWidgetUpdate(true); } else HTMLPlugInImageElement::parseAttribute(name, value); if (!invalidateRenderer || !inDocument() || !renderer()) return; clearUseFallbackContent(); setNeedsStyleRecalc(ReconstructRenderTree); } static void mapDataParamToSrc(Vector& paramNames, Vector& paramValues) { // Some plugins don't understand the "data" attribute of the OBJECT tag (i.e. Real and WMP require "src" attribute). bool foundSrcParam = false; String dataParamValue; for (unsigned i = 0; i < paramNames.size(); ++i) { if (equalLettersIgnoringASCIICase(paramNames[i], "src")) foundSrcParam = true; else if (equalLettersIgnoringASCIICase(paramNames[i], "data")) dataParamValue = paramValues[i]; } if (!foundSrcParam && !dataParamValue.isNull()) { paramNames.append(ASCIILiteral("src")); paramValues.append(WTFMove(dataParamValue)); } } #if PLATFORM(IOS) static bool shouldNotPerformURLAdjustment() { static bool shouldNotPerformURLAdjustment = applicationIsNASAHD() && dyld_get_program_sdk_version() < DYLD_IOS_VERSION_5_0; return shouldNotPerformURLAdjustment; } #endif // FIXME: This function should not deal with url or serviceType! void HTMLObjectElement::parametersForPlugin(Vector& paramNames, Vector& paramValues, String& url, String& serviceType) { HashSet uniqueParamNames; String urlParameter; // Scan the PARAM children and store their name/value pairs. // Get the URL and type from the params if we don't already have them. for (auto& param : childrenOfType(*this)) { String name = param.name(); if (name.isEmpty()) continue; uniqueParamNames.add(name.impl()); paramNames.append(param.name()); paramValues.append(param.value()); // FIXME: url adjustment does not belong in this function. if (url.isEmpty() && urlParameter.isEmpty() && (equalLettersIgnoringASCIICase(name, "src") || equalLettersIgnoringASCIICase(name, "movie") || equalLettersIgnoringASCIICase(name, "code") || equalLettersIgnoringASCIICase(name, "url"))) #if PLATFORM(QT) if(!isQtPluginServiceType(serviceType)) #endif { urlParameter = stripLeadingAndTrailingHTMLSpaces(param.value()); } // FIXME: serviceType calculation does not belong in this function. if (serviceType.isEmpty() && equalLettersIgnoringASCIICase(name, "type")) { serviceType = param.value(); size_t pos = serviceType.find(';'); if (pos != notFound) serviceType = serviceType.left(pos); } } // When OBJECT is used for an applet via Sun's Java plugin, the CODEBASE attribute in the tag // points to the Java plugin itself (an ActiveX component) while the actual applet CODEBASE is // in a PARAM tag. See . This means // we have to explicitly suppress the tag's CODEBASE attribute if there is none in a PARAM, // else our Java plugin will misinterpret it. [4004531] String codebase; if (MIMETypeRegistry::isJavaAppletMIMEType(serviceType)) { codebase = "codebase"; uniqueParamNames.add(codebase.impl()); // pretend we found it in a PARAM already } // Turn the attributes of the element into arrays, but don't override values. if (hasAttributes()) { for (const Attribute& attribute : attributesIterator()) { const AtomicString& name = attribute.name().localName(); if (!uniqueParamNames.contains(name.impl())) { paramNames.append(name.string()); paramValues.append(attribute.value().string()); } } } mapDataParamToSrc(paramNames, paramValues); // HTML5 says that an object resource's URL is specified by the object's data // attribute, not by a param element. However, for compatibility, allow the // resource's URL to be given by a param named "src", "movie", "code" or "url" // if we know that resource points to a plug-in. #if PLATFORM(IOS) if (shouldNotPerformURLAdjustment()) return; #endif #if PLATFORM(QT) if (isQtPluginServiceType(serviceType)) return; #endif if (url.isEmpty() && !urlParameter.isEmpty()) { SubframeLoader& loader = document().frame()->loader().subframeLoader(); if (loader.resourceWillUsePlugin(urlParameter, serviceType)) url = urlParameter; } } bool HTMLObjectElement::hasFallbackContent() const { for (Node* child = firstChild(); child; child = child->nextSibling()) { // Ignore whitespace-only text, and tags, any other content is fallback content. if (is(*child)) { if (!downcast(*child).containsOnlyWhitespace()) return true; } else if (!is(*child)) return true; } return false; } bool HTMLObjectElement::shouldAllowQuickTimeClassIdQuirk() { // This site-specific hack maintains compatibility with Mac OS X Wiki Server, // which embeds QuickTime movies using an object tag containing QuickTime's // ActiveX classid. Treat this classid as valid only if OS X Server's unique // 'generator' meta tag is present. Only apply this quirk if there is no // fallback content, which ensures the quirk will disable itself if Wiki // Server is updated to generate an alternate embed tag as fallback content. if (!document().page() || !document().page()->settings().needsSiteSpecificQuirks() || hasFallbackContent() || !equalLettersIgnoringASCIICase(fastGetAttribute(classidAttr), "clsid:02bf25d5-8c17-4b23-bc80-d3488abddc6b")) return false; for (auto& metaElement : descendantsOfType(document())) { if (equalLettersIgnoringASCIICase(metaElement.name(), "generator") && metaElement.content().startsWith("Mac OS X Server Web Services Server", false)) return true; } return false; } bool HTMLObjectElement::hasValidClassId() { #if PLATFORM(QT) if (isQtPluginServiceType(serviceType())) return true; #endif if (MIMETypeRegistry::isJavaAppletMIMEType(serviceType()) && fastGetAttribute(classidAttr).startsWith("java:", false)) return true; if (shouldAllowQuickTimeClassIdQuirk()) return true; // HTML5 says that fallback content should be rendered if a non-empty // classid is specified for which the UA can't find a suitable plug-in. return fastGetAttribute(classidAttr).isEmpty(); } // FIXME: This should be unified with HTMLEmbedElement::updateWidget and // moved down into HTMLPluginImageElement.cpp void HTMLObjectElement::updateWidget(PluginCreationOption pluginCreationOption) { ASSERT(!renderEmbeddedObject()->isPluginUnavailable()); ASSERT(needsWidgetUpdate()); setNeedsWidgetUpdate(false); // FIXME: This should ASSERT isFinishedParsingChildren() instead. if (!isFinishedParsingChildren()) return; // FIXME: I'm not sure it's ever possible to get into updateWidget during a // removal, but just in case we should avoid loading the frame to prevent // security bugs. if (!SubframeLoadingDisabler::canLoadFrame(*this)) return; String url = this->url(); String serviceType = this->serviceType(); // FIXME: These should be joined into a PluginParameters class. Vector paramNames; Vector paramValues; parametersForPlugin(paramNames, paramValues, url, serviceType); // Note: url is modified above by parametersForPlugin. if (!allowedToLoadFrameURL(url)) return; // FIXME: It's sadness that we have this special case here. // See http://trac.webkit.org/changeset/25128 and // plugins/netscape-plugin-setwindow-size.html if (pluginCreationOption == CreateOnlyNonNetscapePlugins && wouldLoadAsNetscapePlugin(url, serviceType)) { // Ensure updateWidget() is called again during layout to create the Netscape plug-in. setNeedsWidgetUpdate(true); return; } Ref protect(*this); // beforeload and plugin loading can make arbitrary DOM mutations. bool beforeLoadAllowedLoad = guardedDispatchBeforeLoadEvent(url); if (!renderer()) // Do not load the plugin if beforeload removed this element or its renderer. return; bool success = beforeLoadAllowedLoad && hasValidClassId(); if (success) success = requestObject(url, serviceType, paramNames, paramValues); if (!success && hasFallbackContent()) renderFallbackContent(); } Node::InsertionNotificationRequest HTMLObjectElement::insertedInto(ContainerNode& insertionPoint) { HTMLPlugInImageElement::insertedInto(insertionPoint); FormAssociatedElement::insertedInto(insertionPoint); return InsertionShouldCallFinishedInsertingSubtree; } void HTMLObjectElement::finishedInsertingSubtree() { resetFormOwner(); } void HTMLObjectElement::removedFrom(ContainerNode& insertionPoint) { HTMLPlugInImageElement::removedFrom(insertionPoint); FormAssociatedElement::removedFrom(insertionPoint); } void HTMLObjectElement::childrenChanged(const ChildChange& change) { updateDocNamedItem(); if (inDocument() && !useFallbackContent()) { setNeedsWidgetUpdate(true); setNeedsStyleRecalc(); } HTMLPlugInImageElement::childrenChanged(change); } bool HTMLObjectElement::isURLAttribute(const Attribute& attribute) const { return attribute.name() == dataAttr || (attribute.name() == usemapAttr && attribute.value().string()[0] != '#') || HTMLPlugInImageElement::isURLAttribute(attribute); } const AtomicString& HTMLObjectElement::imageSourceURL() const { return fastGetAttribute(dataAttr); } void HTMLObjectElement::renderFallbackContent() { if (useFallbackContent()) return; if (!inDocument()) return; setNeedsStyleRecalc(ReconstructRenderTree); // Before we give up and use fallback content, check to see if this is a MIME type issue. auto* loader = imageLoader(); if (loader && loader->image() && loader->image()->status() != CachedResource::LoadError) { m_serviceType = loader->image()->response().mimeType(); if (!isImageType()) { // If we don't think we have an image type anymore, then clear the image from the loader. loader->clearImage(); return; } } m_useFallbackContent = true; // This was added to keep Acid 2 non-flaky. A style recalc is required to make fallback resources load. // Without forcing, this may happen after all the other resources have been loaded and the document is already // considered complete. FIXME: Would be better to address this with incrementLoadEventDelayCount instead // or disentangle loading from style entirely. document().updateStyleIfNeeded(); } // FIXME: This should be removed, all callers are almost certainly wrong. static bool isRecognizedTagName(const QualifiedName& tagName) { static NeverDestroyed> tagList; if (tagList.get().isEmpty()) { auto* tags = HTMLNames::getHTMLTags(); for (size_t i = 0; i < HTMLNames::HTMLTagsCount; i++) { if (*tags[i] == bgsoundTag || *tags[i] == commandTag || *tags[i] == detailsTag || *tags[i] == figcaptionTag || *tags[i] == figureTag || *tags[i] == summaryTag || *tags[i] == trackTag) { // Even though we have atoms for these tags, we don't want to // treat them as "recognized tags" for the purpose of parsing // because that changes how we parse documents. continue; } tagList.get().add(tags[i]->localName().impl()); } } return tagList.get().contains(tagName.localName().impl()); } void HTMLObjectElement::updateDocNamedItem() { // The rule is " elements with no children other than // elements, unknown elements and whitespace can be // found by name in a document, and other elements cannot." bool wasNamedItem = m_docNamedItem; bool isNamedItem = true; Node* child = firstChild(); while (child && isNamedItem) { if (is(*child)) { Element& element = downcast(*child); // FIXME: Use of isRecognizedTagName is almost certainly wrong here. if (isRecognizedTagName(element.tagQName()) && !element.hasTagName(paramTag)) isNamedItem = false; } else if (is(*child)) { if (!downcast(*child).containsOnlyWhitespace()) isNamedItem = false; } else isNamedItem = false; child = child->nextSibling(); } if (isNamedItem != wasNamedItem && inDocument() && is(document())) { HTMLDocument& document = downcast(this->document()); const AtomicString& id = getIdAttribute(); if (!id.isEmpty()) { if (isNamedItem) document.addDocumentNamedItem(*id.impl(), *this); else document.removeDocumentNamedItem(*id.impl(), *this); } const AtomicString& name = getNameAttribute(); if (!name.isEmpty() && id != name) { if (isNamedItem) document.addDocumentNamedItem(*name.impl(), *this); else document.removeDocumentNamedItem(*name.impl(), *this); } } m_docNamedItem = isNamedItem; } bool HTMLObjectElement::containsJavaApplet() const { if (MIMETypeRegistry::isJavaAppletMIMEType(getAttribute(typeAttr))) return true; for (auto& child : childrenOfType(*this)) { if (child.hasTagName(paramTag) && equalLettersIgnoringASCIICase(child.getNameAttribute(), "type") && MIMETypeRegistry::isJavaAppletMIMEType(child.getAttribute(valueAttr).string())) return true; if (child.hasTagName(objectTag) && downcast(child).containsJavaApplet()) return true; if (child.hasTagName(appletTag)) return true; } return false; } void HTMLObjectElement::addSubresourceAttributeURLs(ListHashSet& urls) const { HTMLPlugInImageElement::addSubresourceAttributeURLs(urls); addSubresourceURL(urls, document().completeURL(fastGetAttribute(dataAttr))); // FIXME: Passing a string that starts with "#" to the completeURL function does // not seem like it would work. The image element has similar but not identical code. const AtomicString& useMap = fastGetAttribute(usemapAttr); if (useMap.startsWith('#')) addSubresourceURL(urls, document().completeURL(useMap)); } void HTMLObjectElement::didMoveToNewDocument(Document* oldDocument) { FormAssociatedElement::didMoveToNewDocument(oldDocument); HTMLPlugInImageElement::didMoveToNewDocument(oldDocument); } bool HTMLObjectElement::appendFormData(FormDataList& encoding, bool) { if (name().isEmpty()) return false; Widget* widget = pluginWidget(); if (!is(widget)) return false; String value; if (!downcast(*widget).getFormValue(value)) return false; encoding.appendData(name(), value); return true; } HTMLFormElement* HTMLObjectElement::virtualForm() const { return FormAssociatedElement::form(); } bool HTMLObjectElement::canContainRangeEndPoint() const { // Call through to HTMLElement because we need to skip HTMLPlugInElement // when calling through to the derived class since returns false unconditionally. // An object element with fallback content should basically be treated like // a generic HTML element. return m_useFallbackContent && HTMLElement::canContainRangeEndPoint(); } }