blob: e31af1e02f71f8bb45616101a5d6db46ae05d9cc [file] [log] [blame]
// Copyright 2014 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/render_frame_proxy_host.h"
#include <memory>
#include <tuple>
#include <utility>
#include <vector>
#include "base/containers/circular_deque.h"
#include "base/containers/contains.h"
#include "base/functional/callback.h"
#include "base/hash/hash.h"
#include "base/lazy_instance.h"
#include "base/metrics/histogram_functions.h"
#include "base/no_destructor.h"
#include "base/strings/utf_string_conversions.h"
#include "base/timer/elapsed_timer.h"
#include "base/trace_event/typed_macros.h"
#include "base/types/optional_util.h"
#include "content/browser/bad_message.h"
#include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/renderer_host/agent_scheduling_group_host.h"
#include "content/browser/renderer_host/batched_proxy_ipc_sender.h"
#include "content/browser/renderer_host/cross_process_frame_connector.h"
#include "content/browser/renderer_host/frame_tree.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/ipc_utils.h"
#include "content/browser/renderer_host/navigation_metrics_utils.h"
#include "content/browser/renderer_host/navigator.h"
#include "content/browser/renderer_host/render_frame_host_delegate.h"
#include "content/browser/renderer_host/render_view_host_delegate.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_view_base.h"
#include "content/browser/renderer_host/render_widget_host_view_child_frame.h"
#include "content/browser/site_instance_group.h"
#include "content/browser/site_instance_impl.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/disallow_activation_reason.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "content/public/common/referrer_type_converters.h"
#include "ipc/ipc_message.h"
#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
#include "services/network/public/cpp/web_sandbox_flags.h"
#include "third_party/abseil-cpp/absl/container/flat_hash_map.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "third_party/blink/public/mojom/frame/frame_owner_properties.mojom.h"
#include "third_party/blink/public/mojom/messaging/transferable_message.mojom.h"
#include "third_party/blink/public/mojom/navigation/navigation_initiator_activation_and_ad_status.mojom.h"
#include "third_party/blink/public/mojom/scroll/scroll_into_view_params.mojom.h"
#include "ui/gfx/geometry/rect_f.h"
namespace content {
namespace {
RenderFrameProxyHost::TestObserver* g_observer_for_testing = nullptr;
// The (process id, routing id) pair that identifies one RenderFrameProxy.
typedef std::pair<int32_t, int32_t> RenderFrameProxyHostID;
using RoutingIDFrameProxyMap =
absl::flat_hash_map<RenderFrameProxyHostID, RenderFrameProxyHost*>;
base::LazyInstance<RoutingIDFrameProxyMap>::DestructorAtExit
g_routing_id_frame_proxy_map = LAZY_INSTANCE_INITIALIZER;
using TokenFrameMap =
absl::flat_hash_map<blink::RemoteFrameToken, RenderFrameProxyHost*>;
TokenFrameMap& GetTokenFrameProxyMap() {
static base::NoDestructor<TokenFrameMap> token_frame_proxy_map;
return *token_frame_proxy_map;
}
// TODO(https://crbug.com/339512240): Remove this killswitch once the
// optimization for postMessage proxy creation finishes rolling out.
BASE_FEATURE(kSkipPostMessageProxyCreationWithinFrameTree,
"SkipPostMessageProxyCreationWithinFrameTree",
base::FEATURE_ENABLED_BY_DEFAULT);
} // namespace
// static
void RenderFrameProxyHost::SetObserverForTesting(TestObserver* observer) {
// Prevent clobbering by previously set TestObserver.
DCHECK(!observer || (observer && !g_observer_for_testing));
g_observer_for_testing = observer;
}
// static
RenderFrameProxyHost* RenderFrameProxyHost::FromID(int process_id,
int routing_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
RoutingIDFrameProxyMap* frames = g_routing_id_frame_proxy_map.Pointer();
auto it = frames->find(RenderFrameProxyHostID(process_id, routing_id));
return it == frames->end() ? nullptr : it->second;
}
// static
RenderFrameProxyHost* RenderFrameProxyHost::FromFrameToken(
int process_id,
const blink::RemoteFrameToken& frame_token) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
TokenFrameMap* frames = &GetTokenFrameProxyMap();
auto it = frames->find(frame_token);
// The check against |process_id| isn't strictly necessary, but represents
// an extra level of protection against a renderer trying to force a frame
// token.
return it != frames->end() &&
it->second->GetProcess()->GetDeprecatedID() == process_id
? it->second
: nullptr;
}
// static
bool RenderFrameProxyHost::IsFrameTokenInUse(
const blink::RemoteFrameToken& frame_token) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
TokenFrameMap* frames = &GetTokenFrameProxyMap();
return frames->find(frame_token) != frames->end();
}
RenderFrameProxyHost::RenderFrameProxyHost(
SiteInstanceGroup* site_instance_group,
scoped_refptr<RenderViewHostImpl> render_view_host,
FrameTreeNode* frame_tree_node,
const blink::RemoteFrameToken& frame_token)
: routing_id_(site_instance_group->process()->GetNextRoutingID()),
site_instance_group_(site_instance_group),
process_(site_instance_group->process()),
frame_tree_node_(frame_tree_node),
render_frame_proxy_created_(false),
render_view_host_(std::move(render_view_host)),
frame_token_(frame_token) {
TRACE_EVENT_BEGIN("navigation.debug", "RenderFrameProxyHost",
perfetto::Track::FromPointer(this),
"render_frame_proxy_host_when_created", *this);
GetAgentSchedulingGroup().AddRoute(routing_id_, this);
CHECK(g_routing_id_frame_proxy_map.Get()
.insert(std::make_pair(
RenderFrameProxyHostID(GetProcess()->GetDeprecatedID(),
routing_id_),
this))
.second);
CHECK(GetTokenFrameProxyMap()
.insert(std::make_pair(frame_token_, this))
.second);
CHECK(render_view_host_ ||
frame_tree_node_->render_manager()->IsMainFrameForInnerDelegate());
bool is_proxy_to_parent =
!frame_tree_node_->IsMainFrame() &&
frame_tree_node_->parent()->GetSiteInstance()->group() ==
site_instance_group;
bool is_proxy_to_outer_delegate =
frame_tree_node_->render_manager()->IsMainFrameForInnerDelegate();
// If this is a proxy to parent frame or this proxy is for the inner
// WebContents's FrameTreeNode in outer WebContents's SiteInstance, then we
// need a CrossProcessFrameConnector.
if (is_proxy_to_parent || is_proxy_to_outer_delegate) {
// The RenderFrameHost navigating cross-process is destroyed and a proxy for
// it is created in the parent's process. CrossProcessFrameConnector
// initialization only needs to happen on an initial cross-process
// navigation, when the RenderFrameHost leaves the same process as its
// parent. The same CrossProcessFrameConnector is used for subsequent cross-
// process navigations, but it will be destroyed if the frame is
// navigated back to the same SiteInstance as its parent.
cross_process_frame_connector_ =
std::make_unique<CrossProcessFrameConnector>(this);
}
if (g_observer_for_testing)
g_observer_for_testing->OnCreated(this);
}
RenderFrameProxyHost::~RenderFrameProxyHost() {
if (g_observer_for_testing)
g_observer_for_testing->OnDeleted(this);
if (GetProcess()->IsInitializedAndNotDead()) {
// TODO(nasko): For now, don't send this IPC for top-level frames, as
// the top-level RenderFrame will delete the RenderFrameProxy.
// This can be removed once we don't have a swapped out state on
// RenderFrame. See https://crbug.com/357747
if (!frame_tree_node_->IsMainFrame() && is_render_frame_proxy_live())
GetAssociatedRemoteFrame()->DetachAndDispose();
}
// TODO(arthursonzogni): There are no known reason for removing the
// RenderViewHostImpl here instead of automatically at the end of the
// destructor. This line can be removed.
render_view_host_.reset();
GetAgentSchedulingGroup().RemoveRoute(routing_id_);
g_routing_id_frame_proxy_map.Get().erase(
RenderFrameProxyHostID(GetProcess()->GetDeprecatedID(), routing_id_));
GetTokenFrameProxyMap().erase(frame_token_);
TRACE_EVENT_END("navigation.debug", perfetto::Track::FromPointer(this));
}
void RenderFrameProxyHost::SetChildRWHView(RenderWidgetHostViewChildFrame* view,
const gfx::Size* initial_frame_size,
bool allow_paint_holding) {
cross_process_frame_connector_->SetView(view, allow_paint_holding);
if (initial_frame_size)
cross_process_frame_connector_->SetLocalFrameSize(*initial_frame_size);
}
RenderViewHostImpl* RenderFrameProxyHost::GetRenderViewHost() {
return render_view_host_.get();
}
std::string RenderFrameProxyHost::ToDebugString() {
return "RFPH:" + frame_tree_node_->current_frame_host()->ToDebugString();
}
bool RenderFrameProxyHost::InitRenderFrameProxy(
const std::optional<base::UnguessableToken>& navigation_metrics_token,
BatchedProxyIPCSender* batched_proxy_ipc_sender) {
DCHECK(!render_frame_proxy_created_);
// We shouldn't be creating proxies for subframes of frames in
// BackForwardCache.
DCHECK(!frame_tree_node_->current_frame_host()->IsInBackForwardCache());
// If the current RenderFrameHost is pending deletion, no new proxies should
// be created for it, since this frame should no longer be visible from other
// processes. We can get here with postMessage while trying to recreate
// proxies for the sender.
if (frame_tree_node_->current_frame_host()->IsPendingDeletion())
return false;
// It is possible to reach this when the process is dead (in particular, when
// creating proxies from CreateProxiesForChildFrame). In that case, don't
// create the proxy. The process shouldn't be resurrected just to create
// RenderFrameProxies; it should be restored only if it needs to host a
// RenderFrame. When that happens, the process will be reinitialized, and
// all necessary proxies, including any of the ones we skipped here, will be
// created by CreateProxiesForSiteInstance. See https://crbug.com/476846
if (!GetProcess()->IsInitializedAndNotDead())
return false;
std::optional<blink::FrameToken> opener_frame_token;
if (frame_tree_node_->opener()) {
opener_frame_token =
frame_tree_node_->render_manager()->GetOpenerFrameToken(
site_instance_group());
}
// The current `RenderFrameHost`'s `devtools_frame_token` can be used here
// because it is not expected to differ when there is a
// `RenderFrameProxyHost` in a separate window. The token may change on
// MPArch activations in the main frame (e.g., prerender), but those
// cannot occur if the `BrowsingInstance` has more than one window.
const ::base::UnguessableToken& devtools_frame_token =
frame_tree_node_->current_frame_host()->devtools_frame_token();
if (frame_tree_node_->parent()) {
// It is safe to use GetRenderFrameProxyHost to get the parent proxy, since
// new child frames always start out as local frames, so a new proxy should
// never have a RenderFrameHost as a parent.
RenderFrameProxyHost* parent_proxy =
frame_tree_node_->parent()
->browsing_context_state()
->GetRenderFrameProxyHost(site_instance_group());
CHECK(parent_proxy);
// Proxies that aren't live in the parent node should not be initialized
// here, since there is no valid parent `blink::RemoteFrame` on the renderer
// side. This can happen when adding a new child frame after an opener
// process crashed and was reloaded. See https://crbug.com/501152.
//
// Note that with `batched_proxy_ipc_sender`, the parent proxy could be
// non-live but pending creation. In that case, it is fine to initialize
// this proxy, as `batched_proxy_ipc_sender` guarantees that its parent
// will be created first in the renderer.
GlobalRoutingID parent_global_id = parent_proxy->GetGlobalID();
bool is_parent_proxy_creation_pending =
batched_proxy_ipc_sender &&
batched_proxy_ipc_sender->IsProxyCreationPending(parent_global_id);
if (!parent_proxy->is_render_frame_proxy_live() &&
!is_parent_proxy_creation_pending) {
return false;
}
// TODO(crbug.com/40248300): Support main frame proxy batch creation
// with batched_proxy_ipc_sender.
if (batched_proxy_ipc_sender) {
batched_proxy_ipc_sender->AddNewChildProxyCreationTask(
GetSafeRef(), frame_token_, opener_frame_token,
frame_tree_node_->tree_scope_type(),
frame_tree_node_->current_replication_state().Clone(),
frame_tree_node_->frame_owner_properties().Clone(),
frame_tree_node_->IsLoading(), devtools_frame_token,
CreateAndBindRemoteFrameInterfaces(), parent_global_id);
// Don't call `SetRenderFrameProxyCreated(true)` here, since the proxy
// wasn't actually created. This will be called for all
// `RenderFrameProxyHosts` later in
// `BatchedProxyIPCSender::CreateAllProxies`, after all proxies
// are created.
} else {
// Note that `navigation_metrics_token` is intentionally *not* passed in
// this IPC. This is because in practice, this path is no longer used for
// creating proxies during a navigation; this is done via
// `batched_proxy_ipc_sender` above instead. This path is still used for
// non-navigation proxy creation, such as creating proxies for a newly
// created subframe.
parent_proxy->GetAssociatedRemoteFrame()->CreateRemoteChild(
frame_token_, opener_frame_token, frame_tree_node_->tree_scope_type(),
frame_tree_node_->current_replication_state().Clone(),
frame_tree_node_->frame_owner_properties().Clone(),
frame_tree_node_->IsLoading(), devtools_frame_token,
CreateAndBindRemoteFrameInterfaces());
SetRenderFrameProxyCreated(true);
}
} else {
GetRenderViewHost()->GetAssociatedPageBroadcast()->CreateRemoteMainFrame(
frame_token_, opener_frame_token,
frame_tree_node_->current_replication_state().Clone(),
frame_tree_node_->IsLoading(), devtools_frame_token,
navigation_metrics_token, CreateAndBindRemoteFrameInterfaces(),
CreateAndBindRemoteMainFrameInterfaces());
SetRenderFrameProxyCreated(true);
}
return true;
}
AgentSchedulingGroupHost& RenderFrameProxyHost::GetAgentSchedulingGroup() {
return site_instance_group_->agent_scheduling_group();
}
void RenderFrameProxyHost::SetRenderFrameProxyCreated(bool created) {
render_frame_proxy_created_ = created;
// Reset the mojo channels when the associated renderer is gone. It allows
// reuse of the mojo channels when this RenderFrameProxyHost is reused.
if (!render_frame_proxy_created_)
TearDownMojoConnection();
}
const mojo::AssociatedRemote<blink::mojom::RemoteFrame>&
RenderFrameProxyHost::GetAssociatedRemoteFrame() {
DCHECK(remote_frame_.is_bound());
return remote_frame_;
}
const mojo::AssociatedRemote<blink::mojom::RemoteMainFrame>&
RenderFrameProxyHost::GetAssociatedRemoteMainFrame() {
DCHECK(remote_main_frame_.is_bound());
return remote_main_frame_;
}
void RenderFrameProxyHost::SetInheritedEffectiveTouchAction(
cc::TouchAction touch_action) {
cross_process_frame_connector_->OnSetInheritedEffectiveTouchAction(
touch_action);
}
void RenderFrameProxyHost::UpdateRenderThrottlingStatus(bool is_throttled,
bool subtree_throttled,
bool display_locked) {
cross_process_frame_connector_->UpdateRenderThrottlingStatus(
is_throttled, subtree_throttled, display_locked);
}
void RenderFrameProxyHost::VisibilityChanged(
blink::mojom::FrameVisibility visibility) {
cross_process_frame_connector_->OnVisibilityChanged(visibility);
}
void RenderFrameProxyHost::UpdateOpener() {
// Another frame in this proxy's SiteInstanceGroup may reach the new opener by
// first reaching this proxy and then referencing its window.opener. Ensure
// the new opener's proxy exists in this case.
if (frame_tree_node_->opener()) {
frame_tree_node_->opener()->render_manager()->CreateOpenerProxies(
site_instance_group(), frame_tree_node_,
frame_tree_node_->current_frame_host()->browsing_context_state(),
/*navigation_metrics_token=*/std::nullopt);
}
if (!is_render_frame_proxy_live())
return;
auto opener_frame_token =
frame_tree_node_->render_manager()->GetOpenerFrameToken(
site_instance_group());
GetAssociatedRemoteFrame()->UpdateOpener(opener_frame_token);
}
void RenderFrameProxyHost::SetFocusedFrame() {
if (!is_render_frame_proxy_live())
return;
GetAssociatedRemoteFrame()->Focus();
}
void RenderFrameProxyHost::ScrollRectToVisible(
const gfx::RectF& rect_to_scroll,
blink::mojom::ScrollIntoViewParamsPtr params) {
if (!is_render_frame_proxy_live())
return;
GetAssociatedRemoteFrame()->ScrollRectToVisible(rect_to_scroll,
std::move(params));
}
void RenderFrameProxyHost::Detach() {
if (frame_tree_node_->render_manager()->IsMainFrameForInnerDelegate()) {
frame_tree_node_->render_manager()->RemoveOuterDelegateFrame();
return;
}
// For a main frame with no outer delegate, no further work is needed. In this
// case, detach can only be triggered by closing the entire RenderViewHost.
// Instead, this cleanup relies on the destructors of RenderFrameHost and
// RenderFrameProxyHost decrementing the refcounts of their associated
// RenderViewHost. When the refcount hits 0, the corresponding renderer object
// is cleaned up. Since WebContents destruction will also destroy
// RenderFrameHost/RenderFrameProxyHost objects in FrameTree, this eventually
// results in all the associated RenderViewHosts being closed.
if (frame_tree_node_->IsMainFrame())
return;
// Otherwise, a remote child frame has been removed from the frame tree.
// Make sure that this action is mirrored to all the other renderers, so
// the frame tree remains consistent.
frame_tree_node_->current_frame_host()->DetachFromProxy();
}
void RenderFrameProxyHost::CheckCompleted() {
RenderFrameHostImpl* target_rfh = frame_tree_node()->current_frame_host();
target_rfh->GetAssociatedLocalFrame()->CheckCompleted();
}
void RenderFrameProxyHost::EnableAutoResize(const gfx::Size& min_size,
const gfx::Size& max_size) {
if (!is_render_frame_proxy_live())
return;
GetAssociatedRemoteFrame()->EnableAutoResize(min_size, max_size);
}
void RenderFrameProxyHost::DisableAutoResize() {
if (!is_render_frame_proxy_live())
return;
GetAssociatedRemoteFrame()->DisableAutoResize();
}
void RenderFrameProxyHost::DidUpdateVisualProperties(
const cc::RenderFrameMetadata& metadata) {
if (!is_render_frame_proxy_live())
return;
GetAssociatedRemoteFrame()->DidUpdateVisualProperties(metadata);
}
void RenderFrameProxyHost::ChildProcessGone() {
if (!is_render_frame_proxy_live())
return;
GetAssociatedRemoteFrame()->ChildProcessGone();
}
void RenderFrameProxyHost::DidFocusFrame() {
TRACE_EVENT("navigation", "RenderFrameProxyHost::DidFocusFrame",
ChromeTrackEvent::kFrameTreeNodeInfo, *frame_tree_node_,
ChromeTrackEvent::kSiteInstanceGroup, *site_instance_group());
// If a fenced frame has requested focus something wrong has gone on. We do
// not support programmatic focus between the embedder and embeddee because
// that could be a side channel.
if (frame_tree_node_->IsInFencedFrameTree() &&
frame_tree_node_->render_manager()->GetProxyToOuterDelegate() == this) {
bad_message::ReceivedBadMessage(GetProcess(),
bad_message::RFPH_FOCUSED_FENCED_FRAME);
return;
}
RenderFrameHostImpl* render_frame_host =
frame_tree_node_->current_frame_host();
// Do not focus inactive RenderFrameHost.
if (!render_frame_host->IsActive())
return;
frame_tree_node_->SetFocusedFrame(site_instance_group());
}
void RenderFrameProxyHost::CapturePaintPreviewOfCrossProcessSubframe(
const gfx::Rect& clip_rect,
const base::UnguessableToken& guid) {
RenderFrameHostImpl* rfh = frame_tree_node_->current_frame_host();
// Do not capture paint on behalf of inactive RenderFrameHost.
if (rfh->IsInactiveAndDisallowActivation(
DisallowActivationReasonId::kCapturePaintPreviewProxy)) {
return;
}
rfh->delegate()->CapturePaintPreviewOfCrossProcessSubframe(clip_rect, guid,
rfh);
}
void RenderFrameProxyHost::SetIsInert(bool inert) {
cross_process_frame_connector_->SetIsInert(inert);
}
std::u16string RenderFrameProxyHost::SerializePostMessageSourceOrigin(
const url::Origin& source_origin) {
std::u16string source_origin_string =
base::UTF8ToUTF16(source_origin.Serialize());
// TODO(crbug.com/40554285, crbug.com/40467682): This serialization used to
// happen in blink via blink::SecurityOrigin::ToString(), but is now happening
// here via url::Origin::Serialize(). The two are the same except for one
// unfortunate difference with file URLs. url::Origin always serializes them
// as "file://", while blink::SecurityOrigin serializes them to "null" or
// "file://" depending on the `allow_file_access_from_file_urls` flag in
// WebPreferences. For now, mimic Blink's file: URL serialization here to
// minimize compat risks. Eventually, this should be improved to (1) rely on
// url::Origin's version and fix url::Origin::Serialize() to honor
// `allow_file_access_from_file_urls` if that is important enough to support,
// (2) plumb `source_origin` further into blink in the receiving renderer, so
// that it can be serialized there (this requires refactoring the other uses
// of RenderFrameHostImpl::PostMessageEvent()), or (3) fix file: URLs to
// always correspond to opaque origins, so that their serializations are
// always "null" in both blink::SecurityOrigin and url::Origin.
if (source_origin.scheme() == url::kFileScheme) {
auto prefs = frame_tree_node()
->current_frame_host()
->delegate()
->GetOrCreateWebPreferences();
if (!prefs.allow_file_access_from_file_urls) {
source_origin_string = u"null";
}
}
return source_origin_string;
}
void RenderFrameProxyHost::RouteMessageEvent(
const std::optional<blink::LocalFrameToken>& source_frame_token,
const url::Origin& source_origin,
const std::u16string& target_origin,
blink::TransferableMessage message) {
base::ElapsedTimer timer;
RenderFrameHostImpl* target_rfh = frame_tree_node()->current_frame_host();
if (!target_rfh->IsRenderFrameLive()) {
// Check if there is an inner delegate involved; if so target its main
// frame or otherwise return since there is no point in forwarding the
// message.
FrameTreeNode* target_ftn = FrameTreeNode::GloballyFindByID(
target_rfh->inner_tree_main_frame_tree_node_id());
target_rfh = target_ftn ? target_ftn->current_frame_host() : nullptr;
if (!target_rfh || !target_rfh->IsRenderFrameLive())
return;
}
// |target_origin| argument of postMessage is already checked by
// blink::LocalDOMWindow::DispatchMessageEventWithOriginCheck (needed for
// messages sent within the same process - e.g. same-site, cross-origin),
// but this check needs to be duplicated below in case the recipient renderer
// process got compromised (i.e. in case the renderer-side check may be
// bypassed).
if (!target_origin.empty()) {
url::Origin target_url_origin =
url::Origin::Create(GURL(base::UTF16ToUTF8(target_origin)));
// Renderer should send either an empty string (this is how "*" is expressed
// in the IPC) or a valid, non-opaque origin. OTOH, there are no security
// implications here - the message payload needs to be protected from an
// unintended recipient, not from the sender.
DCHECK(!target_url_origin.opaque());
// While the postMessage was in flight, the target might have navigated away
// to another origin. In this case, the postMessage should be silently
// dropped.
if (target_url_origin != target_rfh->GetLastCommittedOrigin())
return;
}
std::u16string source_origin_string =
SerializePostMessageSourceOrigin(source_origin);
// Verify the source origin. Note that this used to skip cases where the
// origin serialized to "null", but now that old behavior is behind a kill
// switch.
//
// TODO(crbug.com/40109437): Remove this fallback and always validate opaque
// origins once rollout is complete.
bool should_verify_source_origin =
base::FeatureList::IsEnabled(
features::kAdditionalOpaqueOriginEnforcements) ||
source_origin_string != u"null";
if (should_verify_source_origin) {
auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
if (!policy->HostsOrigin(GetProcess()->GetDeprecatedID(), source_origin)) {
bad_message::ReceivedBadMessage(
GetProcess(), bad_message::RFPH_POST_MESSAGE_INVALID_SOURCE_ORIGIN);
return;
}
}
SiteInstanceGroup* target_group = target_rfh->GetSiteInstance()->group();
bool is_embedder_to_guest_communication = false;
// An embedder could only target a guest's main frame, so it's enough to check
// for the `target_rfh` having an embedder RFH.
if (!target_rfh->GetParentOrOuterDocument()) {
RenderFrameHostImpl* target_embedder_rfh =
target_rfh->GetParentOrOuterDocumentOrEmbedder();
// Note that this is not checking that the source and target are related,
// but that the source is related to the embedder, allowing frames related
// to the embedder to also message the guest.
if (target_embedder_rfh &&
site_instance_group()->IsRelatedSiteInstanceGroup(
target_embedder_rfh->GetSiteInstance()->group())) {
is_embedder_to_guest_communication = true;
}
}
bool is_guest_to_embedder_communication = false;
if (source_frame_token) {
RenderFrameHostImpl* source_rfh = RenderFrameHostImpl::FromFrameToken(
GetProcess()->GetDeprecatedID(), source_frame_token.value());
if (source_rfh) {
RenderFrameHostImpl* source_outermost_rfh =
source_rfh->GetOutermostMainFrame();
RenderFrameHostImpl* source_embedder_rfh =
source_outermost_rfh->GetParentOrOuterDocumentOrEmbedder();
// Note that this is not checking that the source and target are related,
// but that the target is related to the embedder.
if (source_embedder_rfh &&
target_group->IsRelatedSiteInstanceGroup(
source_embedder_rfh->GetSiteInstance()->group())) {
is_guest_to_embedder_communication = true;
}
}
}
// Only deliver the message if the request came from a RenderFrameHost in the
// same BrowsingInstance or if this is a message between a guest and its
// embedder.
if (!target_group->IsRelatedSiteInstanceGroup(site_instance_group()) &&
!is_embedder_to_guest_communication &&
!is_guest_to_embedder_communication) {
return;
}
// Don't deliver any messages to PDF content frames.
if (target_rfh->GetSiteInstance()->GetSiteInfo().is_pdf()) {
bad_message::ReceivedBadMessage(
GetProcess(), bad_message::RFPH_POST_MESSAGE_PDF_CONTENT_FRAME);
return;
}
bool did_call_create_opener_proxies = false;
// If there is a |source_frame_token|, translate it to the frame token of the
// equivalent RenderFrameProxyHost in the target process.
std::optional<blink::RemoteFrameToken> translated_source_token;
if (source_frame_token) {
RenderFrameHostImpl* source_rfh = RenderFrameHostImpl::FromFrameToken(
GetProcess()->GetDeprecatedID(), source_frame_token.value());
if (source_rfh) {
// https://crbug.com/822958: If the postMessage is going to a descendant
// frame, ensure that any pending visual properties such as size are sent
// to the target frame before the postMessage, as sites might implicitly
// be relying on this ordering.
bool target_is_descendant_of_source = false;
for (RenderFrameHost* rfh = target_rfh; rfh; rfh = rfh->GetParent()) {
if (rfh == source_rfh) {
target_is_descendant_of_source = true;
break;
}
}
if (target_is_descendant_of_source) {
target_rfh->GetRenderWidgetHost()
->SynchronizeVisualPropertiesIgnoringPendingAck();
}
if (is_embedder_to_guest_communication) {
// We create a RenderFrameProxyHost for the embedder in the guest's
// render process but we intentionally do not expose the embedder's
// opener chain to it.
// TODO(crbug.com/40261772): Using the main frame will lead to a null
// event.source if a subframe posts a message to the guest. See also
// https://crbug.com/41172969
CHECK(target_rfh->is_main_frame());
source_rfh->GetMainFrame()
->frame_tree_node()
->render_manager()
->CreateRenderFrameProxy(
target_rfh->GetSiteInstance()->group(),
source_rfh->GetMainFrame()->browsing_context_state(),
/*navigation_metrics_token=*/std::nullopt);
} else if (is_guest_to_embedder_communication) {
// A RenderFrameProxyHost was already created when the guest was
// attached.
// We do not create proxies for the subframes of a guest. Note that the
// computation of `is_embedder_to_guest_communication` above assumes
// that guest subframes are not targetable.
} else {
// Ensure that we have a proxy for the source frame in the target
// SiteInstance. If it doesn't exist, create it on demand and also
// create its opener chain, since that will also be accessible to the
// target page. This may be needed in rare cases such as a popup sending
// a postMessage to a subframe of its opener, where the subframe has no
// prior references to the popup.
//
// All cases where a proxy might be missing must involve a message being
// sent across different FrameTrees, since all frames within the same
// FrameTree always have references to one another; therefore, only run
// proxy creation code for those cases as an optimization. For inner
// frame trees, guests are already handled above, and fenced frames
// disallow postMessage to/from their embedder.
if (&source_rfh->frame_tree_node()->frame_tree() !=
&target_rfh->frame_tree_node()->frame_tree() ||
!base::FeatureList::IsEnabled(
kSkipPostMessageProxyCreationWithinFrameTree)) {
// Subtle: postMessages may be sent between frames after their page
// has entered the back-forward cache (e.g., when dispatched from
// pagehide events) - see
// BackForwardCacheBrowserTest.PostMessageDelivered. (These messages
// are subsequently deferred by the target renderer until the cached
// page is re-activated.) In that case, it's neither correct nor
// possible to create proxies, as that requires going through
// FrameTreeNode and RenderFrameHostManager, where the current
// RenderFrameHost is no longer `source_rfh` but rather some other RFH
// in an unrelated SiteInstance. Fortunately, back-forward cache
// restricts GetRelatedActiveContentsCount to 1, and new on-demand
// proxies shouldn't ever be needed within a single FrameTree, where
// all frames already have references to one another. So, this case
// can simply be skipped. The same constraint would also apply to
// pending deletion RenderFrameHosts where messages could be sent from
// unload handlers, where it's also incorrect to create proxies in a
// FrameTreeNode that has moved on to some other unrelated RFH.
if (!source_rfh->IsInBackForwardCache() &&
!source_rfh->IsPendingDeletion()) {
// After skipping back-forward cache and pending deletion cases, we
// should only get here when source_rfh is the current RFH in its
// FrameTreeNode, since we shouldn't receive messages from
// speculative or pending-commit RenderFrameHosts.
CHECK_EQ(source_rfh,
source_rfh->frame_tree_node()->current_frame_host());
source_rfh->frame_tree_node()
->render_manager()
->CreateOpenerProxies(
target_rfh->GetSiteInstance()->group(), nullptr,
source_rfh->browsing_context_state(),
/*navigation_metrics_token=*/std::nullopt);
did_call_create_opener_proxies = true;
}
}
}
// If the message source is a cross-process subframe, its proxy will only
// be created in --site-per-process mode, which is the case when we set an
// actual non-empty value for |translated_source_token|. Otherwise (if the
// proxy wasn't created), use an empty |translated_source_token| (see
// https://crbug.com/485520 for discussion on why this is ok).
// The proxy may be in a different BrowsingContextState in the case of
// postMessages exchanged across inner and outer delegates.
RenderFrameProxyHost* source_proxy_in_target_group =
source_rfh->browsing_context_state()->GetRenderFrameProxyHost(
target_group,
BrowsingContextState::ProxyAccessMode::kAllowOuterDelegate);
if (source_proxy_in_target_group) {
translated_source_token = source_proxy_in_target_group->GetFrameToken();
}
}
}
target_rfh->PostMessageEvent(translated_source_token, source_origin_string,
target_origin, std::move(message));
base::UmaHistogramMicrosecondsTimes(
"SiteIsolation.CrossProcessPostMessageTime", timer.Elapsed());
base::UmaHistogramBoolean(
"SiteIsolation.CrossProcessPostMessage.CreateOpenerProxiesCalled",
did_call_create_opener_proxies);
}
void RenderFrameProxyHost::PrintCrossProcessSubframe(const gfx::Rect& rect,
int32_t document_cookie) {
RenderFrameHostImpl* rfh = frame_tree_node_->current_frame_host();
rfh->delegate()->PrintCrossProcessSubframe(rect, document_cookie, rfh);
}
void RenderFrameProxyHost::SynchronizeVisualProperties(
const blink::FrameVisualProperties& frame_visual_properties) {
cross_process_frame_connector_->OnSynchronizeVisualProperties(
frame_visual_properties);
}
void RenderFrameProxyHost::FocusPage() {
frame_tree_node_->current_frame_host()->FocusPage();
}
void RenderFrameProxyHost::TakeFocus(bool reverse) {
frame_tree_node_->current_frame_host()->TakeFocus(reverse);
}
void RenderFrameProxyHost::UpdateTargetURL(
const GURL& url,
blink::mojom::RemoteMainFrameHost::UpdateTargetURLCallback callback) {
frame_tree_node_->current_frame_host()->UpdateTargetURL(url,
std::move(callback));
}
void RenderFrameProxyHost::RouteCloseEvent() {
// The renderer already ensures that this can only be called on an outermost
// main frame - see DOMWindow::Close(). Terminate the renderer if this is
// not the case.
RenderFrameHostImpl* rfh = frame_tree_node_->current_frame_host();
if (!rfh->IsOutermostMainFrame()) {
bad_message::ReceivedBadMessage(
GetProcess(), bad_message::RFPH_WINDOW_CLOSE_ON_NON_OUTERMOST_FRAME);
return;
}
// Tell the active RenderFrameHost to run unload handlers and close, as long
// as the request came from a RenderFrameHost in the same BrowsingInstance.
// We receive this from a WebViewImpl when it receives a request to close
// the window containing the active RenderFrameHost. Note that different
// BrowsingInstances in the same CoopRelatedGroup should not be able to close
// each other's windows, therefore checking IsRelatedSiteInstance() is enough.
if (site_instance_group()->IsRelatedSiteInstanceGroup(
rfh->GetSiteInstance()->group())) {
rfh->ClosePage(RenderFrameHostImpl::ClosePageSource::kRenderer);
}
}
void RenderFrameProxyHost::OpenURL(blink::mojom::OpenURLParamsPtr params) {
// Verify and unpack IPC payload.
GURL validated_url;
scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory;
RenderFrameHostImpl* current_rfh = frame_tree_node_->current_frame_host();
if (!VerifyOpenURLParams(current_rfh, GetProcess(), params, &validated_url,
&blob_url_loader_factory)) {
return;
}
// Only active documents are allowed to navigate from frame proxy:
// - If the document is in pending deletion, ignore the navigation, because
// the frame is going to disappear soon anyway.
// - If the document is in back-forward cache, it's not allowed to navigate as
// it should remain frozen. Ignore the request and evict the document from
// back-forward cache.
// - If the document is prerendering, we don't expect to get here because
// prerendering pages are expected to defer cross-origin iframes, so there
// should not be any OOPIFs. Just cancel prerendering if we get here.
if (current_rfh->IsInactiveAndDisallowActivation(
DisallowActivationReasonId::kOpenURL)) {
return;
}
// Verify that we are in the same BrowsingInstance as the current
// RenderFrameHost. Note that different BrowsingInstances in the same
// CoopRelatedGroup should not be able to navigate each other's frames,
// therefore checking IsRelatedSiteInstance() is enough.
if (!site_instance_group()->IsRelatedSiteInstanceGroup(
current_rfh->GetSiteInstance()->group())) {
return;
}
if (params->initiator_frame_token) {
RenderFrameHostImpl* initiator_frame = RenderFrameHostImpl::FromFrameToken(
GetProcess()->GetDeprecatedID(), params->initiator_frame_token.value());
if (current_rfh->IsOutermostMainFrame()) {
MaybeRecordAdClickMainFrameNavigationMetrics(
initiator_frame, params->initiator_activation_and_ad_status);
}
}
// Since this navigation targeted a specific RenderFrameProxy, it should stay
// in the current tab.
DCHECK_EQ(WindowOpenDisposition::CURRENT_TAB, params->disposition);
// Augment |download_policy| for situations that were not covered on the
// renderer side, e.g. status not available on remote frame, etc.
blink::NavigationDownloadPolicy download_policy = params->download_policy;
GetContentClient()->browser()->AugmentNavigationDownloadPolicy(
current_rfh, params->user_gesture, &download_policy);
if ((frame_tree_node_->pending_frame_policy().sandbox_flags &
network::mojom::WebSandboxFlags::kDownloads) !=
network::mojom::WebSandboxFlags::kNone) {
download_policy.SetDisallowed(blink::NavigationDownloadType::kSandbox);
}
// This will be used to set the Navigation Timing API navigationStart
// parameter for renderer navigations in the remote frame. If the navigation
// must wait on the current RenderFrameHost to execute its BeforeUnload event,
// the navigation start will be updated when the BeforeUnload ack is received.
const auto navigation_start_time = base::TimeTicks::Now();
blink::LocalFrameToken* initiator_frame_token =
base::OptionalToPtr(params->initiator_frame_token);
// TODO(lfg, lukasza): Remove |extra_headers| parameter from
// RequestTransferURL method once both RenderFrameProxyHost and
// RenderFrameHostImpl call RequestOpenURL from their OnOpenURL handlers.
// See also https://crbug.com/647772.
// TODO(clamy): The transition should probably be changed for POST navigations
// to PAGE_TRANSITION_FORM_SUBMIT. See https://crbug.com/829827.
frame_tree_node_->navigator().NavigateFromFrameProxy(
current_rfh, validated_url, initiator_frame_token,
GetProcess()->GetDeprecatedID(), params->initiator_origin,
params->initiator_base_url,
RenderFrameHostImpl::GetSourceSiteInstanceFromFrameToken(
initiator_frame_token, GetProcess()->GetDeprecatedID(),
current_rfh->GetStoragePartition()),
params->referrer.To<content::Referrer>(), ui::PAGE_TRANSITION_LINK,
params->should_replace_current_entry, download_policy,
params->post_body ? "POST" : "GET", params->post_body,
params->extra_headers, std::move(blob_url_loader_factory),
std::move(params->source_location), params->user_gesture,
params->is_form_submission, params->impression,
params->initiator_activation_and_ad_status,
params->actual_navigation_start, navigation_start_time,
/*is_embedder_initiated_fenced_frame_navigation=*/false,
/*is_unfenced_top_navigation=*/false,
/*force_new_browsing_instance=*/false, params->is_container_initiated,
params->has_rel_opener, params->storage_access_api_status);
}
void RenderFrameProxyHost::UpdateViewportIntersection(
blink::mojom::ViewportIntersectionStatePtr intersection_state,
const std::optional<blink::FrameVisualProperties>& visual_properties) {
cross_process_frame_connector_->UpdateViewportIntersection(
*intersection_state, visual_properties);
}
void RenderFrameProxyHost::DidChangeOpener(
const std::optional<blink::LocalFrameToken>& opener_frame_token) {
frame_tree_node_->render_manager()->DidChangeOpener(opener_frame_token,
site_instance_group());
}
void RenderFrameProxyHost::AdvanceFocus(
blink::mojom::FocusType focus_type,
const blink::LocalFrameToken& source_frame_token) {
// Translate the source RenderFrameHost in this process to its equivalent
// RenderFrameProxyHost in the target SiteInstanceGroup. This is needed for
// continuing the focus traversal from correct place in a parent frame after
// one of its child frames finishes its traversal.
RenderFrameHostImpl* source_rfh = RenderFrameHostImpl::FromFrameToken(
GetProcess()->GetDeprecatedID(), source_frame_token);
RenderFrameHostImpl* target_rfh = frame_tree_node_->current_frame_host();
RenderFrameProxyHost* source_proxy =
source_rfh
? source_rfh->browsing_context_state()->GetRenderFrameProxyHost(
target_rfh->GetSiteInstance()->group())
: nullptr;
if (source_rfh && (source_rfh->HasTransientUserActivation() ||
source_rfh->FocusSourceHasTransientUserActivation())) {
target_rfh->ActivateFocusSourceUserActivation();
source_rfh->DeactivateFocusSourceUserActivation();
}
if (!target_rfh->IsRenderFrameLive()) {
// Do not advance focus if target renderer is gone and continue
// focus traversal in the source frame.
source_rfh->AdvanceFocus(focus_type, this);
source_rfh->delegate()->OnAdvanceFocus(target_rfh);
} else {
target_rfh->AdvanceFocus(focus_type, source_proxy);
target_rfh->delegate()->OnAdvanceFocus(source_rfh);
}
}
bool RenderFrameProxyHost::IsInertForTesting() {
return cross_process_frame_connector_->IsInert();
}
mojo::PendingAssociatedReceiver<blink::mojom::RemoteFrame>
RenderFrameProxyHost::BindRemoteFrameReceiverForTesting() {
remote_frame_.reset();
return remote_frame_.BindNewEndpointAndPassDedicatedReceiver();
}
mojo::PendingAssociatedReceiver<blink::mojom::RemoteMainFrame>
RenderFrameProxyHost::BindRemoteMainFrameReceiverForTesting() {
remote_main_frame_.reset();
return remote_main_frame_.BindNewEndpointAndPassDedicatedReceiver();
}
blink::mojom::RemoteFrameInterfacesFromBrowserPtr
RenderFrameProxyHost::CreateAndBindRemoteFrameInterfaces() {
auto params = blink::mojom::RemoteFrameInterfacesFromBrowser::New();
BindRemoteFrameInterfaces(
params->frame_receiver.InitWithNewEndpointAndPassRemote(),
params->frame_host.InitWithNewEndpointAndPassReceiver());
return params;
}
blink::mojom::RemoteMainFrameInterfacesPtr
RenderFrameProxyHost::CreateAndBindRemoteMainFrameInterfaces() {
auto params = blink::mojom::RemoteMainFrameInterfaces::New();
BindRemoteMainFrameInterfaces(
params->main_frame.InitWithNewEndpointAndPassRemote(),
params->main_frame_host.InitWithNewEndpointAndPassReceiver());
return params;
}
void RenderFrameProxyHost::BindRemoteFrameInterfaces(
mojo::PendingAssociatedRemote<blink::mojom::RemoteFrame> remote_frame,
mojo::PendingAssociatedReceiver<blink::mojom::RemoteFrameHost>
remote_frame_host_receiver) {
DCHECK(!remote_frame_.is_bound());
DCHECK(!remote_frame_host_receiver_.is_bound());
remote_frame_.Bind(std::move(remote_frame));
remote_frame_host_receiver_.Bind(std::move(remote_frame_host_receiver));
if (g_observer_for_testing)
g_observer_for_testing->OnRemoteFrameBound(this);
}
void RenderFrameProxyHost::BindRemoteMainFrameInterfaces(
mojo::PendingAssociatedRemote<blink::mojom::RemoteMainFrame>
remote_main_frame,
mojo::PendingAssociatedReceiver<blink::mojom::RemoteMainFrameHost>
remote_main_frame_host_receiver) {
DCHECK(!remote_main_frame_.is_bound());
DCHECK(!remote_main_frame_host_receiver_.is_bound());
remote_main_frame_.Bind(std::move(remote_main_frame));
remote_main_frame_host_receiver_.Bind(
std::move(remote_main_frame_host_receiver));
if (g_observer_for_testing)
g_observer_for_testing->OnRemoteMainFrameBound(this);
}
void RenderFrameProxyHost::TearDownMojoConnection() {
remote_frame_.reset();
remote_frame_host_receiver_.reset();
remote_main_frame_.reset();
remote_main_frame_host_receiver_.reset();
}
void RenderFrameProxyHost::WriteIntoTrace(
perfetto::TracedProto<TraceProto> proto) const {
proto->set_routing_id(GetRoutingID());
proto->set_process_id(GetProcess()->GetDeprecatedID());
proto->set_is_render_frame_proxy_live(is_render_frame_proxy_live());
if (site_instance_group()) {
proto->set_rvh_map_id(frame_tree_node_->frame_tree()
.GetRenderViewHostMapId(site_instance_group())
.value());
proto->set_site_instance_id(site_instance_group()->GetId().value());
}
}
base::SafeRef<RenderFrameProxyHost> RenderFrameProxyHost::GetSafeRef() {
return weak_factory_.GetSafeRef();
}
} // namespace content