| /* |
| * 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-2017 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 "DOMFormData.h" |
| #include "ElementIterator.h" |
| #include "Frame.h" |
| #include "FrameLoader.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 <wtf/IsoMallocInlines.h> |
| #include <wtf/Ref.h> |
| |
| #if PLATFORM(IOS_FAMILY) |
| #include "RuntimeApplicationChecks.h" |
| #include <wtf/spi/darwin/dyldSPI.h> |
| #endif |
| |
| namespace WebCore { |
| |
| WTF_MAKE_ISO_ALLOCATED_IMPL(HTMLObjectElement); |
| |
| using namespace HTMLNames; |
| |
| inline HTMLObjectElement::HTMLObjectElement(const QualifiedName& tagName, Document& document, HTMLFormElement* form) |
| : HTMLPlugInImageElement(tagName, document) |
| , FormAssociatedElement(form) |
| { |
| ASSERT(hasTagName(objectTag)); |
| } |
| |
| Ref<HTMLObjectElement> HTMLObjectElement::create(const QualifiedName& tagName, Document& document, HTMLFormElement* form) |
| { |
| auto result = adoptRef(*new HTMLObjectElement(tagName, document, form)); |
| result->finishCreating(); |
| return result; |
| } |
| |
| RenderWidget* HTMLObjectElement::renderWidgetLoadingPlugin() const |
| { |
| // Needs to load the plugin immediatedly because this function is called |
| // when JavaScript code accesses the plugin. |
| // FIXME: <rdar://16893708> 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. |
| } |
| |
| 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 = !hasAttributeWithoutSynchronization(classidAttr); |
| setNeedsWidgetUpdate(true); |
| } else if (name == dataAttr) { |
| m_url = stripLeadingAndTrailingHTMLSpaces(value); |
| invalidateRenderer = !hasAttributeWithoutSynchronization(classidAttr); |
| setNeedsWidgetUpdate(true); |
| updateImageLoaderWithNewURLSoon(); |
| } else if (name == classidAttr) { |
| invalidateRenderer = true; |
| setNeedsWidgetUpdate(true); |
| } else |
| HTMLPlugInImageElement::parseAttribute(name, value); |
| |
| if (!invalidateRenderer || !isConnected() || !renderer()) |
| return; |
| |
| m_useFallbackContent = false; |
| scheduleUpdateForAfterStyleResolution(); |
| invalidateStyleAndRenderersForSubtree(); |
| } |
| |
| static void mapDataParamToSrc(Vector<String>& paramNames, Vector<String>& 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("src"_s); |
| paramValues.append(WTFMove(dataParamValue)); |
| } |
| } |
| |
| // FIXME: This function should not deal with url or serviceType! |
| void HTMLObjectElement::parametersForPlugin(Vector<String>& paramNames, Vector<String>& paramValues, String& url, String& serviceType) |
| { |
| HashSet<StringImpl*, ASCIICaseInsensitiveHash> 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<HTMLParamElement>(*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"))) |
| 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 <http://java.sun.com/products/plugin/1.2/docs/tags.html>. 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 <object> element into arrays, but don't override <param> 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 (url.isEmpty() && !urlParameter.isEmpty()) { |
| SubframeLoader& loader = document().frame()->loader().subframeLoader(); |
| if (loader.resourceWillUsePlugin(urlParameter, serviceType)) |
| url = urlParameter; |
| } |
| } |
| |
| bool HTMLObjectElement::hasFallbackContent() const |
| { |
| for (RefPtr<Node> child = firstChild(); child; child = child->nextSibling()) { |
| // Ignore whitespace-only text, and <param> tags, any other content is fallback content. |
| if (is<Text>(*child)) { |
| if (!downcast<Text>(*child).data().isAllSpecialCharacters<isHTMLSpace>()) |
| return true; |
| } else if (!is<HTMLParamElement>(*child)) |
| return true; |
| } |
| return false; |
| } |
| |
| bool HTMLObjectElement::hasValidClassId() |
| { |
| if (MIMETypeRegistry::isJavaAppletMIMEType(serviceType()) && protocolIs(attributeWithoutSynchronization(classidAttr), "java")) |
| 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 attributeWithoutSynchronization(classidAttr).isEmpty(); |
| } |
| |
| // FIXME: This should be unified with HTMLEmbedElement::updateWidget and |
| // moved down into HTMLPluginImageElement.cpp |
| void HTMLObjectElement::updateWidget(CreatePlugins createPlugins) |
| { |
| ASSERT(!renderEmbeddedObject()->isPluginUnavailable()); |
| ASSERT(needsWidgetUpdate()); |
| |
| // FIXME: This should ASSERT isFinishedParsingChildren() instead. |
| if (!isFinishedParsingChildren()) { |
| setNeedsWidgetUpdate(false); |
| 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)) { |
| setNeedsWidgetUpdate(false); |
| return; |
| } |
| |
| String url = this->url(); |
| String serviceType = this->serviceType(); |
| |
| // FIXME: These should be joined into a PluginParameters class. |
| Vector<String> paramNames; |
| Vector<String> paramValues; |
| parametersForPlugin(paramNames, paramValues, url, serviceType); |
| |
| // Note: url is modified above by parametersForPlugin. |
| if (!allowedToLoadFrameURL(url)) { |
| setNeedsWidgetUpdate(false); |
| 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 (createPlugins == CreatePlugins::No && wouldLoadAsPlugIn(url, serviceType)) |
| return; |
| |
| setNeedsWidgetUpdate(false); |
| |
| Ref<HTMLObjectElement> protectedThis(*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() && allowedToLoadFrameURL(url); |
| if (success) |
| success = requestObject(url, serviceType, paramNames, paramValues); |
| if (!success && hasFallbackContent()) |
| renderFallbackContent(); |
| } |
| |
| Node::InsertedIntoAncestorResult HTMLObjectElement::insertedIntoAncestor(InsertionType insertionType, ContainerNode& parentOfInsertedTree) |
| { |
| HTMLPlugInImageElement::insertedIntoAncestor(insertionType, parentOfInsertedTree); |
| FormAssociatedElement::insertedIntoAncestor(insertionType, parentOfInsertedTree); |
| return InsertedIntoAncestorResult::NeedsPostInsertionCallback; |
| } |
| |
| void HTMLObjectElement::didFinishInsertingNode() |
| { |
| resetFormOwner(); |
| } |
| |
| void HTMLObjectElement::removedFromAncestor(RemovalType removalType, ContainerNode& oldParentOfRemovedTree) |
| { |
| HTMLPlugInImageElement::removedFromAncestor(removalType, oldParentOfRemovedTree); |
| FormAssociatedElement::removedFromAncestor(removalType, oldParentOfRemovedTree); |
| } |
| |
| void HTMLObjectElement::childrenChanged(const ChildChange& change) |
| { |
| updateExposedState(); |
| if (isConnected() && !m_useFallbackContent) { |
| setNeedsWidgetUpdate(true); |
| scheduleUpdateForAfterStyleResolution(); |
| invalidateStyleForSubtree(); |
| } |
| HTMLPlugInImageElement::childrenChanged(change); |
| } |
| |
| bool HTMLObjectElement::isURLAttribute(const Attribute& attribute) const |
| { |
| return attribute.name() == dataAttr || attribute.name() == codebaseAttr || (attribute.name() == usemapAttr && attribute.value().string()[0] != '#') || HTMLPlugInImageElement::isURLAttribute(attribute); |
| } |
| |
| const AtomicString& HTMLObjectElement::imageSourceURL() const |
| { |
| return attributeWithoutSynchronization(dataAttr); |
| } |
| |
| void HTMLObjectElement::renderFallbackContent() |
| { |
| if (m_useFallbackContent) |
| return; |
| |
| if (!isConnected()) |
| return; |
| |
| scheduleUpdateForAfterStyleResolution(); |
| invalidateStyleAndRenderersForSubtree(); |
| |
| // 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; |
| } |
| |
| static inline bool preventsParentObjectFromExposure(const Element& child) |
| { |
| static const auto mostKnownTags = makeNeverDestroyed([] { |
| HashSet<QualifiedName> set; |
| auto* tags = HTMLNames::getHTMLTags(); |
| for (size_t i = 0; i < HTMLNames::HTMLTagsCount; i++) { |
| auto& tag = *tags[i]; |
| // Only the param element was explicitly mentioned in the HTML specification rule |
| // we were trying to implement, but these are other known HTML elements that we |
| // have decided, over the years, to treat as children that do not prevent object |
| // names from being exposed. |
| if (tag == bgsoundTag |
| || tag == commandTag |
| || tag == detailsTag |
| || tag == figcaptionTag |
| || tag == figureTag |
| || tag == paramTag |
| || tag == summaryTag |
| || tag == trackTag) |
| continue; |
| set.add(tag); |
| } |
| return set; |
| }()); |
| return mostKnownTags.get().contains(child.tagQName()); |
| } |
| |
| static inline bool preventsParentObjectFromExposure(const Node& child) |
| { |
| if (is<Element>(child)) |
| return preventsParentObjectFromExposure(downcast<Element>(child)); |
| if (is<Text>(child)) |
| return !downcast<Text>(child).data().isAllSpecialCharacters<isHTMLSpace>(); |
| return true; |
| } |
| |
| static inline bool shouldBeExposed(const HTMLObjectElement& element) |
| { |
| // FIXME: This should be redone to use the concept of an exposed object element, |
| // as documented in the HTML specification section describing DOM tree accessors. |
| |
| // The rule we try to implement here, from older HTML specifications, is "object elements |
| // with no children other than param elements, unknown elements and whitespace can be found |
| // by name in a document, and other object elements cannot". |
| |
| for (auto child = makeRefPtr(element.firstChild()); child; child = child->nextSibling()) { |
| if (preventsParentObjectFromExposure(*child)) |
| return false; |
| } |
| return true; |
| } |
| |
| void HTMLObjectElement::updateExposedState() |
| { |
| bool wasExposed = std::exchange(m_isExposed, shouldBeExposed(*this)); |
| |
| if (m_isExposed != wasExposed && isConnected() && !isInShadowTree() && is<HTMLDocument>(document())) { |
| auto& document = downcast<HTMLDocument>(this->document()); |
| |
| auto& id = getIdAttribute(); |
| if (!id.isEmpty()) { |
| if (m_isExposed) |
| document.addDocumentNamedItem(*id.impl(), *this); |
| else |
| document.removeDocumentNamedItem(*id.impl(), *this); |
| } |
| |
| auto& name = getNameAttribute(); |
| if (!name.isEmpty() && id != name) { |
| if (m_isExposed) |
| document.addDocumentNamedItem(*name.impl(), *this); |
| else |
| document.removeDocumentNamedItem(*name.impl(), *this); |
| } |
| } |
| } |
| |
| bool HTMLObjectElement::containsJavaApplet() const |
| { |
| if (MIMETypeRegistry::isJavaAppletMIMEType(attributeWithoutSynchronization(typeAttr))) |
| return true; |
| |
| for (auto& child : childrenOfType<Element>(*this)) { |
| if (child.hasTagName(paramTag) && equalLettersIgnoringASCIICase(child.getNameAttribute(), "type") |
| && MIMETypeRegistry::isJavaAppletMIMEType(child.attributeWithoutSynchronization(valueAttr).string())) |
| return true; |
| if (child.hasTagName(objectTag) && downcast<HTMLObjectElement>(child).containsJavaApplet()) |
| return true; |
| if (child.hasTagName(appletTag)) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void HTMLObjectElement::addSubresourceAttributeURLs(ListHashSet<URL>& urls) const |
| { |
| HTMLPlugInImageElement::addSubresourceAttributeURLs(urls); |
| |
| addSubresourceURL(urls, document().completeURL(attributeWithoutSynchronization(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 = attributeWithoutSynchronization(usemapAttr); |
| if (useMap.startsWith('#')) |
| addSubresourceURL(urls, document().completeURL(useMap)); |
| } |
| |
| void HTMLObjectElement::didMoveToNewDocument(Document& oldDocument, Document& newDocument) |
| { |
| FormAssociatedElement::didMoveToNewDocument(oldDocument); |
| HTMLPlugInImageElement::didMoveToNewDocument(oldDocument, newDocument); |
| } |
| |
| bool HTMLObjectElement::appendFormData(DOMFormData& formData, bool) |
| { |
| if (name().isEmpty()) |
| return false; |
| |
| // Use PluginLoadingPolicy::DoNotLoad here or it would fire JS events synchronously |
| // which would not be safe here. |
| auto widget = makeRefPtr(pluginWidget(PluginLoadingPolicy::DoNotLoad)); |
| if (!is<PluginViewBase>(widget)) |
| return false; |
| String value; |
| if (!downcast<PluginViewBase>(*widget).getFormValue(value)) |
| return false; |
| formData.append(name(), value); |
| return true; |
| } |
| |
| bool HTMLObjectElement::canContainRangeEndPoint() const |
| { |
| // Call through to HTMLElement because HTMLPlugInElement::canContainRangeEndPoint |
| // returns false unconditionally. An object element using fallback content is |
| // treated like a generic HTML element. |
| return m_useFallbackContent && HTMLElement::canContainRangeEndPoint(); |
| } |
| |
| } |