blob: 09ba3a516a7676118d0c1c97cc9136ccd8c24827 [file] [log] [blame]
/*
* 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 "third_party/blink/renderer/core/html/html_object_element.h"
#include "third_party/blink/renderer/bindings/core/v8/script_event_listener.h"
#include "third_party/blink/renderer/core/dom/attribute.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/element_traversal.h"
#include "third_party/blink/renderer/core/dom/shadow_root.h"
#include "third_party/blink/renderer/core/dom/tag_collection.h"
#include "third_party/blink/renderer/core/dom/text.h"
#include "third_party/blink/renderer/core/exported/web_plugin_container_impl.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/local_frame_client.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/html/html_image_loader.h"
#include "third_party/blink/renderer/core/html/html_meta_element.h"
#include "third_party/blink/renderer/core/html/html_param_element.h"
#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h"
#include "third_party/blink/renderer/core/html_names.h"
#include "third_party/blink/renderer/core/layout/layout_embedded_object.h"
#include "third_party/blink/renderer/platform/network/mime/mime_type_registry.h"
namespace blink {
using namespace html_names;
inline HTMLObjectElement::HTMLObjectElement(Document& document,
const CreateElementFlags flags)
: HTMLPlugInElement(kObjectTag,
document,
flags,
kShouldNotPreferPlugInsForImages),
use_fallback_content_(false) {}
inline HTMLObjectElement::~HTMLObjectElement() = default;
HTMLObjectElement* HTMLObjectElement::Create(Document& document,
const CreateElementFlags flags) {
auto* element = MakeGarbageCollected<HTMLObjectElement>(document, flags);
element->EnsureUserAgentShadowRoot();
return element;
}
void HTMLObjectElement::Trace(Visitor* visitor) {
ListedElement::Trace(visitor);
HTMLPlugInElement::Trace(visitor);
}
const AttrNameToTrustedType& HTMLObjectElement::GetCheckedAttributeTypes()
const {
DEFINE_STATIC_LOCAL(AttrNameToTrustedType, attribute_map,
({{"data", SpecificTrustedType::kTrustedScriptURL},
{"codebase", SpecificTrustedType::kTrustedScriptURL}}));
return attribute_map;
}
LayoutEmbeddedContent* HTMLObjectElement::ExistingLayoutEmbeddedContent()
const {
// This will return 0 if the layoutObject is not a LayoutEmbeddedContent.
return GetLayoutEmbeddedContent();
}
bool HTMLObjectElement::IsPresentationAttribute(
const QualifiedName& name) const {
if (name == kBorderAttr)
return true;
return HTMLPlugInElement::IsPresentationAttribute(name);
}
void HTMLObjectElement::CollectStyleForPresentationAttribute(
const QualifiedName& name,
const AtomicString& value,
MutableCSSPropertyValueSet* style) {
if (name == kBorderAttr)
ApplyBorderAttributeToStyle(value, style);
else
HTMLPlugInElement::CollectStyleForPresentationAttribute(name, value, style);
}
void HTMLObjectElement::ParseAttribute(
const AttributeModificationParams& params) {
const QualifiedName& name = params.name;
if (name == kFormAttr) {
FormAttributeChanged();
} else if (name == kTypeAttr) {
SetServiceType(params.new_value.LowerASCII());
wtf_size_t pos = service_type_.Find(";");
if (pos != kNotFound)
SetServiceType(service_type_.Left(pos));
// TODO(schenney): crbug.com/572908 What is the right thing to do here?
// Should we suppress the reload stuff when a persistable widget-type is
// specified?
ReloadPluginOnAttributeChange(name);
} else if (name == kDataAttr) {
SetUrl(StripLeadingAndTrailingHTMLSpaces(params.new_value));
if (GetLayoutObject() && IsImageType()) {
SetNeedsPluginUpdate(true);
if (!image_loader_)
image_loader_ = HTMLImageLoader::Create(this);
image_loader_->UpdateFromElement(ImageLoader::kUpdateIgnorePreviousError);
} else {
ReloadPluginOnAttributeChange(name);
}
} else if (name == kClassidAttr) {
class_id_ = params.new_value;
ReloadPluginOnAttributeChange(name);
} else {
HTMLPlugInElement::ParseAttribute(params);
}
}
static void MapDataParamToSrc(PluginParameters& plugin_params) {
// Some plugins don't understand the "data" attribute of the OBJECT tag (i.e.
// Real and WMP require "src" attribute).
int src_index = plugin_params.FindStringInNames("src");
int data_index = plugin_params.FindStringInNames("data");
if (src_index == -1 && data_index != -1) {
plugin_params.AppendNameWithValue("src",
plugin_params.Values()[data_index]);
}
}
// TODO(schenney): crbug.com/572908 This function should not deal with url or
// serviceType!
void HTMLObjectElement::ParametersForPlugin(PluginParameters& plugin_params) {
HashSet<StringImpl*, CaseFoldingHash> unique_param_names;
// 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 (HTMLParamElement* p = Traversal<HTMLParamElement>::FirstChild(*this); p;
p = Traversal<HTMLParamElement>::NextSibling(*p)) {
String name = p->GetName();
if (name.IsEmpty())
continue;
unique_param_names.insert(name.Impl());
plugin_params.AppendNameWithValue(p->GetName(), p->Value());
// TODO(schenney): crbug.com/572908 url adjustment does not belong in this
// function.
// HTML5 says that an object resource's URL is specified by the object's
// data attribute, not by a param element with a name of "data". However,
// for compatibility, allow the resource's URL to be given by a param
// element with one of the common names if we know that resource points
// to a plugin.
if (url_.IsEmpty() && !DeprecatedEqualIgnoringCase(name, "data") &&
HTMLParamElement::IsURLParameter(name)) {
SetUrl(StripLeadingAndTrailingHTMLSpaces(p->Value()));
}
// TODO(schenney): crbug.com/572908 serviceType calculation does not belong
// in this function.
if (service_type_.IsEmpty() && DeprecatedEqualIgnoringCase(name, "type")) {
wtf_size_t pos = p->Value().Find(";");
if (pos != kNotFound)
SetServiceType(p->Value().GetString().Left(pos));
}
}
// Turn the attributes of the <object> element into arrays, but don't override
// <param> values.
AttributeCollection attributes = Attributes();
for (const Attribute& attribute : attributes) {
const AtomicString& name = attribute.GetName().LocalName();
if (!unique_param_names.Contains(name.Impl()))
plugin_params.AppendAttribute(attribute);
}
MapDataParamToSrc(plugin_params);
}
bool HTMLObjectElement::HasFallbackContent() const {
for (Node* child = firstChild(); child; child = child->nextSibling()) {
// Ignore whitespace-only text, and <param> tags, any other content is
// fallback content.
if (child->IsTextNode()) {
if (!ToText(child)->ContainsOnlyWhitespaceOrEmpty())
return true;
} else if (!IsHTMLParamElement(*child)) {
return true;
}
}
return false;
}
bool HTMLObjectElement::HasValidClassId() const {
if (MIMETypeRegistry::IsJavaAppletMIMEType(service_type_) &&
ClassId().StartsWithIgnoringASCIICase("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 plugin.
return ClassId().IsEmpty();
}
void HTMLObjectElement::ReloadPluginOnAttributeChange(
const QualifiedName& name) {
// Following,
// http://www.whatwg.org/specs/web-apps/current-work/#the-object-element
// (Enumerated list below "Whenever one of the following conditions occur:")
//
// the updating of certain attributes should bring about "redetermination"
// of what the element contains.
bool needs_invalidation;
if (name == kTypeAttr) {
needs_invalidation =
!FastHasAttribute(kClassidAttr) && !FastHasAttribute(kDataAttr);
} else if (name == kDataAttr) {
needs_invalidation = !FastHasAttribute(kClassidAttr);
} else if (name == kClassidAttr) {
needs_invalidation = true;
} else {
NOTREACHED();
needs_invalidation = false;
}
SetNeedsPluginUpdate(true);
if (needs_invalidation)
LazyReattachIfNeeded();
}
// TODO(schenney): crbug.com/572908 This should be unified with
// HTMLEmbedElement::UpdatePlugin and moved down into html_plugin_element.cc
void HTMLObjectElement::UpdatePluginInternal() {
DCHECK(!GetLayoutEmbeddedObject()->ShowsUnavailablePluginIndicator());
DCHECK(NeedsPluginUpdate());
SetNeedsPluginUpdate(false);
// TODO(schenney): crbug.com/572908 This should ASSERT
// isFinishedParsingChildren() instead.
if (!IsFinishedParsingChildren()) {
DispatchErrorEvent();
return;
}
// TODO(schenney): crbug.com/572908 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)) {
DispatchErrorEvent();
return;
}
PluginParameters plugin_params;
ParametersForPlugin(plugin_params);
// Note: url is modified above by parametersForPlugin.
if (!AllowedToLoadFrameURL(url_)) {
DispatchErrorEvent();
return;
}
// TODO(schenney): crbug.com/572908 Is it possible to get here without a
// layoutObject now that we don't have beforeload events?
if (!GetLayoutObject())
return;
// Overwrites the URL and MIME type of a Flash embed to use an HTML5 embed.
KURL overriden_url =
GetDocument().GetFrame()->Client()->OverrideFlashEmbedWithHTML(
GetDocument().CompleteURL(url_));
if (!overriden_url.IsEmpty()) {
url_ = overriden_url.GetString();
SetServiceType("text/html");
}
if (!HasValidClassId() || !RequestObject(plugin_params)) {
if (!url_.IsEmpty())
DispatchErrorEvent();
if (HasFallbackContent())
RenderFallbackContent(ContentFrame());
} else {
if (IsErrorplaceholder())
DispatchErrorEvent();
}
}
Node::InsertionNotificationRequest HTMLObjectElement::InsertedInto(
ContainerNode& insertion_point) {
HTMLPlugInElement::InsertedInto(insertion_point);
ListedElement::InsertedInto(insertion_point);
return kInsertionDone;
}
void HTMLObjectElement::RemovedFrom(ContainerNode& insertion_point) {
HTMLPlugInElement::RemovedFrom(insertion_point);
ListedElement::RemovedFrom(insertion_point);
}
void HTMLObjectElement::ChildrenChanged(const ChildrenChange& change) {
if (isConnected() && !UseFallbackContent()) {
SetNeedsPluginUpdate(true);
LazyReattachIfNeeded();
}
HTMLPlugInElement::ChildrenChanged(change);
}
bool HTMLObjectElement::IsURLAttribute(const Attribute& attribute) const {
return attribute.GetName() == kCodebaseAttr ||
attribute.GetName() == kDataAttr ||
(attribute.GetName() == kUsemapAttr && attribute.Value()[0] != '#') ||
HTMLPlugInElement::IsURLAttribute(attribute);
}
bool HTMLObjectElement::HasLegalLinkAttribute(const QualifiedName& name) const {
return name == kClassidAttr || name == kDataAttr || name == kCodebaseAttr ||
HTMLPlugInElement::HasLegalLinkAttribute(name);
}
const QualifiedName& HTMLObjectElement::SubResourceAttributeName() const {
return kDataAttr;
}
const AtomicString HTMLObjectElement::ImageSourceURL() const {
return getAttribute(kDataAttr);
}
void HTMLObjectElement::ReattachFallbackContent() {
if (!GetDocument().InStyleRecalc())
LazyReattachIfAttached();
}
void HTMLObjectElement::RenderFallbackContent(Frame* frame) {
DCHECK(!frame || frame == ContentFrame());
if (UseFallbackContent())
return;
if (!isConnected())
return;
// Before we give up and use fallback content, check to see if this is a MIME
// type issue.
if (image_loader_ && image_loader_->GetContent() &&
image_loader_->GetContent()->GetContentStatus() !=
ResourceStatus::kLoadError) {
SetServiceType(image_loader_->GetContent()->GetResponse().MimeType());
if (!IsImageType()) {
// If we don't think we have an image type anymore, then clear the image
// from the loader.
image_loader_->ClearImage();
ReattachFallbackContent();
return;
}
}
use_fallback_content_ = true;
// TODO(schenney): crbug.com/572908 Style gets recalculated which is
// suboptimal.
ReattachFallbackContent();
}
bool HTMLObjectElement::IsExposed() const {
// http://www.whatwg.org/specs/web-apps/current-work/#exposed
for (HTMLObjectElement* ancestor =
Traversal<HTMLObjectElement>::FirstAncestor(*this);
ancestor;
ancestor = Traversal<HTMLObjectElement>::FirstAncestor(*ancestor)) {
if (ancestor->IsExposed())
return false;
}
for (HTMLElement& element : Traversal<HTMLElement>::DescendantsOf(*this)) {
if (IsHTMLObjectElement(element) || IsHTMLEmbedElement(element))
return false;
}
return true;
}
bool HTMLObjectElement::ContainsJavaApplet() const {
if (MIMETypeRegistry::IsJavaAppletMIMEType(getAttribute(kTypeAttr)))
return true;
for (HTMLElement& child : Traversal<HTMLElement>::ChildrenOf(*this)) {
if (IsHTMLParamElement(child) &&
DeprecatedEqualIgnoringCase(child.GetNameAttribute(), "type") &&
MIMETypeRegistry::IsJavaAppletMIMEType(
child.getAttribute(kValueAttr).GetString()))
return true;
if (IsHTMLObjectElement(child) &&
ToHTMLObjectElement(child).ContainsJavaApplet())
return true;
}
return false;
}
void HTMLObjectElement::DidMoveToNewDocument(Document& old_document) {
ListedElement::DidMoveToNewDocument(old_document);
HTMLPlugInElement::DidMoveToNewDocument(old_document);
}
HTMLFormElement* HTMLObjectElement::formOwner() const {
return ListedElement::Form();
}
bool HTMLObjectElement::IsInteractiveContent() const {
return FastHasAttribute(kUsemapAttr);
}
bool HTMLObjectElement::UseFallbackContent() const {
return HTMLPlugInElement::UseFallbackContent() || use_fallback_content_;
}
bool HTMLObjectElement::WillUseFallbackContentAtLayout() const {
return !HasValidClassId() && HasFallbackContent();
}
void HTMLObjectElement::AssociateWith(HTMLFormElement* form) {
AssociateByParser(form);
}
const HTMLObjectElement* ToHTMLObjectElementFromListedElement(
const ListedElement* element) {
SECURITY_DCHECK(!element || !element->IsFormControlElement());
const HTMLObjectElement* object_element =
static_cast<const HTMLObjectElement*>(element);
// We need to assert after the cast because ListedElement doesn't
// have hasTagName.
SECURITY_DCHECK(!object_element ||
object_element->HasTagName(html_names::kObjectTag));
return object_element;
}
const HTMLObjectElement& ToHTMLObjectElementFromListedElement(
const ListedElement& element) {
return *ToHTMLObjectElementFromListedElement(&element);
}
} // namespace blink