blob: 5b800502911ef19ac2700ccd29823353c884e0d2 [file] [log] [blame]
/*
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 1999 Antti Koivisto (koivisto@kde.org)
* (C) 2000 Simon Hausmann (hausmann@kde.org)
* (C) 2001 Dirk Mueller (mueller@kde.org)
* Copyright (C) 2004, 2006, 2008, 2009 Apple Inc. All rights reserved.
* Copyright (C) 2009 Ericsson AB. All rights reserved.
*
* 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_iframe_element.h"
#include "base/metrics/histogram_macros.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/web_sandbox_flags.h"
#include "services/network/public/mojom/trust_tokens.mojom-blink.h"
#include "services/network/public/mojom/web_sandbox_flags.mojom-blink.h"
#include "third_party/blink/public/mojom/feature_policy/feature_policy.mojom-blink.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_html_iframe_element.h"
#include "third_party/blink/renderer/core/css/css_property_names.h"
#include "third_party/blink/renderer/core/css/style_change_reason.h"
#include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/feature_policy/document_policy_parser.h"
#include "third_party/blink/renderer/core/feature_policy/feature_policy_parser.h"
#include "third_party/blink/renderer/core/feature_policy/iframe_policy.h"
#include "third_party/blink/renderer/core/fetch/trust_token_issuance_authorization.h"
#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
#include "third_party/blink/renderer/core/html/html_document.h"
#include "third_party/blink/renderer/core/html/trust_token_attribute_parsing.h"
#include "third_party/blink/renderer/core/html_names.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/layout/layout_iframe.h"
#include "third_party/blink/renderer/core/loader/document_loader.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
#include "third_party/blink/renderer/platform/json/json_parser.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
namespace blink {
HTMLIFrameElement::HTMLIFrameElement(Document& document)
: HTMLFrameElementBase(html_names::kIFrameTag, document),
collapsed_by_client_(false),
sandbox_(MakeGarbageCollected<HTMLIFrameElementSandbox>(this)),
referrer_policy_(network::mojom::ReferrerPolicy::kDefault) {}
void HTMLIFrameElement::Trace(Visitor* visitor) const {
visitor->Trace(sandbox_);
visitor->Trace(policy_);
HTMLFrameElementBase::Trace(visitor);
Supplementable<HTMLIFrameElement>::Trace(visitor);
}
HTMLIFrameElement::~HTMLIFrameElement() = default;
const AttrNameToTrustedType& HTMLIFrameElement::GetCheckedAttributeTypes()
const {
DEFINE_STATIC_LOCAL(AttrNameToTrustedType, attribute_map,
({{"srcdoc", SpecificTrustedType::kHTML}}));
return attribute_map;
}
void HTMLIFrameElement::SetCollapsed(bool collapse) {
if (collapsed_by_client_ == collapse)
return;
collapsed_by_client_ = collapse;
// This is always called in response to an IPC, so should not happen in the
// middle of a style recalc.
DCHECK(!GetDocument().InStyleRecalc());
// Trigger style recalc to trigger layout tree re-attachment.
SetNeedsStyleRecalc(kLocalStyleChange, StyleChangeReasonForTracing::Create(
style_change_reason::kFrame));
}
DOMTokenList* HTMLIFrameElement::sandbox() const {
return sandbox_.Get();
}
DOMFeaturePolicy* HTMLIFrameElement::featurePolicy() {
if (!policy_ && GetExecutionContext()) {
policy_ = MakeGarbageCollected<IFramePolicy>(
GetExecutionContext(), GetFramePolicy().container_policy,
GetOriginForFeaturePolicy());
}
return policy_.Get();
}
bool HTMLIFrameElement::IsPresentationAttribute(
const QualifiedName& name) const {
if (name == html_names::kWidthAttr || name == html_names::kHeightAttr ||
name == html_names::kAlignAttr || name == html_names::kFrameborderAttr)
return true;
return HTMLFrameElementBase::IsPresentationAttribute(name);
}
void HTMLIFrameElement::CollectStyleForPresentationAttribute(
const QualifiedName& name,
const AtomicString& value,
MutableCSSPropertyValueSet* style) {
if (name == html_names::kWidthAttr) {
AddHTMLLengthToStyle(style, CSSPropertyID::kWidth, value);
} else if (name == html_names::kHeightAttr) {
AddHTMLLengthToStyle(style, CSSPropertyID::kHeight, value);
} else if (name == html_names::kAlignAttr) {
ApplyAlignmentAttributeToStyle(value, style);
} else if (name == html_names::kFrameborderAttr) {
// LocalFrame border doesn't really match the HTML4 spec definition for
// iframes. It simply adds a presentational hint that the border should be
// off if set to zero.
if (!value.ToInt()) {
// Add a rule that nulls out our border width.
AddPropertyToPresentationAttributeStyle(
style, CSSPropertyID::kBorderWidth, 0,
CSSPrimitiveValue::UnitType::kPixels);
}
} else {
HTMLFrameElementBase::CollectStyleForPresentationAttribute(name, value,
style);
}
}
void HTMLIFrameElement::ParseAttribute(
const AttributeModificationParams& params) {
const QualifiedName& name = params.name;
const AtomicString& value = params.new_value;
if (name == html_names::kNameAttr) {
auto* document = DynamicTo<HTMLDocument>(GetDocument());
if (document && IsInDocumentTree()) {
document->RemoveNamedItem(name_);
document->AddNamedItem(value);
}
AtomicString old_name = name_;
name_ = value;
if (name_ != old_name)
FrameOwnerPropertiesChanged();
} else if (name == html_names::kSandboxAttr) {
sandbox_->DidUpdateAttributeValue(params.old_value, value);
network::mojom::blink::WebSandboxFlags current_flags =
network::mojom::blink::WebSandboxFlags::kNone;
if (!value.IsNull()) {
using network::mojom::blink::WebSandboxFlags;
WebSandboxFlags ignored_flags =
!RuntimeEnabledFeatures::StorageAccessAPIEnabled()
? WebSandboxFlags::kStorageAccessByUserActivation
: WebSandboxFlags::kNone;
auto parsed = network::ParseWebSandboxPolicy(sandbox_->value().Utf8(),
ignored_flags);
current_flags = parsed.flags;
if (!parsed.error_message.empty()) {
GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::blink::ConsoleMessageSource::kOther,
mojom::blink::ConsoleMessageLevel::kError,
WebString::FromUTF8(
"Error while parsing the 'sandbox' attribute: " +
parsed.error_message)));
}
}
SetSandboxFlags(current_flags);
UseCounter::Count(GetDocument(), WebFeature::kSandboxViaIFrame);
} else if (name == html_names::kReferrerpolicyAttr) {
referrer_policy_ = network::mojom::ReferrerPolicy::kDefault;
if (!value.IsNull()) {
SecurityPolicy::ReferrerPolicyFromString(
value, kSupportReferrerPolicyLegacyKeywords, &referrer_policy_);
UseCounter::Count(GetDocument(),
WebFeature::kHTMLIFrameElementReferrerPolicyAttribute);
}
} else if (name == html_names::kAllowfullscreenAttr) {
bool old_allow_fullscreen = allow_fullscreen_;
allow_fullscreen_ = !value.IsNull();
if (allow_fullscreen_ != old_allow_fullscreen) {
// TODO(iclelland): Remove this use counter when the allowfullscreen
// attribute state is snapshotted on document creation. crbug.com/682282
if (allow_fullscreen_ && ContentFrame()) {
UseCounter::Count(
GetDocument(),
WebFeature::
kHTMLIFrameElementAllowfullscreenAttributeSetAfterContentLoad);
}
FrameOwnerPropertiesChanged();
UpdateContainerPolicy();
}
} else if (name == html_names::kAllowpaymentrequestAttr) {
bool old_allow_payment_request = allow_payment_request_;
allow_payment_request_ = !value.IsNull();
if (allow_payment_request_ != old_allow_payment_request) {
FrameOwnerPropertiesChanged();
UpdateContainerPolicy();
}
} else if (name == html_names::kCspAttr) {
if (value && (value.Contains('\n') || value.Contains('\r') ||
!MatchesTheSerializedCSPGrammar(value.GetString()))) {
required_csp_ = g_null_atom;
GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::blink::ConsoleMessageSource::kOther,
mojom::blink::ConsoleMessageLevel::kError,
"'csp' attribute is invalid: " + value));
return;
}
if (required_csp_ != value) {
required_csp_ = value;
CSPAttributeChanged();
UseCounter::Count(GetDocument(), WebFeature::kIFrameCSPAttribute);
}
} else if (name == html_names::kAllowAttr) {
if (allow_ != value) {
allow_ = value;
UpdateContainerPolicy();
if (!value.IsEmpty()) {
UseCounter::Count(GetDocument(),
WebFeature::kFeaturePolicyAllowAttribute);
}
}
} else if (name == html_names::kDisallowdocumentaccessAttr &&
RuntimeEnabledFeatures::DisallowDocumentAccessEnabled()) {
UseCounter::Count(GetDocument(), WebFeature::kDisallowDocumentAccess);
SetDisallowDocumentAccesss(!value.IsNull());
// We don't need to call tell the client frame properties
// changed since this attribute only stays inside the renderer.
} else if (name == html_names::kPolicyAttr) {
if (required_policy_ != value) {
required_policy_ = value;
UpdateRequiredPolicy();
}
} else if (name == html_names::kTrusttokenAttr) {
UseCounter::Count(GetDocument(), WebFeature::kTrustTokenIframe);
trust_token_ = value;
} else {
// Websites picked up a Chromium article that used this non-specified
// attribute which ended up changing shape after the specification process.
// This error message and use count will help developers to move to the
// proper solution.
// To avoid polluting the console, this is being recorded only once per
// page.
if (name == "gesture" && value == "media" && GetDocument().Loader() &&
!GetDocument().Loader()->GetUseCounterHelper().HasRecordedMeasurement(
WebFeature::kHTMLIFrameElementGestureMedia)) {
UseCounter::Count(GetDocument(),
WebFeature::kHTMLIFrameElementGestureMedia);
GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::ConsoleMessageSource::kOther,
mojom::ConsoleMessageLevel::kWarning,
"<iframe gesture=\"media\"> is not supported. "
"Use <iframe allow=\"autoplay\">, "
"https://goo.gl/ximf56"));
}
if (name == html_names::kSrcAttr)
LogUpdateAttributeIfIsolatedWorldAndInDocument("iframe", params);
HTMLFrameElementBase::ParseAttribute(params);
}
}
DocumentPolicyFeatureState HTMLIFrameElement::ConstructRequiredPolicy() const {
if (!RuntimeEnabledFeatures::DocumentPolicyNegotiationEnabled(
GetExecutionContext()))
return {};
if (!required_policy_.IsEmpty()) {
UseCounter::Count(
GetDocument(),
mojom::blink::WebFeature::kDocumentPolicyIframePolicyAttribute);
}
PolicyParserMessageBuffer logger;
DocumentPolicy::ParsedDocumentPolicy new_required_policy =
DocumentPolicyParser::Parse(required_policy_, logger)
.value_or(DocumentPolicy::ParsedDocumentPolicy{});
for (const auto& message : logger.GetMessages()) {
GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::blink::ConsoleMessageSource::kOther, message.level,
message.content));
}
if (!new_required_policy.endpoint_map.empty()) {
GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::blink::ConsoleMessageSource::kOther,
mojom::blink::ConsoleMessageLevel::kWarning,
"Iframe policy attribute cannot specify reporting endpoint."));
}
for (const auto& policy_entry : new_required_policy.feature_state) {
mojom::blink::DocumentPolicyFeature feature = policy_entry.first;
if (!GetDocument().DocumentPolicyFeatureObserved(feature)) {
UMA_HISTOGRAM_ENUMERATION(
"Blink.UseCounter.DocumentPolicy.PolicyAttribute", feature);
}
}
return new_required_policy.feature_state;
}
ParsedFeaturePolicy HTMLIFrameElement::ConstructContainerPolicy() const {
if (!GetExecutionContext())
return ParsedFeaturePolicy();
scoped_refptr<const SecurityOrigin> src_origin = GetOriginForFeaturePolicy();
scoped_refptr<const SecurityOrigin> self_origin =
GetExecutionContext()->GetSecurityOrigin();
PolicyParserMessageBuffer logger;
// Start with the allow attribute
ParsedFeaturePolicy container_policy = FeaturePolicyParser::ParseAttribute(
allow_, self_origin, src_origin, logger, GetExecutionContext());
// Process the allow* attributes. These only take effect if the corresponding
// feature is not present in the allow attribute's value.
// If allowfullscreen attribute is present and no fullscreen policy is set,
// enable the feature for all origins.
if (AllowFullscreen()) {
bool policy_changed = AllowFeatureEverywhereIfNotPresent(
mojom::blink::FeaturePolicyFeature::kFullscreen, container_policy);
if (!policy_changed) {
logger.Warn(
"Allow attribute will take precedence over 'allowfullscreen'.");
}
}
// If the allowpaymentrequest attribute is present and no 'payment' policy is
// set, enable the feature for all origins.
if (AllowPaymentRequest()) {
bool policy_changed = AllowFeatureEverywhereIfNotPresent(
mojom::blink::FeaturePolicyFeature::kPayment, container_policy);
if (!policy_changed) {
logger.Warn(
"Allow attribute will take precedence over 'allowpaymentrequest'.");
}
}
// Update the JavaScript policy object associated with this iframe, if it
// exists.
if (policy_)
policy_->UpdateContainerPolicy(container_policy, src_origin);
for (const auto& message : logger.GetMessages()) {
GetDocument().AddConsoleMessage(
MakeGarbageCollected<ConsoleMessage>(
mojom::blink::ConsoleMessageSource::kOther, message.level,
message.content),
/* discard_duplicates */ true);
}
return container_policy;
}
bool HTMLIFrameElement::LayoutObjectIsNeeded(const ComputedStyle& style) const {
return ContentFrame() && !collapsed_by_client_ &&
HTMLElement::LayoutObjectIsNeeded(style);
}
LayoutObject* HTMLIFrameElement::CreateLayoutObject(const ComputedStyle&,
LegacyLayout) {
return new LayoutIFrame(this);
}
Node::InsertionNotificationRequest HTMLIFrameElement::InsertedInto(
ContainerNode& insertion_point) {
InsertionNotificationRequest result =
HTMLFrameElementBase::InsertedInto(insertion_point);
auto* html_doc = DynamicTo<HTMLDocument>(GetDocument());
if (html_doc && insertion_point.IsInDocumentTree())
html_doc->AddNamedItem(name_);
LogAddElementIfIsolatedWorldAndInDocument("iframe", html_names::kSrcAttr);
return result;
}
void HTMLIFrameElement::RemovedFrom(ContainerNode& insertion_point) {
HTMLFrameElementBase::RemovedFrom(insertion_point);
auto* html_doc = DynamicTo<HTMLDocument>(GetDocument());
if (html_doc && insertion_point.IsInDocumentTree())
html_doc->RemoveNamedItem(name_);
}
bool HTMLIFrameElement::IsInteractiveContent() const {
return true;
}
network::mojom::ReferrerPolicy HTMLIFrameElement::ReferrerPolicyAttribute() {
return referrer_policy_;
}
network::mojom::blink::TrustTokenParamsPtr
HTMLIFrameElement::ConstructTrustTokenParams() const {
if (!trust_token_)
return nullptr;
JSONParseError parse_error;
std::unique_ptr<JSONValue> parsed_attribute =
ParseJSON(trust_token_, &parse_error);
if (!parsed_attribute) {
GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::blink::ConsoleMessageSource::kOther,
mojom::blink::ConsoleMessageLevel::kError,
"iframe trusttoken attribute was invalid JSON: " + parse_error.message +
String::Format(" (line %d, col %d)", parse_error.line,
parse_error.column)));
return nullptr;
}
network::mojom::blink::TrustTokenParamsPtr parsed_params =
internal::TrustTokenParamsFromJson(std::move(parsed_attribute));
if (!parsed_params) {
GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::blink::ConsoleMessageSource::kOther,
mojom::blink::ConsoleMessageLevel::kError,
"Couldn't parse iframe trusttoken attribute (was it missing a "
"field?)"));
return nullptr;
}
// Trust token redemption and signing (but not issuance) require that the
// trust-token-redemption feature policy be present.
bool operation_requires_feature_policy =
parsed_params->type ==
network::mojom::blink::TrustTokenOperationType::kRedemption ||
parsed_params->type ==
network::mojom::blink::TrustTokenOperationType::kSigning;
if (operation_requires_feature_policy &&
(!GetExecutionContext()->IsFeatureEnabled(
mojom::blink::FeaturePolicyFeature::kTrustTokenRedemption))) {
GetExecutionContext()->AddConsoleMessage(
MakeGarbageCollected<ConsoleMessage>(
mojom::blink::ConsoleMessageSource::kOther,
mojom::blink::ConsoleMessageLevel::kError,
"Trust Tokens: Attempted redemption or signing without the "
"trust-token-redemption Feature Policy feature present."));
return nullptr;
}
if (parsed_params->type ==
network::mojom::blink::TrustTokenOperationType::kIssuance &&
!IsTrustTokenIssuanceAvailableInExecutionContext(
*GetExecutionContext())) {
GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::blink::ConsoleMessageSource::kOther,
mojom::blink::ConsoleMessageLevel::kError,
"Trust Tokens issuance is disabled except in "
"contexts with the TrustTokens Origin Trial enabled."));
return nullptr;
}
return parsed_params;
}
} // namespace blink