0

Fenced Frames: content::FencedFrame owns a FrameTree

This CL follows-up on the work in https://crrev.com/c/2987019.

Before this CL, the content::FencedFrame object was practically a
shell. This CL begins to fill out content::FencedFrame by making the
following changes:

 - content::FencedFrame owns an "inner" MPArch FrameTree. We introduce
   a new FrameTree::Type::kFencedFrame type of FrameTree, and give
   content::FencedFrame a new `frame_tree_` member that is initialized
   in its constructor
 - Introduce
   content::FencedFrame::CreateProxyAndAttachToOuterFrameTree() which
   actually creates a(n):
     - Outer delegate child FrameTreeNode which is a child of the
       RenderFrameHostImpl that owns the content::FencedFrame object
     - RenderFrameProxyHost whose renderer-side RenderFrameProxy is
       used by Blink in the outer frame tree to remotely reference the
       inner FrameTree's main frame. This is required in the
       current architecture in order to get CrossProcessFrameConnector
       to work, though we are considering divorcing these two concepts
       in the future
 - Filling out content::FencedFrame::Navigate() to actually navigate the
   inner FrameTree via Navigator::NavigateFromFrameProxy()
 - Adding RenderFrameHost traversal tests that exercise the inner/outer
   delegate mechanisms ensuring they work for MPArch Fenced Frames
   (excluding nested fenced frames which do not work yet)
 - Adding a basic navigation test ensuring that a fenced frame's
   RenderFrameHostImpl can indeed navigate successfully

Additionally, this CL does the following:
 - WebContentsObserverConsistencyChecker: We tighten the following
   conditions in the following ways:
     - FrameTree::AddFrame used to not call RenderFrameCreated() on the
       newly-created FTN's RFHI for portals, since it was just a dummy
       FTN and would never have a real RenderFrame. We now do the same
       for MPArch fenced frames (not ShadowDOM ones) from having
       RenderFrameCreated() for the same reason
     - WebContentObserverConsistencyChecker (a test-only WCO
       implementation) implements RenderFrameHostChanged() where it
       verifies that RenderFrameCreated() is called for each newly-added
       child frame, except for portals (due to logic that mirrors
       FrameTree::AddFrame()). We modify this condition to also exclude
       the dummy child FTN's RFHIs for *fenced frames* from this check,
       to mirror our changes to FrameTree::AddFrame().
 - We change IsFencedFrame() => IsFencedFrameRoot() on FrameTreeNode,
   RenderFrameHost, and RenderFrameHostImpl
     - This is consistent with the email sent out on navigation-dev@
       that summarizes the various ways to determine if you're dealing
       with a renderer-host object associated with a fenced frame [1]

-- Lifetime --

This CL also implements the following regarding the fenced frame's
lifetime:

 - Introduce RenderFrameHostImpl::DestroyFencedFrame(FencedFrame*).
   This is a bit complicated, and the flow around this looks like so:
     1.) Something (e.g., a renderer crash) kills the outer delegate
         child FrameTreeNode in the outer FrameTree, which represents
         the inner fenced frame FrameTree
     2.) FencedFrame::OnFrameTreeNodeDestroyed() is invoked, calling...
     3.) RenderFrameHostImpl::DestroyFencedFrame(this), which calls...
     4.) FencedFrame::dtor() is invoked
   While this is complicated, we wanted to mirror the portals flow for
   now, and simplify both at once in a subsequent CL. In a subsequent
   CL, we're aiming to have RFHI explicitly destroy the portals and
   fenced frames rooted at it

[1]: https://groups.google.com/a/chromium.org/g/navigation-dev/c/duZ1Zcs2RLg

