blob: 33704e3549a439c496610a4e2262dd2392984bf1 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/renderer_host/browsing_context_state.h"
#include "base/memory/ptr_util.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/site_instance_impl.h"
#include "content/common/content_navigation_policy.h"
#include "services/network/public/cpp/web_sandbox_flags.h"
#include "services/network/public/mojom/web_sandbox_flags.mojom.h"
namespace features {
BASE_FEATURE(kNewBrowsingContextStateOnBrowsingContextGroupSwap,
"NewBrowsingContextStateOnBrowsingContextGroupSwap",
base::FEATURE_DISABLED_BY_DEFAULT);
BrowsingContextStateImplementationType GetBrowsingContextMode() {
if (base::FeatureList::IsEnabled(
kNewBrowsingContextStateOnBrowsingContextGroupSwap)) {
return BrowsingContextStateImplementationType::
kSwapForCrossBrowsingInstanceNavigations;
}
return BrowsingContextStateImplementationType::
kLegacyOneToOneWithFrameTreeNode;
}
} // namespace features
namespace content {
using perfetto::protos::pbzero::ChromeTrackEvent;
BrowsingContextState::BrowsingContextState(
blink::mojom::FrameReplicationStatePtr replication_state,
RenderFrameHostImpl* parent,
absl::optional<BrowsingInstanceId> browsing_instance_id,
absl::optional<base::UnguessableToken> coop_related_group_token)
: replication_state_(std::move(replication_state)),
parent_(parent),
browsing_instance_id_(browsing_instance_id),
coop_related_group_token_(coop_related_group_token) {
TRACE_EVENT_BEGIN("navigation", "BrowsingContextState",
perfetto::Track::FromPointer(this),
"browsing_context_state_when_created", this);
}
BrowsingContextState::~BrowsingContextState() {
TRACE_EVENT_END("navigation", perfetto::Track::FromPointer(this));
}
RenderFrameProxyHost* BrowsingContextState::GetRenderFrameProxyHost(
SiteInstanceGroup* site_instance_group,
ProxyAccessMode proxy_access_mode) const {
TRACE_EVENT_BEGIN("navigation.debug",
"BrowsingContextState::GetRenderFrameProxyHost",
ChromeTrackEvent::kBrowsingContextState, this,
ChromeTrackEvent::kSiteInstanceGroup, site_instance_group);
auto* proxy =
GetRenderFrameProxyHostImpl(site_instance_group, proxy_access_mode);
TRACE_EVENT_END("navigation.debug", ChromeTrackEvent::kRenderFrameProxyHost,
proxy);
return proxy;
}
RenderFrameProxyHost* BrowsingContextState::GetRenderFrameProxyHostImpl(
SiteInstanceGroup* site_instance_group,
ProxyAccessMode proxy_access_mode) const {
if (features::GetBrowsingContextMode() ==
features::BrowsingContextStateImplementationType::
kSwapForCrossBrowsingInstanceNavigations &&
proxy_access_mode == ProxyAccessMode::kRegular) {
// CHECK to verify that the proxy is being accessed from the correct
// BrowsingContextState. As both BrowsingContextState (in non-legacy mode)
// and RenderFrameProxyHost (via SiteInstance) are tied to a given
// CoopRelatedGroup, the CoopRelatedGroupId of the BrowsingContextState
// (in the non-legacy mode) and of the SiteInstanceGroup should match. If
// they do not, the code calling this method has likely chosen the wrong
// BrowsingContextState (e.g. one from the current RenderFrameHost rather
// than from speculative or vice versa) – as this can lead to various
// unpredictable bugs in proxy management logic, we want to crash the
// browser here when this condition fails.
//
// Note: Outer delegates are an exception, and when we're expecting to
// interact with one, we should pass in the proper `proxy_access_mode` to
// not end up in this condition.
CHECK_EQ(coop_related_group_token_.value(),
site_instance_group->coop_related_group_token());
}
auto it = proxy_hosts_.find(site_instance_group->GetId());
if (it != proxy_hosts_.end()) {
return it->second.get();
}
return nullptr;
}
void BrowsingContextState::DeleteRenderFrameProxyHost(
SiteInstanceGroup* site_instance_group,
ProxyAccessMode proxy_access_mode) {
if (features::GetBrowsingContextMode() ==
features::BrowsingContextStateImplementationType::
kSwapForCrossBrowsingInstanceNavigations &&
proxy_access_mode == ProxyAccessMode::kRegular) {
// See comments in GetRenderFrameProxyHost for why this check is needed.
CHECK_EQ(coop_related_group_token_.value(),
site_instance_group->coop_related_group_token());
}
TRACE_EVENT("navigation", "BrowsingContextState::DeleteRenderFrameProxyHost",
ChromeTrackEvent::kBrowsingContextState, this,
ChromeTrackEvent::kSiteInstanceGroup, site_instance_group);
site_instance_group->RemoveObserver(this);
proxy_hosts_.erase(site_instance_group->GetId());
}
RenderFrameProxyHost* BrowsingContextState::CreateRenderFrameProxyHost(
SiteInstanceImpl* site_instance,
const scoped_refptr<RenderViewHostImpl>& rvh,
FrameTreeNode* frame_tree_node,
ProxyAccessMode proxy_access_mode,
const blink::RemoteFrameToken& frame_token) {
TRACE_EVENT_BEGIN(
"navigation", "BrowsingContextState::CreateRenderFrameProxyHost",
ChromeTrackEvent::kBrowsingContextState, this,
ChromeTrackEvent::kSiteInstanceGroup, site_instance->group(),
ChromeTrackEvent::kRenderViewHost, rvh ? rvh.get() : nullptr,
ChromeTrackEvent::kFrameTreeNodeInfo, frame_tree_node);
if (features::GetBrowsingContextMode() ==
features::BrowsingContextStateImplementationType::
kLegacyOneToOneWithFrameTreeNode) {
DCHECK_EQ(this,
frame_tree_node->current_frame_host()->browsing_context_state());
}
if (features::GetBrowsingContextMode() ==
features::BrowsingContextStateImplementationType::
kSwapForCrossBrowsingInstanceNavigations &&
proxy_access_mode == ProxyAccessMode::kRegular) {
// See comments in GetRenderFrameProxyHost for why this check is needed.
CHECK_EQ(coop_related_group_token_.value(),
site_instance->coop_related_group_token());
}
auto site_instance_group_id = site_instance->group()->GetId();
CHECK(proxy_hosts_.find(site_instance_group_id) == proxy_hosts_.end())
<< "A proxy already existed for this SiteInstanceGroup.";
RenderFrameProxyHost* proxy_host = new RenderFrameProxyHost(
site_instance, std::move(rvh), frame_tree_node, frame_token);
proxy_hosts_[site_instance_group_id] = base::WrapUnique(proxy_host);
site_instance->group()->AddObserver(this);
TRACE_EVENT_END("navigation", ChromeTrackEvent::kRenderFrameProxyHost,
proxy_host);
return proxy_host;
}
RenderFrameProxyHost* BrowsingContextState::CreateOuterDelegateProxy(
SiteInstanceImpl* outer_contents_site_instance,
FrameTreeNode* frame_tree_node,
const blink::RemoteFrameToken& frame_token) {
// We only get here when Delegate for this manager is an inner delegate.
return CreateRenderFrameProxyHost(outer_contents_site_instance,
/*rvh=*/nullptr, frame_tree_node,
ProxyAccessMode::kAllowOuterDelegate,
frame_token);
}
void BrowsingContextState::DeleteOuterDelegateProxy(
SiteInstanceGroup* outer_contents_site_instance_group) {
DeleteRenderFrameProxyHost(
outer_contents_site_instance_group,
BrowsingContextState::ProxyAccessMode::kAllowOuterDelegate);
}
size_t BrowsingContextState::GetProxyCount() {
return proxy_hosts_.size();
}
bool BrowsingContextState::UpdateFramePolicyHeaders(
network::mojom::WebSandboxFlags sandbox_flags,
const blink::ParsedPermissionsPolicy& parsed_header) {
bool changed = false;
if (replication_state_->permissions_policy_header != parsed_header) {
replication_state_->permissions_policy_header = parsed_header;
changed = true;
}
// TODO(iclelland): Kill the renderer if sandbox flags is not a subset of the
// currently effective sandbox flags from the frame. https://crbug.com/740556
network::mojom::WebSandboxFlags updated_flags =
sandbox_flags | replication_state_->frame_policy.sandbox_flags;
if (replication_state_->active_sandbox_flags != updated_flags) {
replication_state_->active_sandbox_flags = updated_flags;
changed = true;
}
// Notify any proxies if the policies have been changed.
if (changed) {
ExecuteRemoteFramesBroadcastMethod(
base::BindRepeating(
[](blink::mojom::FrameReplicationStatePtr& replication_state,
RenderFrameProxyHost* proxy) {
proxy->GetAssociatedRemoteFrame()->DidSetFramePolicyHeaders(
replication_state->active_sandbox_flags,
replication_state->permissions_policy_header);
},
std::ref(replication_state_)),
/*group_to_skip=*/nullptr, /*outer_delegate_proxy=*/nullptr);
}
return changed;
}
bool BrowsingContextState::CommitFramePolicy(
const blink::FramePolicy& new_frame_policy) {
// Documents create iframes, iframes host new documents. Both are associated
// with sandbox flags. They are required to be stricter or equal to their
// owner when they change, as we go down.
// TODO(https://crbug.com/1262061). Enforce the invariant mentioned above,
// once the interactions with fenced frame has been tested and clarified.
bool did_change_flags = new_frame_policy.sandbox_flags !=
replication_state_->frame_policy.sandbox_flags;
bool did_change_container_policy =
new_frame_policy.container_policy !=
replication_state_->frame_policy.container_policy;
bool did_change_required_document_policy =
new_frame_policy.required_document_policy !=
replication_state_->frame_policy.required_document_policy;
if (did_change_flags) {
replication_state_->frame_policy.sandbox_flags =
new_frame_policy.sandbox_flags;
}
if (did_change_container_policy) {
replication_state_->frame_policy.container_policy =
new_frame_policy.container_policy;
}
if (did_change_required_document_policy) {
replication_state_->frame_policy.required_document_policy =
new_frame_policy.required_document_policy;
}
UpdateFramePolicyHeaders(new_frame_policy.sandbox_flags,
replication_state_->permissions_policy_header);
return did_change_flags || did_change_container_policy ||
did_change_required_document_policy;
}
void BrowsingContextState::SetFrameName(const std::string& name,
const std::string& unique_name) {
if (name == replication_state_->name) {
// |unique_name| shouldn't change unless |name| changes.
DCHECK_EQ(unique_name, replication_state_->unique_name);
return;
}
if (parent_) {
// Non-main frames should have a non-empty unique name.
DCHECK(!unique_name.empty());
} else {
// Unique name of main frames should always stay empty.
DCHECK(unique_name.empty());
}
// Note the unique name should only be able to change before the first real
// load is committed, but that's not strongly enforced here.
ExecuteRemoteFramesBroadcastMethod(
base::BindRepeating(
[](const std::string& name, const std::string& unique_name,
RenderFrameProxyHost* proxy) {
proxy->GetAssociatedRemoteFrame()->SetReplicatedName(name,
unique_name);
},
std::ref(name), std::ref(unique_name)),
/*group_to_skip=*/nullptr, /*outer_delegate_proxy=*/nullptr);
replication_state_->unique_name = unique_name;
replication_state_->name = name;
}
void BrowsingContextState::SetCurrentOrigin(
const url::Origin& origin,
bool is_potentially_trustworthy_unique_origin) {
if (origin.IsSameOriginWith(replication_state_->origin) &&
replication_state_->has_potentially_trustworthy_unique_origin ==
is_potentially_trustworthy_unique_origin) {
return;
}
ExecuteRemoteFramesBroadcastMethod(
base::BindRepeating(
[](const url::Origin& origin,
bool is_potentially_trustworthy_unique_origin,
RenderFrameProxyHost* proxy) {
proxy->GetAssociatedRemoteFrame()->SetReplicatedOrigin(
origin, is_potentially_trustworthy_unique_origin);
},
std::ref(origin), std::ref(is_potentially_trustworthy_unique_origin)),
/*group_to_skip=*/nullptr, /*outer_delegate_proxy=*/nullptr);
replication_state_->origin = origin;
replication_state_->has_potentially_trustworthy_unique_origin =
is_potentially_trustworthy_unique_origin;
}
void BrowsingContextState::SetInsecureRequestPolicy(
blink::mojom::InsecureRequestPolicy policy) {
if (policy == replication_state_->insecure_request_policy)
return;
ExecuteRemoteFramesBroadcastMethod(
base::BindRepeating(
[](blink::mojom::InsecureRequestPolicy policy,
RenderFrameProxyHost* proxy) {
proxy->GetAssociatedRemoteFrame()->EnforceInsecureRequestPolicy(
policy);
},
policy),
/*group_to_skip=*/nullptr, /*outer_delegate_proxy=*/nullptr);
replication_state_->insecure_request_policy = policy;
}
void BrowsingContextState::SetInsecureNavigationsSet(
const std::vector<uint32_t>& insecure_navigations_set) {
DCHECK(std::is_sorted(insecure_navigations_set.begin(),
insecure_navigations_set.end()));
if (insecure_navigations_set == replication_state_->insecure_navigations_set)
return;
ExecuteRemoteFramesBroadcastMethod(
base::BindRepeating(
[](const std::vector<uint32_t>& insecure_navigations_set,
RenderFrameProxyHost* proxy) {
proxy->GetAssociatedRemoteFrame()->EnforceInsecureNavigationsSet(
insecure_navigations_set);
},
std::ref(insecure_navigations_set)),
/*group_to_skip=*/nullptr, /*outer_delegate_proxy=*/nullptr);
replication_state_->insecure_navigations_set = insecure_navigations_set;
}
void BrowsingContextState::OnSetHadStickyUserActivationBeforeNavigation(
bool value) {
ExecuteRemoteFramesBroadcastMethod(
base::BindRepeating(
[](bool value, RenderFrameProxyHost* proxy) {
proxy->GetAssociatedRemoteFrame()
->SetHadStickyUserActivationBeforeNavigation(value);
},
value),
/*group_to_skip=*/nullptr, /*outer_delegate_proxy=*/nullptr);
replication_state_->has_received_user_gesture_before_nav = value;
}
void BrowsingContextState::SetIsAdFrame(bool is_ad_frame) {
if (is_ad_frame == replication_state_->is_ad_frame)
return;
replication_state_->is_ad_frame = is_ad_frame;
ExecuteRemoteFramesBroadcastMethod(
base::BindRepeating(
[](bool is_ad_frame, RenderFrameProxyHost* proxy) {
proxy->GetAssociatedRemoteFrame()->SetReplicatedIsAdFrame(
is_ad_frame);
},
is_ad_frame),
/*group_to_skip=*/nullptr, /*outer_delegate_proxy=*/nullptr);
}
void BrowsingContextState::ActiveFrameCountIsZero(
SiteInstanceGroup* site_instance_group) {
// |site_instance_group| no longer contains any active RenderFrameHosts, so we
// don't need to maintain a proxy there anymore.
RenderFrameProxyHost* proxy = GetRenderFrameProxyHost(site_instance_group);
CHECK(proxy);
TRACE_EVENT_INSTANT("navigation",
"BrowsingContextState::ActiveFrameCountIsZero",
ChromeTrackEvent::kBrowsingContextState, this,
ChromeTrackEvent::kRenderFrameProxyHost, proxy);
DeleteRenderFrameProxyHost(site_instance_group);
}
void BrowsingContextState::RenderProcessGone(
SiteInstanceGroup* site_instance_group,
const ChildProcessTerminationInfo& info) {
GetRenderFrameProxyHost(site_instance_group,
ProxyAccessMode::kAllowOuterDelegate)
->SetRenderFrameProxyCreated(false);
}
void BrowsingContextState::SendFramePolicyUpdatesToProxies(
SiteInstanceGroup* parent_group,
const blink::FramePolicy& frame_policy) {
// Notify all of the frame's proxies about updated policies, excluding
// the parent process since it already knows the latest state.
ExecuteRemoteFramesBroadcastMethod(
base::BindRepeating(
[](SiteInstanceGroup* parent_group,
const blink::FramePolicy& frame_policy,
RenderFrameProxyHost* proxy) {
if (proxy->site_instance_group() == parent_group)
return;
proxy->GetAssociatedRemoteFrame()->DidUpdateFramePolicy(
frame_policy);
},
base::Unretained(parent_group), std::ref(frame_policy)),
/*group_to_skip=*/nullptr, /*outer_delegate_proxy=*/nullptr);
}
void BrowsingContextState::OnDidStartLoading() {
ExecuteRemoteFramesBroadcastMethod(
base::BindRepeating([](RenderFrameProxyHost* proxy) {
proxy->GetAssociatedRemoteFrame()->DidStartLoading();
}),
/*group_to_skip=*/nullptr, /*outer_delegate_proxy=*/nullptr);
}
void BrowsingContextState::OnDidStopLoading() {
ExecuteRemoteFramesBroadcastMethod(
base::BindRepeating([](RenderFrameProxyHost* proxy) {
proxy->GetAssociatedRemoteFrame()->DidStopLoading();
}),
/*group_to_skip=*/nullptr, /*outer_delegate_proxy=*/nullptr);
}
void BrowsingContextState::ResetProxyHosts() {
for (const auto& pair : proxy_hosts_) {
pair.second->site_instance_group()->RemoveObserver(this);
}
proxy_hosts_.clear();
}
void BrowsingContextState::UpdateOpener(
SiteInstanceGroup* source_site_instance_group) {
for (const auto& pair : proxy_hosts_) {
if (pair.second->site_instance_group() == source_site_instance_group)
continue;
pair.second->UpdateOpener();
}
}
void BrowsingContextState::OnDidUpdateFrameOwnerProperties(
const blink::mojom::FrameOwnerProperties& properties) {
// Notify this frame's proxies if they live in a different process from its
// parent. This is only currently needed for the allowFullscreen property,
// since that can be queried on RemoteFrame ancestors.
//
// TODO(alexmos): It would be sufficient to only send this update to proxies
// in the current FrameTree.
ExecuteRemoteFramesBroadcastMethod(
base::BindRepeating(
[](SiteInstanceGroup* parent_group,
const blink::mojom::FrameOwnerProperties& properties,
RenderFrameProxyHost* proxy) {
if (proxy->site_instance_group() == parent_group)
return;
proxy->GetAssociatedRemoteFrame()->SetFrameOwnerProperties(
properties.Clone());
},
base::Unretained(parent_->GetSiteInstance()->group()),
std::ref(properties)),
/*group_to_skip=*/nullptr, /*outer_delegate_proxy=*/nullptr);
}
void BrowsingContextState::ExecuteRemoteFramesBroadcastMethod(
base::RepeatingCallback<void(RenderFrameProxyHost*)> callback,
SiteInstanceGroup* group_to_skip,
RenderFrameProxyHost* outer_delegate_proxy) {
for (const auto& pair : proxy_hosts_) {
if (outer_delegate_proxy == pair.second.get())
continue;
if (pair.second->site_instance_group() == group_to_skip) {
continue;
}
if (!pair.second->is_render_frame_proxy_live())
continue;
callback.Run(pair.second.get());
}
}
void BrowsingContextState::WriteIntoTrace(
perfetto::TracedProto<TraceProto> proto) const {
if (browsing_instance_id_.has_value()) {
proto->set_browsing_instance_id(browsing_instance_id_.value().value());
}
if (coop_related_group_token_.has_value()) {
proto->set_coop_related_group_token(
coop_related_group_token_.value().ToString());
}
perfetto::TracedDictionary dict = std::move(proto).AddDebugAnnotations();
dict.Add("this", static_cast<const void*>(this));
}
base::SafeRef<BrowsingContextState> BrowsingContextState::GetSafeRef() {
return weak_factory_.GetSafeRef();
}
} // namespace content