HTMLObjectElement.cpp [plain text]
#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"
#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;
}
HTMLObjectElement::~HTMLObjectElement()
{
clearForm();
}
int HTMLObjectElement::defaultTabIndex() const
{
return 0;
}
bool HTMLObjectElement::isPresentationAttribute(const QualifiedName& name) const
{
if (name == borderAttr)
return true;
return HTMLPlugInImageElement::isPresentationAttribute(name);
}
void HTMLObjectElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomString& value, MutableStyleProperties& style)
{
if (name == borderAttr)
applyBorderAttributeToStyle(value, style);
else
HTMLPlugInImageElement::collectStyleForPresentationAttribute(name, value, style);
}
void HTMLObjectElement::parseAttribute(const QualifiedName& name, const AtomString& 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)
{
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));
}
}
void HTMLObjectElement::parametersForPlugin(Vector<String>& paramNames, Vector<String>& paramValues, String& url, String& serviceType)
{
HashSet<StringImpl*, ASCIICaseInsensitiveHash> uniqueParamNames;
String urlParameter;
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());
if (url.isEmpty() && urlParameter.isEmpty() && (equalLettersIgnoringASCIICase(name, "src") || equalLettersIgnoringASCIICase(name, "movie") || equalLettersIgnoringASCIICase(name, "code") || equalLettersIgnoringASCIICase(name, "url")))
urlParameter = stripLeadingAndTrailingHTMLSpaces(param.value());
if (serviceType.isEmpty() && equalLettersIgnoringASCIICase(name, "type")) {
serviceType = param.value();
size_t pos = serviceType.find(';');
if (pos != notFound)
serviceType = serviceType.left(pos);
}
}
String codebase;
if (MIMETypeRegistry::isJavaAppletMIMEType(serviceType)) {
codebase = "codebase";
uniqueParamNames.add(codebase.impl()); }
if (hasAttributes()) {
for (const Attribute& attribute : attributesIterator()) {
const AtomString& name = attribute.name().localName();
if (!uniqueParamNames.contains(name.impl())) {
paramNames.append(name.string());
paramValues.append(attribute.value().string());
}
}
}
mapDataParamToSrc(paramNames, paramValues);
if (url.isEmpty() && !urlParameter.isEmpty()) {
auto& 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()) {
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;
return attributeWithoutSynchronization(classidAttr).isEmpty();
}
void HTMLObjectElement::updateWidget(CreatePlugins createPlugins)
{
ASSERT(!renderEmbeddedObject()->isPluginUnavailable());
ASSERT(needsWidgetUpdate());
if (!isFinishedParsingChildren()) {
setNeedsWidgetUpdate(false);
return;
}
if (!SubframeLoadingDisabler::canLoadFrame(*this)) {
setNeedsWidgetUpdate(false);
return;
}
String url = this->url();
String serviceType = this->serviceType();
Vector<String> paramNames;
Vector<String> paramValues;
parametersForPlugin(paramNames, paramValues, url, serviceType);
if (!canLoadURL(url)) {
setNeedsWidgetUpdate(false);
return;
}
if (createPlugins == CreatePlugins::No && wouldLoadAsPlugIn(url, serviceType))
return;
setNeedsWidgetUpdate(false);
Ref<HTMLObjectElement> protectedThis(*this); bool beforeLoadAllowedLoad = guardedDispatchBeforeLoadEvent(url);
if (!renderer()) return;
bool success = beforeLoadAllowedLoad && hasValidClassId() && canLoadURL(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);
}
bool HTMLObjectElement::isInteractiveContent() const
{
return hasAttributeWithoutSynchronization(usemapAttr);
}
const AtomString& HTMLObjectElement::imageSourceURL() const
{
return attributeWithoutSynchronization(dataAttr);
}
void HTMLObjectElement::renderFallbackContent()
{
if (m_useFallbackContent)
return;
if (!isConnected())
return;
scheduleUpdateForAfterStyleResolution();
invalidateStyleAndRenderersForSubtree();
auto* loader = imageLoader();
if (loader && loader->image() && loader->image()->status() != CachedResource::LoadError) {
m_serviceType = loader->image()->response().mimeType();
if (!isImageType()) {
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];
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)
{
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)));
const AtomString& 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;
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
{
return m_useFallbackContent && HTMLElement::canContainRangeEndPoint();
}
}