Bug: 1123606
Change-Id: Ifad64f808aa078f87dfffcaae1869d73919287d7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3057656
Commit-Queue: Dominic Farolino <dom@chromium.org>
Reviewed-by: Alex Moshchuk <alexmos@chromium.org>
Reviewed-by: Shivani Sharma <shivanisha@chromium.org>
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Reviewed-by: Alexander Timin <altimin@chromium.org>
Reviewed-by: Kentaro Hara <haraken@chromium.org>
Reviewed-by: Christoph Schwering <schwering@google.com>
Cr-Commit-Position: refs/heads/main@{#916618}
This commit is contained in:
Dominic Farolino
2021-08-31 00:37:36 +00:00
committed by Chromium LUCI CQ
parent 68ee28bd80
commit 4bc10eeb30
23 changed files with 525 additions and 132 deletions

@ -42,7 +42,7 @@ namespace {
// and FrameData::parent_form) from its parent form. This is already guaranteed
// because FormData::child_frames does not contain fenced frames. However,
// UpdateTreeOfRendererForm() would still invoke TriggerReparse() to detect the
// parent form. HostedByFencedFrame() should be implemented to suppress this.
// parent form. IsFencedFrameRoot() should be implemented to suppress this.
//
// We also do not want to fill across iframes with the disallowdocumentaccess
// attribute (https://crbug.com/961448). Since disallowdocumentaccess is
@ -51,8 +51,8 @@ namespace {
// FrameData::parent_form for frames that disallow document access, there is no
// immediate need to support it. See https://crrev.com/c/3055422 for a draft
// implementation.
bool HostedByFencedFrame(content::RenderFrameHost* rfh) {
return rfh->HostedByFencedFrame();
bool IsFencedFrameRoot(content::RenderFrameHost* rfh) {
return rfh->IsFencedFrameRoot();
}
} // namespace
@ -423,7 +423,7 @@ void FormForest::UpdateTreeOfRendererForm(FormData* form,
// UpdateTreeOfRendererForm() will be called for the parent form, whose
// FormData::child_frames now include |frame|.
content::RenderFrameHost* parent_rfh = rfh->GetParent();
if (!frame->parent_form && parent_rfh && !HostedByFencedFrame(rfh)) {
if (!frame->parent_form && parent_rfh && !IsFencedFrameRoot(rfh)) {
LocalFrameToken parent_frame_token(
LocalFrameToken(parent_rfh->GetFrameToken().value()));
FrameData* parent_frame = GetFrameData(parent_frame_token);

@ -5,12 +5,166 @@
#include "content/browser/fenced_frame/fenced_frame.h"
#include "base/notreached.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "url/gurl.h"
namespace content {
FencedFrame::FencedFrame() = default;
FencedFrame::FencedFrame(RenderFrameHostImpl& owner_render_frame_host)
: web_contents_(static_cast<WebContentsImpl*>(
WebContents::FromRenderFrameHost(&owner_render_frame_host))),
owner_render_frame_host_(owner_render_frame_host),
frame_tree_(std::make_unique<FrameTree>(
web_contents_->GetBrowserContext(),
/*delegate=*/this,
/*navigation_controller_delegate=*/web_contents_,
/*navigator_delegate=*/web_contents_,
/*render_frame_delegate=*/web_contents_,
/*render_view_delegate=*/web_contents_,
/*render_widget_delegate=*/web_contents_,
/*manager_delegate=*/web_contents_,
/*page_delegate=*/web_contents_,
FrameTree::Type::kFencedFrame)) {
scoped_refptr<SiteInstance> site_instance =
SiteInstance::Create(web_contents_->GetBrowserContext());
// Note that even though this is happening in response to an event in the
// renderer (i.e., the creation of a <fencedframe> element), we are still
// putting `renderer_initiated_creation` as false. This is because that
// parameter is only used when a renderer is creating a new window and has
// already created the main frame for the window, but wants the browser to
// refrain from showing the main frame until the renderer signals the browser
// via the mojom.LocalMainFrameHost.ShowCreatedWindow(). This flow does not
// apply for fenced frames, portals, and prerendered nested FrameTrees, hence
// the decision to mark it as false.
frame_tree_->Init(site_instance.get(), /*renderer_initiated_creation=*/false,
/*main_frame_name=*/"");
FencedFrame::~FencedFrame() = default;
// TODO(crbug.com/1199679): This should be moved to FrameTree::Init.
web_contents_->NotifySwappedFromRenderManager(
/*old_frame=*/nullptr,
frame_tree_->root()->render_manager()->current_frame_host(),
/*is_main_frame=*/true);
CreateProxyAndAttachToOuterFrameTree();
}
FencedFrame::~FencedFrame() {
DCHECK(frame_tree_);
frame_tree_->Shutdown();
frame_tree_.reset();
if (on_destroyed_callback_for_testing_)
std::move(on_destroyed_callback_for_testing_).Run();
}
void FencedFrame::Navigate(const GURL& url) {
FrameTreeNode* inner_root = frame_tree_->root();
// TODO(crbug.com/1237552): Resolve the discussion around navigations being
// treated as downloads, and implement the correct thing.
blink::NavigationDownloadPolicy download_policy;
// Note `initiator_frame_token` here *always* corresponds to the outer render
// frame host, however crbug.com/1074422 points out that it is possible that
// another same-origin-domain document can synchronously script the document
// hosted in the outer render frame host, and thus be the true initiator of
// the navigation even though this wouldn't be reflected here. See that bug
// for more discussion and plans for an eventual resolution.
const blink::LocalFrameToken initiator_frame_token =
owner_render_frame_host_.GetFrameToken();
inner_root->navigator().NavigateFromFrameProxy(
inner_root->current_frame_host(), url, &initiator_frame_token,
owner_render_frame_host_.GetProcess()->GetID(),
owner_render_frame_host_.GetLastCommittedOrigin(),
owner_render_frame_host_.GetSiteInstance(), content::Referrer(),
ui::PAGE_TRANSITION_LINK,
/*should_replace_current_entry=*/false, download_policy, "GET",
/*post_body=*/nullptr, /*extra_headers=*/"",
/*blob_url_loader_factory=*/nullptr,
network::mojom::SourceLocation::New(), /*has_user_gesture=*/false,
absl::nullopt);
}
void FencedFrame::DidStopLoading() {
if (on_did_finish_loading_callback_for_testing_)
std::move(on_did_finish_loading_callback_for_testing_).Run();
}
bool FencedFrame::IsHidden() {
return web_contents_->IsHidden();
}
int FencedFrame::GetOuterDelegateFrameTreeNodeId() {
DCHECK(outer_delegate_frame_tree_node_);
return outer_delegate_frame_tree_node_->frame_tree_node_id();
}
RenderFrameProxyHost* FencedFrame::GetProxyToInnerMainFrame() {
DCHECK(proxy_to_inner_main_frame_);
return proxy_to_inner_main_frame_;
}
void FencedFrame::OnFrameTreeNodeDestroyed(
FrameTreeNode* outer_delegate_frame_tree_node) {
DCHECK_EQ(outer_delegate_frame_tree_node_, outer_delegate_frame_tree_node);
owner_render_frame_host_.DestroyFencedFrame(*this);
// Don't use `this` after this point, as it is destroyed.
}
void FencedFrame::CreateProxyAndAttachToOuterFrameTree() {
// The fenced frame should not already be attached.
DCHECK(!outer_delegate_frame_tree_node_);
outer_delegate_frame_tree_node_ =
owner_render_frame_host_.frame_tree()->AddFrame(
&owner_render_frame_host_,
owner_render_frame_host_.GetProcess()->GetID(),
owner_render_frame_host_.GetProcess()->GetNextRoutingID(),
/*frame_remote=*/mojo::NullAssociatedRemote(),
mojo::PendingRemote<blink::mojom::BrowserInterfaceBroker>()
.InitWithNewPipeAndPassReceiver(),
/*policy_container_bind_params=*/nullptr,
blink::mojom::TreeScopeType::kDocument, "", "", true,
blink::LocalFrameToken(), base::UnguessableToken::Create(),
blink::FramePolicy(), blink::mojom::FrameOwnerProperties(), false,
blink::mojom::FrameOwnerElementType::kFencedframe);
// Connect the outer delegate RenderFrameHost with the inner main
// FrameTreeNode. This allows us to traverse from the outer delegate RFH
// inward, to the inner fenced frame FrameTree.
outer_delegate_frame_tree_node_->current_frame_host()
->set_inner_tree_main_frame_tree_node_id(
frame_tree_->root()->frame_tree_node_id());
// We observe the outer node because when it is destroyed by its parent
// RenderFrameHostImpl, we respond to its destruction by destroying ourself
// and the inner fenced frame FrameTree.
outer_delegate_frame_tree_node_->AddObserver(this);
FrameTreeNode* inner_root = frame_tree_->root();
proxy_to_inner_main_frame_ =
inner_root->render_manager()->CreateOuterDelegateProxy(
owner_render_frame_host_.GetSiteInstance());
inner_root->current_frame_host()->PropagateEmbeddingTokenToParentFrame();
}
base::UnguessableToken FencedFrame::GetDevToolsFrameToken() const {
DCHECK(frame_tree_);
return frame_tree_->GetMainFrame()->GetDevToolsFrameToken();
}
void FencedFrame::WaitForDidStopLoadingForTesting() {
if (!frame_tree_->IsLoading())
return;
base::RunLoop run_loop;
on_did_finish_loading_callback_for_testing_ = run_loop.QuitClosure();
run_loop.Run();
}
void FencedFrame::SetOnDestroyedCallbackForTesting(base::OnceClosure cb) {
DCHECK(!on_destroyed_callback_for_testing_);
on_destroyed_callback_for_testing_ = std::move(cb);
}
} // namespace content

@ -8,21 +8,30 @@
#include <memory>
#include <string>
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/common/content_export.h"
#include "content/common/frame.mojom.h"
#include "mojo/public/cpp/bindings/associated_receiver.h"
#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
#include "third_party/blink/public/mojom/fenced_frame/fenced_frame.mojom.h"
class GURL;
namespace content {
class RenderFrameHostImpl;
class RenderFrameProxyHost;
class WebContentsImpl;
// This is the browser-side host object for the <fencedframe> element
// implemented in Blink. This is only used for the MPArch version of fenced
// frames, not the ShadowDOM implementation. It is owned by and stored directly
// on `RenderFrameHostImpl` for now.
class CONTENT_EXPORT FencedFrame : public blink::mojom::FencedFrameOwnerHost {
// on `RenderFrameHostImpl`.
class CONTENT_EXPORT FencedFrame : public blink::mojom::FencedFrameOwnerHost,
public FrameTree::Delegate,
public FrameTreeNode::Observer {
public:
FencedFrame();
explicit FencedFrame(RenderFrameHostImpl& owner_render_frame_host);
~FencedFrame() override;
void Bind(mojo::PendingAssociatedReceiver<blink::mojom::FencedFrameOwnerHost>
@ -30,7 +39,73 @@ class CONTENT_EXPORT FencedFrame : public blink::mojom::FencedFrameOwnerHost {
receiver_.Bind(std::move(receiver));
}
// blink::mojom::FencedFrameOwnerHost implementation.
void Navigate(const GURL& url) override;
// FrameTree::Delegate.
void DidStartLoading(FrameTreeNode* frame_tree_node,
bool to_different_document) override {}
void DidStopLoading() override;
void DidChangeLoadProgress() override {}
bool IsHidden() override;
void NotifyPageChanged(PageImpl& page) override {}
int GetOuterDelegateFrameTreeNodeId() override;
// FrameTreeNode::Observer.
// We are monitoring the destruction of the outer delegate dummy
// FrameTreeNode. That node is a direct child of `owner_render_frame_host_`,
// so in order to make the lifetime of `this` fenced frame perfectly match
// that of a traditional child node, we tie ourselves directly to its
// destruction.
void OnFrameTreeNodeDestroyed(FrameTreeNode*) override;
// TODO(crbug.com/1123606): Make FencedFrame a NavigationControllerDelegate
// to suppress certain events about the fenced frame from being exposed to the
// outer WebContents.
RenderFrameProxyHost* GetProxyToInnerMainFrame();
// Returns the devtools frame token of the fenced frame's inner FrameTree's
// main frame.
base::UnguessableToken GetDevToolsFrameToken() const;
// For testing only.
void WaitForDidStopLoadingForTesting();
void SetOnDestroyedCallbackForTesting(base::OnceClosure cb);
private:
// Called when a fenced frame is created from a synchronous IPC from the
// renderer. This creates a proxy to the main frame of the inner `FrameTree`,
// for use by the embedding RenderFrameHostImpl.
void CreateProxyAndAttachToOuterFrameTree();
WebContentsImpl* const web_contents_;
// This is the RenderFrameHostImpl that owns the <fencedframe> element in the
// renderer.
RenderFrameHostImpl& owner_render_frame_host_;
// The FrameTreeNode in the outer FrameTree that represents the inner fenced
// frame FrameTree. It is a "dummy" child FrameTreeNode that `this` is
// responsible for adding as a child of `owner_render_frame_host_`; it is
// initially null, and only set in the constructor (indirectly via
// `CreateProxyAndAttachToOuterFrameTree()`).
// Furthermore, the lifetime of `this` is directly tied to it (see
// `OnFrameTreeNodeDestroyed()`).
FrameTreeNode* outer_delegate_frame_tree_node_ = nullptr;
// This is for use by the "outer" FrameTree (i.e., the one that
// `owner_render_frame_host_` is associated with). It is set in the
// constructor. Initially null, and only set in the constructor (indirectly
// via `CreateProxyAndAttachToOuterFrameTree()`).
RenderFrameProxyHost* proxy_to_inner_main_frame_ = nullptr;
// The FrameTree that we create to host the "inner" fenced frame contents.
std::unique_ptr<FrameTree> frame_tree_;
base::OnceClosure on_did_finish_loading_callback_for_testing_;
base::OnceClosure on_destroyed_callback_for_testing_;
// Receives messages from the frame owner element in Blink.
mojo::AssociatedReceiver<blink::mojom::FencedFrameOwnerHost> receiver_{this};
};

@ -3,21 +3,25 @@
// found in the LICENSE file.
#include "base/callback.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "content/browser/fenced_frame/fenced_frame.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/frame.mojom-test-utils.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/test_frame_navigation_observer.h"
#include "content/shell/browser/shell.h"
#include "content/shell/browser/shell_browser_context.h"
#include "content/shell/browser/shell_content_browser_client.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/mojom/fenced_frame/fenced_frame.mojom.h"
#include "url/gurl.h"
@ -37,23 +41,125 @@ class FencedFrameBrowserTest : public ContentBrowserTest {
ASSERT_TRUE(embedded_test_server()->Start());
}
FencedFrame* CreateAndGetFencedFrame() {
RenderFrameHostImplWrapper primary_rfh(primary_main_frame_host());
size_t previous_fenced_frame_count = primary_rfh->GetFencedFrames().size();
EXPECT_TRUE(ExecJs(
primary_rfh.get(),
"document.body.appendChild(document.createElement('fencedframe'));"));
std::vector<FencedFrame*> fenced_frames = primary_rfh->GetFencedFrames();
EXPECT_EQ(previous_fenced_frame_count + 1, fenced_frames.size());
// Return the most-recently added FencedFrame.
return fenced_frames.back();
}
WebContentsImpl* web_contents() {
return static_cast<WebContentsImpl*>(shell()->web_contents());
}
RenderFrameHostImpl* primary_main_frame_host() {
return web_contents()->GetMainFrame();
}
// Note that this method assumes the fenced frame was the first child added to
// the primary render frame host.
FrameTreeNode* fenced_frame_root_node() {
RenderFrameHostImplWrapper dummy_child_frame(
primary_main_frame_host()->child_at(0)->current_frame_host());
EXPECT_NE(dummy_child_frame->inner_tree_main_frame_tree_node_id(),
FrameTreeNode::kFrameTreeNodeInvalidId);
return FrameTreeNode::GloballyFindByID(
dummy_child_frame->inner_tree_main_frame_tree_node_id());
}
RenderFrameHostImpl* fenced_frame_root_host() {
return fenced_frame_root_node()->current_frame_host();
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
// Tests that the renderer can create a <fencedframe> that results in a
// browser-side content::FencedFrame also being created.
IN_PROC_BROWSER_TEST_F(FencedFrameBrowserTest, CreateFencedFrame) {
IN_PROC_BROWSER_TEST_F(FencedFrameBrowserTest, CreateFromScriptAndDestroy) {
EXPECT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL(
"fencedframe.test", "/title1.html")));
WebContentsImpl* web_contents_impl =
static_cast<WebContentsImpl*>(shell()->web_contents());
RenderFrameHostImpl* primary_rfh = web_contents_impl->GetMainFrame();
RenderFrameHostImplWrapper primary_rfh(primary_main_frame_host());
EXPECT_TRUE(ExecJs(
primary_rfh,
"document.body.appendChild(document.createElement('fencedframe'));"));
EXPECT_EQ(1U, primary_rfh->GetFencedFrames().size());
FencedFrame* fenced_frame = CreateAndGetFencedFrame();
EXPECT_NE(fenced_frame, nullptr);
RenderFrameHostImplWrapper inner_fenced_frame_rfh(fenced_frame_root_host());
// Test that the outer => inner delegate mechanism works correctly.
EXPECT_THAT(
CollectAllRenderFrameHosts(primary_rfh.get()),
testing::ElementsAre(primary_rfh.get(), inner_fenced_frame_rfh.get()));
// Test that the inner => outer delegate mechanism works correctly.
EXPECT_EQ(inner_fenced_frame_rfh->ParentOrOuterDelegateFrame(),
primary_rfh.get());
// Test `FrameTreeNode::IsFencedFrameRoot()`.
EXPECT_FALSE(web_contents()->GetFrameTree()->root()->IsFencedFrameRoot());
EXPECT_FALSE(primary_rfh->child_at(0)->IsFencedFrameRoot());
EXPECT_TRUE(fenced_frame_root_node()->IsFencedFrameRoot());
// Test `FrameTreeNode::IsInFencedFrameTree()`.
EXPECT_FALSE(web_contents()->GetFrameTree()->root()->IsInFencedFrameTree());
EXPECT_FALSE(primary_rfh->child_at(0)->IsInFencedFrameTree());
EXPECT_TRUE(fenced_frame_root_node()->IsInFencedFrameTree());
base::RunLoop destroyed_run_loop;
fenced_frame->SetOnDestroyedCallbackForTesting(
destroyed_run_loop.QuitClosure());
EXPECT_TRUE(ExecJs(primary_rfh.get(),
"document.querySelector('fencedframe').remove();"));
destroyed_run_loop.Run();
EXPECT_TRUE(primary_rfh->GetFencedFrames().empty());
EXPECT_TRUE(inner_fenced_frame_rfh.IsDestroyed());
}
IN_PROC_BROWSER_TEST_F(FencedFrameBrowserTest, CreateFromParser) {
const GURL top_level_url = embedded_test_server()->GetURL(
"fencedframe.test", "/fenced_frames/basic.html");
EXPECT_TRUE(NavigateToURL(shell(), top_level_url));
// The fenced frame is set-up synchronously, so it should exist immediately.
EXPECT_TRUE(fenced_frame_root_host());
}
IN_PROC_BROWSER_TEST_F(FencedFrameBrowserTest, Navigation) {
const GURL top_level_url =
embedded_test_server()->GetURL("fencedframe.test", "/title1.html");
EXPECT_TRUE(NavigateToURL(shell(), top_level_url));
RenderFrameHostImplWrapper primary_rfh(primary_main_frame_host());
FencedFrame* fenced_frame = CreateAndGetFencedFrame();
EXPECT_NE(fenced_frame, nullptr);
RenderFrameHostImplWrapper inner_fenced_frame_rfh(fenced_frame_root_host());
const GURL fenced_frame_url =
embedded_test_server()->GetURL("fencedframe.test", "/title2.html");
EXPECT_TRUE(
ExecJs(primary_rfh.get(),
JsReplace("document.querySelector('fencedframe').src = $1;",
fenced_frame_url.spec())));
fenced_frame->WaitForDidStopLoadingForTesting();
// Test that a fenced frame navigation does not impact the primary main
// frame...
EXPECT_EQ(top_level_url, primary_rfh->GetLastCommittedURL());
// ... but should target the correct frame.
EXPECT_EQ(fenced_frame_url, fenced_frame_root_host()->GetLastCommittedURL());
EXPECT_EQ(url::Origin::Create(fenced_frame_url),
fenced_frame_root_host()->GetLastCommittedOrigin());
}
} // namespace content

@ -10,30 +10,17 @@
namespace content {
class FencedFrameTreeNodeTest
: public RenderViewHostImplTestHarness,
public ::testing::WithParamInterface<
blink::features::FencedFramesImplementationType> {
class FencedFrameTreeNodeTest : public RenderViewHostImplTestHarness {
public:
// Provides meaningful param names instead of /0 and /1.
static std::string DescribeParams(
const ::testing::TestParamInfo<ParamType>& info) {
switch (info.param) {
case blink::features::FencedFramesImplementationType::kShadowDOM:
return "ShadowDOM";
case blink::features::FencedFramesImplementationType::kMPArch:
return "MPArch";
}
}
FencedFrameTreeNodeTest() {
// Note that we only run these tests for the ShadowDOM implementation of
// fenced frames, due to how they add subframes in a way that is very
// specific to the ShadowDOM implementation, and not suitable for the MPArch
// implementation. We test the MPArch implementation in
// `FencedFrameBrowserTest`.
scoped_feature_list_.InitAndEnableFeatureWithParameters(
blink::features::kFencedFrames,
{{"implementation_type",
GetParam() ==
blink::features::FencedFramesImplementationType::kShadowDOM
? "shadow_dom"
: "mparch"}});
{{"implementation_type", "shadow_dom"}});
}
FrameTreeNode* AddFrame(FrameTree* frame_tree,
@ -56,7 +43,7 @@ class FencedFrameTreeNodeTest
base::test::ScopedFeatureList scoped_feature_list_;
};
TEST_P(FencedFrameTreeNodeTest, IsFencedFrame) {
TEST_F(FencedFrameTreeNodeTest, IsFencedFrameHelpers) {
main_test_rfh()->InitializeRenderFrameIfNeeded();
FrameTree* frame_tree = contents()->GetFrameTree();
FrameTreeNode* root = frame_tree->root();
@ -66,7 +53,7 @@ TEST_P(FencedFrameTreeNodeTest, IsFencedFrame) {
constexpr auto kOwnerType = blink::mojom::FrameOwnerElementType::kIframe;
AddFrame(frame_tree, root->current_frame_host(), process_id, 14,
blink::FramePolicy(), kOwnerType);
EXPECT_FALSE(root->child_at(0)->IsFencedFrame());
EXPECT_FALSE(root->child_at(0)->IsFencedFrameRoot());
EXPECT_FALSE(root->child_at(0)->IsInFencedFrameTree());
// Add a fenced frame.
@ -77,49 +64,29 @@ TEST_P(FencedFrameTreeNodeTest, IsFencedFrame) {
policy.is_fenced = true;
AddFrame(frame_tree, root->current_frame_host(), process_id, 15, policy,
kFencedframeOwnerType);
// TODO(crbug.com/1123606): Simulate the tree to be fenced frame tree until
// the MPArch supporting code lands.
if (blink::features::kFencedFramesImplementationTypeParam.Get() ==
blink::features::FencedFramesImplementationType::kMPArch) {
root->child_at(1)->frame_tree()->SetFencedFrameTreeForTesting();
}
// TODO(crbug.com/1123606): Once the MPArch code lands, the FrameTreeNode that
// we call these methods on should be either `root->child_at(1)`, or the inner
// main FrameTreeNode that this node points to if one exists.
EXPECT_TRUE(root->child_at(1)->IsFencedFrame());
EXPECT_TRUE(root->child_at(1)->IsFencedFrameRoot());
EXPECT_TRUE(root->child_at(1)->IsInFencedFrameTree());
// Add a nested iframe in the fenced frame.
// main-frame -> fenced-frame -> iframe.
AddFrame(frame_tree, root->child_at(1)->current_frame_host(), process_id, 16,
blink::FramePolicy(), kOwnerType);
EXPECT_FALSE(root->child_at(1)->child_at(0)->IsFencedFrame());
EXPECT_FALSE(root->child_at(1)->child_at(0)->IsFencedFrameRoot());
EXPECT_TRUE(root->child_at(1)->child_at(0)->IsInFencedFrameTree());
// Add a nested fenced frame inside the existing fenced frame.
// main-frame -> fenced-frame -> fenced-frame.
AddFrame(frame_tree, root->child_at(1)->current_frame_host(), process_id, 17,
policy, kFencedframeOwnerType);
// TODO(crbug.com/1123606): Once the MPArch code lands, the FrameTreeNode that
// we call these methods on should be either `root->child_at(1)`, or the inner
// main FrameTreeNode that this node points to if one exists.
EXPECT_TRUE(root->child_at(1)->child_at(1)->IsFencedFrame());
EXPECT_TRUE(root->child_at(1)->child_at(1)->IsFencedFrameRoot());
EXPECT_TRUE(root->child_at(1)->child_at(1)->IsInFencedFrameTree());
// Add a nested fenced frame inside the iframe added above.
// main-frame -> iframe -> fenced-frame.
AddFrame(frame_tree, root->child_at(0)->current_frame_host(), process_id, 18,
policy, kFencedframeOwnerType);
EXPECT_TRUE(root->child_at(0)->child_at(0)->IsFencedFrame());
EXPECT_TRUE(root->child_at(0)->child_at(0)->IsFencedFrameRoot());
EXPECT_TRUE(root->child_at(0)->child_at(0)->IsInFencedFrameTree());
}
INSTANTIATE_TEST_SUITE_P(
All,
FencedFrameTreeNodeTest,
::testing::Values(
blink::features::FencedFramesImplementationType::kShadowDOM,
blink::features::FencedFramesImplementationType::kMPArch),
&FencedFrameTreeNodeTest::DescribeParams);
} // namespace content

@ -300,11 +300,18 @@ FrameTreeNode* FrameTree::AddFrame(
bool was_discarded,
blink::mojom::FrameOwnerElementType owner_type) {
CHECK_NE(new_routing_id, MSG_ROUTING_NONE);
// Normally this path is for blink adding a child local frame. But portals are
// making a remote frame, as the local frame is only created in a nested
// FrameTree.
DCHECK_NE(frame_remote.is_valid(),
owner_type == blink::mojom::FrameOwnerElementType::kPortal);
// Normally this path is for blink adding a child local frame. But both
// portals and fenced frames add a dummy child frame that never gets a
// corresponding RenderFrameImpl in any renderer process, and therefore its
// `frame_remote` is invalid. Also its RenderFrameHostImpl is exempt from
// having `RenderFrameCreated()` called on it (see later in this method, as
// well as `WebContentsObserverConsistencyChecker::RenderFrameHostChanged()`).
bool is_dummy_frame_for_portal_or_fenced_frame =
owner_type == blink::mojom::FrameOwnerElementType::kPortal ||
(owner_type == blink::mojom::FrameOwnerElementType::kFencedframe &&
blink::features::kFencedFramesImplementationTypeParam.Get() ==
blink::features::FencedFramesImplementationType::kMPArch);
DCHECK_NE(frame_remote.is_valid(), is_dummy_frame_for_portal_or_fenced_frame);
// A child frame always starts with an initial empty document, which means
// it is in the same SiteInstance as the parent frame. Ensure that the process
@ -361,9 +368,9 @@ FrameTreeNode* FrameTree::AddFrame(
// exists in the renderer process.
// For consistency with navigating to a new RenderFrameHost case, we dispatch
// RenderFrameCreated before RenderFrameHostChanged.
if (added_node->frame_owner_element_type() !=
blink::mojom::FrameOwnerElementType::kPortal) {
// Portals do not have a live RenderFrame in the renderer process.
if (!is_dummy_frame_for_portal_or_fenced_frame) {
// The outer dummy FrameTreeNode for both portals and fenced frames does not
// have a live RenderFrame in the renderer process.
added_node->current_frame_host()->RenderFrameCreated();
}

@ -157,7 +157,21 @@ class CONTENT_EXPORT FrameTree {
// This FrameTree is used to prerender a page in the background which is
// invisible to the user.
kPrerender
kPrerender,
// This FrameTree is used to host the contents of a <fencedframe> element.
// Even for <fencedframe>s hosted inside a prerendered page, the FrameTree
// associated with the fenced frame will be kFencedFrame, but the
// RenderFrameHosts inside of it will have their lifecycle state set to
// `RenderFrameHost::LifecycleState::kActive`.
//
// TODO(crbug.com/1232528, crbug.com/1244274): We should either:
// * Fully support nested FrameTrees inside prerendered pages, in which
// case all of the RenderFrameHosts inside the nested trees should have
// their lifecycle state set to kPrerendering, or...
// * Ban nested FrameTrees in prerendered pages, and therefore obsolete the
// above paragraph about <fencedframe>s hosted in prerendered pages.
kFencedFrame
};
// A set of delegates are remembered here so that we can create
@ -407,12 +421,6 @@ class CONTENT_EXPORT FrameTree {
// Must be called before FrameTree is destroyed.
void Shutdown();
// Returns true if this is a fenced frame tree.
// TODO(crbug.com/1123606): Integrate this with the MPArch based fenced frame
// code once that lands.
bool IsFencedFrameTree() const { return is_fenced_frame_tree_; }
void SetFencedFrameTreeForTesting() { is_fenced_frame_tree_ = true; }
bool IsBeingDestroyed() const { return is_being_destroyed_; }
private:
@ -464,10 +472,6 @@ class CONTENT_EXPORT FrameTree {
// Indicates type of frame tree.
const Type type_;
// TODO(crbug.com/1123606): Integrate this with the MPArch based fenced frame
// code once that lands. Possibly this will then be part of |type_|.
bool is_fenced_frame_tree_ = false;
bool is_being_destroyed_ = false;
#if DCHECK_IS_ON()

@ -897,7 +897,7 @@ IN_PROC_BROWSER_TEST_F(FencedFrameTreeBrowserTest,
}
EXPECT_EQ(1U, root->child_count());
EXPECT_TRUE(root->child_at(0)->IsFencedFrame());
EXPECT_TRUE(root->child_at(0)->IsFencedFrameRoot());
EXPECT_TRUE(root->child_at(0)->IsInFencedFrameTree());
GURL https_url(https_server()->GetURL("a.test", "/title1.html"));
@ -955,7 +955,7 @@ IN_PROC_BROWSER_TEST_F(FencedFrameTreeBrowserTest, CheckFencedFrameNoCookies) {
FrameTreeNode* fenced_frame = root_rfh->child_at(0);
EXPECT_TRUE(fenced_frame->IsFencedFrame());
EXPECT_TRUE(fenced_frame->IsFencedFrameRoot());
EXPECT_TRUE(fenced_frame->IsInFencedFrameTree());
GURL https_url(https_server()->GetURL("a.test", "/title1.html"));
@ -987,7 +987,7 @@ IN_PROC_BROWSER_TEST_F(FencedFrameTreeBrowserTest, CheckFencedFrameNoCookies) {
"var f1 = document.createElement('iframe');"
"document.body.appendChild(f1);"));
EXPECT_EQ(1U, fenced_frame->child_count());
EXPECT_FALSE(fenced_frame->child_at(0)->IsFencedFrame());
EXPECT_FALSE(fenced_frame->child_at(0)->IsFencedFrameRoot());
EXPECT_TRUE(fenced_frame->child_at(0)->IsInFencedFrameTree());
std::string navigate_script = JsReplace("f1.src = $1;", main_url.spec());
@ -1033,7 +1033,7 @@ IN_PROC_BROWSER_TEST_F(FencedFrameTreeBrowserTest, CheckIsFencedFrame) {
"var f = document.createElement('fencedframe');"
"document.body.appendChild(f);"));
EXPECT_EQ(1U, root->child_count());
EXPECT_TRUE(root->child_at(0)->IsFencedFrame());
EXPECT_TRUE(root->child_at(0)->IsFencedFrameRoot());
EXPECT_TRUE(root->child_at(0)->IsInFencedFrameTree());
// Add an iframe.
@ -1041,7 +1041,7 @@ IN_PROC_BROWSER_TEST_F(FencedFrameTreeBrowserTest, CheckIsFencedFrame) {
"var f = document.createElement('iframe');"
"document.body.appendChild(f);"));
EXPECT_EQ(2U, root->child_count());
EXPECT_FALSE(root->child_at(1)->IsFencedFrame());
EXPECT_FALSE(root->child_at(1)->IsFencedFrameRoot());
EXPECT_FALSE(root->child_at(1)->IsInFencedFrameTree());
// Add a nested iframe inside the fenced frame.
@ -1049,7 +1049,7 @@ IN_PROC_BROWSER_TEST_F(FencedFrameTreeBrowserTest, CheckIsFencedFrame) {
"var f = document.createElement('iframe');"
"document.body.appendChild(f);"));
EXPECT_EQ(1U, root->child_at(0)->child_count());
EXPECT_FALSE(root->child_at(0)->child_at(0)->IsFencedFrame());
EXPECT_FALSE(root->child_at(0)->child_at(0)->IsFencedFrameRoot());
EXPECT_TRUE(root->child_at(0)->child_at(0)->IsInFencedFrameTree());
// Add a nested fenced frame.
@ -1057,7 +1057,7 @@ IN_PROC_BROWSER_TEST_F(FencedFrameTreeBrowserTest, CheckIsFencedFrame) {
"var f = document.createElement('fencedframe');"
"document.body.appendChild(f);"));
EXPECT_EQ(2U, root->child_at(0)->child_count());
EXPECT_TRUE(root->child_at(0)->child_at(1)->IsFencedFrame());
EXPECT_TRUE(root->child_at(0)->child_at(1)->IsFencedFrameRoot());
EXPECT_TRUE(root->child_at(0)->child_at(1)->IsInFencedFrameTree());
}

@ -823,21 +823,14 @@ bool FrameTreeNode::HasNavigation() {
return false;
}
bool FrameTreeNode::IsFencedFrame() const {
bool FrameTreeNode::IsFencedFrameRoot() const {
if (!blink::features::IsFencedFramesEnabled())
return false;
switch (blink::features::kFencedFramesImplementationTypeParam.Get()) {
case blink::features::FencedFramesImplementationType::kMPArch: {
// TODO(crbug.com/1123606): Once the MPArch code lands, this can be
// simplified to frame_tree()->IsFencedFrameTree() and IsMainFrame()
// instead of checking the frame_owner_element_type().
if (frame_owner_element_type() ==
blink::mojom::FrameOwnerElementType::kFencedframe) {
DCHECK(frame_tree()->IsFencedFrameTree());
return true;
}
return false;
return IsMainFrame() &&
frame_tree()->type() == FrameTree::Type::kFencedFrame;
}
case blink::features::FencedFramesImplementationType::kShadowDOM: {
return effective_frame_policy().is_fenced;
@ -853,7 +846,7 @@ bool FrameTreeNode::IsInFencedFrameTree() const {
switch (blink::features::kFencedFramesImplementationTypeParam.Get()) {
case blink::features::FencedFramesImplementationType::kMPArch:
return frame_tree()->IsFencedFrameTree();
return frame_tree()->type() == FrameTree::Type::kFencedFrame;
case blink::features::FencedFramesImplementationType::kShadowDOM: {
auto* node = this;
while (node) {

@ -493,7 +493,7 @@ class CONTENT_EXPORT FrameTreeNode {
// implementation this only returns true if |this| is the actual
// root node of the inner FrameTree and not the proxy FrameTreeNode in the
// outer FrameTree.
bool IsFencedFrame() const;
bool IsFencedFrameRoot() const;
// Returns false if fenced frames are disabled. Returns true if the
// feature is enabled and if |this| or any of its ancestor nodes is a

@ -1732,9 +1732,9 @@ void NavigationRequest::OnPrerenderingActivationChecksComplete(
void NavigationRequest::BeginNavigationImpl() {
SetState(WILL_START_NAVIGATION);
// if this is a fenced frame with a urn:uuid then convert it to a url before
// If this is a fenced frame with a urn:uuid then convert it to a url before
// starting the request.
if (frame_tree_node_->IsFencedFrame() && common_params_->url.is_valid() &&
if (frame_tree_node_->IsFencedFrameRoot() && common_params_->url.is_valid() &&
common_params_->url.scheme() == url::kUrnScheme) {
// TODO(crbug.com/1123606): With MPArch, make sure that the mapping is
// retrieved from the primary root instead of this tree's root.

@ -41,8 +41,10 @@ void PageImpl::GetManifest(GetManifestCallback callback) {
}
bool PageImpl::IsPrimary() {
// TODO(https://crbug.com/1222722): Query RenderFrameHost::IsInFencedFrame()
// when it is available.
// TODO(1244137): Check for portals as well, once they are migrated to MPArch.
if (main_document_.IsFencedFrameRoot())
return false;
return main_document_.lifecycle_state() ==
RenderFrameHostImpl::LifecycleStateImpl::kActive;
}

@ -1949,8 +1949,8 @@ bool RenderFrameHostImpl::IsDescendantOf(RenderFrameHost* ancestor) {
return false;
}
bool RenderFrameHostImpl::HostedByFencedFrame() {
return frame_tree_node_->IsFencedFrame();
bool RenderFrameHostImpl::IsFencedFrameRoot() {
return frame_tree_node_->IsFencedFrameRoot();
}
void RenderFrameHostImpl::ForEachRenderFrameHost(
@ -6660,7 +6660,7 @@ void RenderFrameHostImpl::CreatePortal(
RenderFrameProxyHost* proxy_host = (*it)->CreateProxyAndAttachPortal();
// Since the portal is newly created and has yet to commit a navigation, this
// state is trivial.
// state is default-constructed.
const blink::mojom::FrameReplicationState& initial_replicated_state =
proxy_host->frame_tree_node()->current_replication_state();
DCHECK(initial_replicated_state.origin.opaque());
@ -6712,10 +6712,27 @@ void RenderFrameHostImpl::DestroyFencedFrame(FencedFrame& fenced_frame) {
void RenderFrameHostImpl::CreateFencedFrame(
mojo::PendingAssociatedReceiver<blink::mojom::FencedFrameOwnerHost>
pending_receiver) {
fenced_frames_.push_back(std::make_unique<FencedFrame>());
pending_receiver,
CreateFencedFrameCallback callback) {
fenced_frames_.push_back(std::make_unique<FencedFrame>(*this));
FencedFrame* fenced_frame = fenced_frames_.back().get();
fenced_frame->Bind(std::move(pending_receiver));
RenderFrameProxyHost* proxy_host = fenced_frame->GetProxyToInnerMainFrame();
// Since the fenced frame is newly created and has yet to commit a navigation,
// this state is default-constructed.
const blink::mojom::FrameReplicationState& initial_replicated_state =
proxy_host->frame_tree_node()->current_replication_state();
// Note that a default-constructed `FrameReplicationState` always has an
// opaque origin, simply because the frame hasn't had any navigations yet.
// Fenced frames (after their first navigation) do not have opaque origins,
// and this default-constructed FRS does not impact that.
DCHECK(initial_replicated_state.origin.opaque());
std::move(callback).Run(
proxy_host->GetRoutingID(), initial_replicated_state.Clone(),
proxy_host->GetFrameToken(), fenced_frame->GetDevToolsFrameToken());
}
void RenderFrameHostImpl::CreateNewPopupWidget(
@ -11882,7 +11899,7 @@ RenderFrameHostImpl* RenderFrameHostImpl::ParentOrOuterDelegateFrame() {
if (parent_)
return parent_;
// Find the parent in the WebContentsTree (GuestView or Portal).
// Find the parent in the outer embedder (GuestView, Portal, or Fenced Frame).
FrameTreeNode* frame_in_embedder =
frame_tree_node()->render_manager()->GetOuterDelegateNode();
if (frame_in_embedder)

@ -317,7 +317,7 @@ class CONTENT_EXPORT RenderFrameHostImpl
PageImpl& GetPage() override;
std::vector<RenderFrameHost*> GetFramesInSubtree() override;
bool IsDescendantOf(RenderFrameHost*) override;
bool HostedByFencedFrame() override;
bool IsFencedFrameRoot() override;
void ForEachRenderFrameHost(FrameIterationCallback on_frame) override;
void ForEachRenderFrameHost(
FrameIterationAlwaysContinueCallback on_frame) override;
@ -985,11 +985,21 @@ class CONTENT_EXPORT RenderFrameHostImpl
// tree).
//
// Please note that Prerender2 is an experimental feature behind the flag.
//
// Note that at the moment, this state is *not* used for RenderFrameHosts in
// nested FrameTrees inside prerendered pages. See crbug.com/1232528,
// crbug.com/1244274 for more discussion on whether or not we should support
// nested FrameTrees inside prerendered pages.
kPrerendering,
// This state corresponds to when a RenderFrameHost is the current one in
// its RenderFrameHostManager and FrameTreeNode for a primary frame tree. In
// this state, RenderFrameHost is visible to the user.
// its RenderFrameHostManager/FrameTreeNode inside a primary FrameTree or
// its descendant FrameTrees. In this state, RenderFrameHost is visible to
// the user. TODO(crbug.com/1232528, crbug.com/1244274): At the moment,
// prerendered pages implicitly support nested frame trees, whose
// RenderFrameHost's are always kActive even though they are not shown to
// the user. We need to formally determine if prerenders should support
// nested FrameTrees.
//
// Transition to kActive state may happen from one of:
// - kSpeculative -- when a speculative RenderFrameHost commits to make it
@ -1003,6 +1013,12 @@ class CONTENT_EXPORT RenderFrameHostImpl
// RenderFrameHost can also be created in this state for an empty document
// in a FrameTreeNode (e.g initializing root and child in an empty
// primary FrameTree).
//
// Note that this state is also used for nested pages e.g., the
// RenderFrameHosts in <fencedframe> and <portal> elements, as these nested
// contexts do not get their own lifecycle state. A RenderFrameHost can tell
// if it is in a <fencedframe> however, by checking its `FrameTree`'s type.
// This will be true for portals as well once they are migrated to MPArch.
kActive,
// This state corresponds to when RenderFrameHost is stored in
@ -1892,7 +1908,7 @@ class CONTENT_EXPORT RenderFrameHostImpl
void set_inner_tree_main_frame_tree_node_id(int id) {
inner_tree_main_frame_tree_node_id_ = id;
}
int inner_tree_main_frame_tree_node_id() {
int inner_tree_main_frame_tree_node_id() const {
return inner_tree_main_frame_tree_node_id_;
}
@ -2506,7 +2522,8 @@ class CONTENT_EXPORT RenderFrameHostImpl
AdoptPortalCallback callback) override;
void CreateFencedFrame(
mojo::PendingAssociatedReceiver<blink::mojom::FencedFrameOwnerHost>
pending_receiver) override;
pending_receiver,
CreateFencedFrameCallback callback) override;
void GetKeepAliveHandleFactory(
mojo::PendingReceiver<blink::mojom::KeepAliveHandleFactory> receiver)
override;

@ -641,8 +641,26 @@ interface FrameHost {
// frame tree that will host the contents of the <fencedframe> element.
// `fenced_frame` - the receiver that the browser will bind to receive
// messages from the renderer.
CreateFencedFrame(
pending_associated_receiver<blink.mojom.FencedFrameOwnerHost> fenced_frame);
// `proxy_routing_id` - the routing ID assigned to the RenderFrameProxy
// placeholder in the owner process.
// `initial_replication_state` - the replicated state associated with the
// above RenderFrameProxy. This state is
// default-constructed, and thus has an opaque
// origin. This ensures that the origin that the
// proxy host is initialized with in the browser
// and renderer processes both match. For more
// info about this parameter see
// crbug.com/1028269.
// `frame_token` - the unique identifier of the RenderFrameProxy
// `devtools_frame_token` - the unique identifier of the main FrameTreeNode in
// the "inner" fenced frame FrameTree
[Sync] CreateFencedFrame(
pending_associated_receiver<blink.mojom.FencedFrameOwnerHost>
fenced_frame)
=> (int32 proxy_routing_id,
blink.mojom.FrameReplicationState initial_replication_state,
blink.mojom.RemoteFrameToken frame_token,
mojo_base.mojom.UnguessableToken devtools_frame_token);
// Asynchronously creates a child frame. A routing ID must be allocated first
// by calling RenderMessageFilter::GenerateFrameRoutingID()

@ -273,12 +273,13 @@ class CONTENT_EXPORT RenderFrameHost : public IPC::Listener,
virtual bool IsDescendantOf(RenderFrameHost* ancestor) = 0;
// Fenced frames (meta-bug https://crbug.com/1111084):
// Returns if this document is directly hosted by a fenced frame.
// Returns true if this document is the root of a fenced frame tree.
//
// In particular, this always returns false for document loaded inside an
// <iframe>, even if the <iframe> is part of a document loaded inside a fenced
// iframe.
virtual bool HostedByFencedFrame() = 0;
// In particular, this always returns false for frames loaded inside a
// <fencedframe> element, if the frame is not the top-level <fencedframe>
// itself. That is, this will return false for all <iframes> nested under a
// <fencedframe>.
virtual bool IsFencedFrameRoot() = 0;
// |ForEachRenderFrameHost| traverses this RenderFrameHost and all of its
// descendants, including frames in any inner frame trees, in breadth-first

@ -3636,7 +3636,9 @@ blink::WebRemoteFrame* RenderFrameImpl::CreateFencedFrame(
blink::RemoteFrameToken frame_token;
base::UnguessableToken devtools_frame_token;
GetFrameHost()->CreateFencedFrame(std::move(receiver));
GetFrameHost()->CreateFencedFrame(std::move(receiver), &proxy_routing_id,
&initial_replicated_state, &frame_token,
&devtools_frame_token);
RenderFrameProxy* proxy = RenderFrameProxy::CreateProxyForPortalOrFencedFrame(
agent_scheduling_group_, this, proxy_routing_id, frame_token,

@ -0,0 +1,6 @@
<!DOCTYPE html>
<html>
<body>
<fencedframe></fencedframe>
</body>
</html>

@ -162,8 +162,8 @@ class MockFrameHost : public mojom::FrameHost {
}
void CreateFencedFrame(
mojo::PendingAssociatedReceiver<blink::mojom::FencedFrameOwnerHost>)
override {
mojo::PendingAssociatedReceiver<blink::mojom::FencedFrameOwnerHost>,
CreateFencedFrameCallback) override {
NOTREACHED() << "At the moment, content::FencedFrame is not used in any "
"unit tests, so this path should not be hit";
}

@ -157,10 +157,21 @@ void WebContentsObserverConsistencyChecker::RenderFrameHostChanged(
if (new_host->GetParent()) {
AssertRenderFrameExists(new_host->GetParent());
// RenderFrameCreated should be called before RenderFrameHostChanged for all
// the subframes except for Portals which do not have a live RenderFrame in
// the renderer process.
if (new_host->GetFrameOwnerElementType() !=
blink::mojom::FrameOwnerElementType::kPortal) {
// the subframes except for those which are the outer delegates for:
// - Portals
// - Fenced frames based specifically on MPArch
// This is because those special-case frames do not have live RenderFrames
// in the renderer process.
bool is_render_frame_created_needed_for_child =
(new_host->GetFrameOwnerElementType() !=
blink::mojom::FrameOwnerElementType::kPortal &&
new_host->GetFrameOwnerElementType() !=
blink::mojom::FrameOwnerElementType::kFencedframe) ||
(new_host->GetFrameOwnerElementType() ==
blink::mojom::FrameOwnerElementType::kFencedframe &&
blink::features::kFencedFramesImplementationTypeParam.Get() ==
blink::features::FencedFramesImplementationType::kShadowDOM);
if (is_render_frame_created_needed_for_child) {
AssertRenderFrameExists(new_host);
}
CHECK(current_hosts_.count(GetRoutingPair(new_host->GetParent())))

@ -12,4 +12,8 @@ import "url/mojom/url.mojom";
// interact with the backing `content::FencedFrame` in the browser process which
// hosts an "inner" frame tree via Multiple Page Architecture.
interface FencedFrameOwnerHost {
// Navigates the main frame of the fenced frame's frame tree, to `url`.
// TODO(crbug.com/1243568): Document the restrictions on the types of URLs
// that `url` can represent, once these restrictions are made.
Navigate(url.mojom.Url url);
};

@ -33,6 +33,9 @@ void FencedFrameMPArchDelegate::DidGetInserted() {
DCHECK_EQ(remote_frame, GetElement().ContentFrame());
}
void FencedFrameMPArchDelegate::Navigate(const KURL& url) {}
void FencedFrameMPArchDelegate::Navigate(const KURL& url) {
DCHECK(remote_);
remote_->Navigate(url);
}
} // namespace blink

@ -90,7 +90,13 @@ void HTMLFencedFrameElement::Navigate() {
if (!isConnected())
return;
KURL url = KURL(GetNonEmptyURLAttribute(html_names::kSrcAttr));
KURL url = GetNonEmptyURLAttribute(html_names::kSrcAttr);
// TODO(crbug.com/1243568): Convert empty URLs to about:blank, and more
// generally implement the navigation restrictions to potentially-trustworthy
// URLs + urn:uuids.
if (url.IsEmpty())
return;
DCHECK(frame_delegate_);
frame_delegate_->Navigate(url);