[MimeHandlerView] Async Attach: Content API
This CL will rework the attaching logic for frame-based
MimeHandlerViewGuest. The notable change is the addition of a new public
API to RenderFrameHost which will (asynchronously) prepare the
FrameTreeNode for attaching an inner WebContents so that its usage with
the attaching API, WebContents::AttachToOuterContentsFrame is safe.
Previously this was achieved by navigating the current RenderFrameHost
to "about:blank" but that proved to have certain issues such as
navigation races as well as inefficient handling of the 'beforeunload'
handlers (from outside content layer it is nearly impossible to determine
whether a beforeunload modal is shown and is blocking the
navigation).
In summary, with this CL:
1- There is no longer a need for a navigation to about:blank for
every embedded PDF.
2- There is no need for adding navigation throttles.
3- beforeunload is properly addressed (an improvement over the
previous approach of waiting for a timeout to decide navigation was
actually blocked).
4- Less lines of code and a more tractable attaching code.
Bug: 659750, 911161
Change-Id: Ib8981744b5683bb1cb8d557322bfc40f08bb4ccb
Reviewed-on: https://chromium-review.googlesource.com/c/1407596
Commit-Queue: Ehsan Karamad <[email protected]>
Reviewed-by: Lucas Gadani <[email protected]>
Reviewed-by: Alex Moshchuk <[email protected]>
Reviewed-by: James MacLean <[email protected]>
Cr-Commit-Position: refs/heads/master@{#635607}
diff --git a/content/browser/frame_host/frame_tree.cc b/content/browser/frame_host/frame_tree.cc
index 066674c..0197747 100644
--- a/content/browser/frame_host/frame_tree.cc
+++ b/content/browser/frame_host/frame_tree.cc
@@ -240,7 +240,11 @@
// Now that the new node is part of the FrameTree and has a RenderFrameHost,
// we can announce the creation of the initial RenderFrame which already
// exists in the renderer process.
- added_node->current_frame_host()->SetRenderFrameCreated(true);
+ if (added_node->frame_owner_element_type() !=
+ blink::FrameOwnerElementType::kPortal) {
+ // Portals do not have a live RenderFrame in the renderer process.
+ added_node->current_frame_host()->SetRenderFrameCreated(true);
+ }
return added_node;
}
diff --git a/content/browser/frame_host/navigation_controller_impl.cc b/content/browser/frame_host/navigation_controller_impl.cc
index 823ac88..71a0a5f 100644
--- a/content/browser/frame_host/navigation_controller_impl.cc
+++ b/content/browser/frame_host/navigation_controller_impl.cc
@@ -2947,12 +2947,10 @@
CHECK(!frame_entry || url_to_load == frame_entry->url());
}
- if (auto* rfh = node->current_frame_host()) {
- if (rfh->is_attaching_inner_delegate()) {
- // Avoid starting any new navigations since this node is now preparing for
- // attaching an inner delegate.
- return nullptr;
- }
+ if (node->render_manager()->is_attaching_inner_delegate()) {
+ // Avoid starting any new navigations since this node is now preparing for
+ // attaching an inner delegate.
+ return nullptr;
}
if (!IsValidURLForNavigation(node->IsMainFrame(), virtual_url, url_to_load))
@@ -3066,12 +3064,10 @@
origin_to_commit.reset();
}
- if (auto* rfh = frame_tree_node->current_frame_host()) {
- if (rfh->is_attaching_inner_delegate()) {
- // Avoid starting any new navigations since this node is now preparing for
- // attaching an inner delegate.
- return nullptr;
- }
+ if (frame_tree_node->render_manager()->is_attaching_inner_delegate()) {
+ // Avoid starting any new navigations since this node is now preparing for
+ // attaching an inner delegate.
+ return nullptr;
}
if (!IsValidURLForNavigation(frame_tree_node->IsMainFrame(),
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
index e47341d..64543a0 100644
--- a/content/browser/frame_host/render_frame_host_impl.cc
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -2485,6 +2485,13 @@
}
void RenderFrameHostImpl::OnSwapOutACK() {
+ if (frame_tree_node_->render_manager()->is_attaching_inner_delegate()) {
+ // This RFH was swapped out while attaching an inner delegate. The RFH
+ // will stay around but it will no longer be associated with a RenderFrame.
+ SetRenderFrameCreated(false);
+ return;
+ }
+
// Ignore spurious swap out ack.
if (!is_waiting_for_swapout_ack_)
return;
@@ -2862,14 +2869,10 @@
navigation_control_.FlushForTesting();
}
-bool RenderFrameHostImpl::PrepareForInnerWebContentsAttach() {
- DCHECK(MimeHandlerViewMode::UsesCrossProcessFrame());
- if (IsCrossProcessSubframe() || !GetParent())
- return false;
- ResetNavigationRequests();
- ResetLoadingState();
- is_attaching_inner_delegate_ = true;
- return true;
+void RenderFrameHostImpl::PrepareForInnerWebContentsAttach(
+ PrepareForInnerWebContentsAttachCallback callback) {
+ frame_tree_node_->render_manager()->PrepareForInnerDelegateAttach(
+ std::move(callback));
}
void RenderFrameHostImpl::OnDidAccessInitialDocument() {
@@ -3812,7 +3815,7 @@
blink::mojom::BlobURLTokenPtr blob_url_token,
mojom::NavigationClientAssociatedPtrInfo navigation_client,
blink::mojom::NavigationInitiatorPtr navigation_initiator) {
- if (is_attaching_inner_delegate_) {
+ if (frame_tree_node_->render_manager()->is_attaching_inner_delegate()) {
// Avoid starting any new navigations since this frame is in the process of
// attaching an inner delegate.
return;
@@ -4181,11 +4184,14 @@
bool for_navigation =
type == BeforeUnloadType::BROWSER_INITIATED_NAVIGATION ||
type == BeforeUnloadType::RENDERER_INITIATED_NAVIGATION;
- DCHECK(for_navigation || !is_reload);
+ bool for_inner_delegate_attach =
+ type == BeforeUnloadType::INNER_DELEGATE_ATTACH;
+ DCHECK(for_navigation || for_inner_delegate_attach || !is_reload);
// TAB_CLOSE and DISCARD should only dispatch beforeunload on main frames.
DCHECK(type == BeforeUnloadType::BROWSER_INITIATED_NAVIGATION ||
type == BeforeUnloadType::RENDERER_INITIATED_NAVIGATION ||
+ type == BeforeUnloadType::INNER_DELEGATE_ATTACH ||
frame_tree_node_->IsMainFrame());
if (!for_navigation) {
@@ -6305,7 +6311,8 @@
} else {
// This frame might be used for attaching an inner WebContents in which
// case |navigation_requests_| are deleted during attaching.
- DCHECK(is_attaching_inner_delegate_);
+ // TODO(ekaramad): Add a DCHECK to ensure the FrameTreeNode is attaching
+ // an inner delegate (https://crbug.com/911161).
}
}
// Remove the requests from the list of NavigationRequests waiting to commit.
diff --git a/content/browser/frame_host/render_frame_host_impl.h b/content/browser/frame_host/render_frame_host_impl.h
index 248d252..93471d6 100644
--- a/content/browser/frame_host/render_frame_host_impl.h
+++ b/content/browser/frame_host/render_frame_host_impl.h
@@ -266,7 +266,8 @@
bool push_to_renderer_now) override;
bool IsSandboxed(blink::WebSandboxFlags flags) const override;
void FlushNetworkAndNavigationInterfacesForTesting() override;
- bool PrepareForInnerWebContentsAttach() override;
+ void PrepareForInnerWebContentsAttach(
+ PrepareForInnerWebContentsAttachCallback callback) override;
// IPC::Sender
bool Send(IPC::Message* msg) override;
@@ -518,6 +519,10 @@
// the confirmation dialog will not be displayed and the discard will
// automatically be canceled.
DISCARD,
+ // This reason is used when preparing a FrameTreeNode for attaching an inner
+ // delegate. In this case beforeunload is dispatched in the frame and all
+ // the nested child frames.
+ INNER_DELEGATE_ATTACH,
};
// Runs the beforeunload handler for this frame and its subframes. |type|
@@ -846,10 +851,6 @@
void AudioContextPlaybackStarted(int audio_context_id);
void AudioContextPlaybackStopped(int audio_context_id);
- bool is_attaching_inner_delegate() const {
- return is_attaching_inner_delegate_;
- }
-
// BackForwardCache:
//
// When a RenderFrameHostImpl enters the BackForwardCache, the document enters
@@ -1639,15 +1640,6 @@
// document or not.
bool is_loading_;
- // TODO(ekaramad): This flag is used for block navigations that are not using
- // the network stack from starting and interfering with the inner WebContents
- // attaching process. Investigate if it can be removed after moving the attach
- // logic into content layer (https://crbug.com/911161).
- // When true the RFH is in a transient state where it cancels all navigations
- // and does not accept any new navigations until an inner WebContents is
- // attached.
- bool is_attaching_inner_delegate_ = false;
-
// The unique ID of the latest NavigationEntry that this RenderFrameHost is
// showing. This may change even when this frame hasn't committed a page,
// such as for a new subframe navigation in a different frame. Tracking this
diff --git a/content/browser/frame_host/render_frame_host_manager.cc b/content/browser/frame_host/render_frame_host_manager.cc
index 11643f5..9d8a7f4c 100644
--- a/content/browser/frame_host/render_frame_host_manager.cc
+++ b/content/browser/frame_host/render_frame_host_manager.cc
@@ -49,8 +49,10 @@
#include "content/public/browser/render_widget_host_iterator.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/site_isolation_policy.h"
+#include "content/public/common/child_process_host.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
+#include "content/public/common/mime_handler_view_mode.h"
#include "content/public/common/referrer.h"
#include "content/public/common/url_constants.h"
#include "content/public/common/url_utils.h"
@@ -211,10 +213,21 @@
void RenderFrameHostManager::OnBeforeUnloadACK(
bool proceed,
const base::TimeTicks& proceed_time) {
+ // If beforeunload was dispatched as part of preparing this frame for
+ // attaching an inner delegate, continue attaching now.
+ if (is_attaching_inner_delegate()) {
+ DCHECK(frame_tree_node_->parent());
+ if (proceed) {
+ CreateNewFrameForInnerDelegateAttachIfNecessary();
+ } else {
+ NotifyPrepareForInnerDelegateAttachComplete(false /* success */);
+ }
+ return;
+ }
+
bool proceed_to_fire_unload = false;
delegate_->BeforeUnloadFiredFromRenderManager(proceed, proceed_time,
&proceed_to_fire_unload);
-
if (proceed_to_fire_unload) {
// If we're about to close the tab and there's a speculative RFH, cancel it.
// Otherwise, if the navigation in the speculative RFH completes before the
@@ -1154,6 +1167,25 @@
}
}
+void RenderFrameHostManager::PrepareForInnerDelegateAttach(
+ RenderFrameHost::PrepareForInnerWebContentsAttachCallback callback) {
+ DCHECK(MimeHandlerViewMode::UsesCrossProcessFrame());
+ CHECK(frame_tree_node_->parent());
+ attach_inner_delegate_callback_ = std::move(callback);
+ DCHECK_EQ(attach_to_inner_delegate_state_, AttachToInnerDelegateState::NONE);
+ attach_to_inner_delegate_state_ = AttachToInnerDelegateState::PREPARE_FRAME;
+ if (current_frame_host()->ShouldDispatchBeforeUnload(
+ false /* check_subframes_only */)) {
+ // If there are beforeunload handlers in the frame or a nested subframe we
+ // should first dispatch the event and wait for the ACK form the renderer
+ // before proceeding with CreateNewFrameForInnerDelegateAttachIfNecessary.
+ current_frame_host()->DispatchBeforeUnload(
+ RenderFrameHostImpl::BeforeUnloadType::INNER_DELEGATE_ATTACH, false);
+ return;
+ }
+ CreateNewFrameForInnerDelegateAttachIfNecessary();
+}
+
RenderFrameHostManager::SiteInstanceDescriptor
RenderFrameHostManager::DetermineSiteInstanceForURL(
const GURL& dest_url,
@@ -1954,9 +1986,6 @@
false /* is_loading */,
render_frame_host->frame_tree_node()->current_replication_state()));
proxy->set_render_frame_proxy_created(true);
-
- // There is no longer a RenderFrame associated with this RenderFrameHost.
- render_frame_host->SetRenderFrameCreated(false);
}
void RenderFrameHostManager::SetRWHViewForInnerContents(
@@ -2708,4 +2737,67 @@
->is_focused());
}
+void RenderFrameHostManager::CreateNewFrameForInnerDelegateAttachIfNecessary() {
+ DCHECK(is_attaching_inner_delegate());
+ // Remove all navigations and any speculative frames which might interfere
+ // with the loading state.
+ current_frame_host()->ResetNavigationRequests();
+ current_frame_host()->ResetLoadingState();
+ // Remove any speculative frames first and ongoing navigation state. This
+ // should reset the loading state for good.
+ frame_tree_node_->ResetNavigationRequest(false /* keep_state */,
+ false /* inform_renderer */);
+ if (speculative_render_frame_host_) {
+ // The FrameTreeNode::ResetNavigationRequest call above may not have cleaned
+ // up the speculative RenderFrameHost if the NavigationRequest had already
+ // been transferred to RenderFrameHost. Ensure it is cleaned up now.
+ DiscardUnusedFrame(UnsetSpeculativeRenderFrameHost());
+ }
+
+ if (!current_frame_host()->IsCrossProcessSubframe()) {
+ // At this point the beforeunload is dispatched and the result has been to
+ // proceed with attaching. There are also no upcoming navigations which
+ // would interfere with the upcoming attach. If the frame is in the same
+ // SiteInstance as its parent it can be safely used for attaching an inner
+ // Delegate.
+ NotifyPrepareForInnerDelegateAttachComplete(true /* success */);
+ return;
+ }
+
+ // We need a new RenderFrameHost in its parent's SiteInstance to be able to
+ // safely use the WebContentsImpl attach API.
+ DCHECK(!speculative_render_frame_host_);
+ if (!CreateSpeculativeRenderFrameHost(
+ current_frame_host()->GetSiteInstance(),
+ current_frame_host()->GetParent()->GetSiteInstance())) {
+ NotifyPrepareForInnerDelegateAttachComplete(false /* success */);
+ return;
+ }
+ // Swap in the speculative frame. It will later on be swapped out when the
+ // WebContents::AttachToOuterWebContentsFrame is called.
+ speculative_render_frame_host_->Send(
+ new FrameMsg_SwapIn(speculative_render_frame_host_->GetRoutingID()));
+ CommitPending(std::move(speculative_render_frame_host_));
+ NotifyPrepareForInnerDelegateAttachComplete(true /* success */);
+}
+
+void RenderFrameHostManager::NotifyPrepareForInnerDelegateAttachComplete(
+ bool success) {
+ DCHECK(is_attaching_inner_delegate());
+ int32_t process_id = success ? render_frame_host_->GetProcess()->GetID()
+ : ChildProcessHost::kInvalidUniqueID;
+ int32_t routing_id =
+ success ? render_frame_host_->GetRoutingID() : MSG_ROUTING_NONE;
+ // Invoking the callback asynchronously to meet the APIs promise.
+ base::PostTaskWithTraits(
+ FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(
+ [](RenderFrameHost::PrepareForInnerWebContentsAttachCallback callback,
+ int32_t process_id, int32_t routing_id) {
+ std::move(callback).Run(
+ RenderFrameHostImpl::FromID(process_id, routing_id));
+ },
+ std::move(attach_inner_delegate_callback_), process_id, routing_id));
+}
+
} // namespace content
diff --git a/content/browser/frame_host/render_frame_host_manager.h b/content/browser/frame_host/render_frame_host_manager.h
index 3f9d94b..729c8d83 100644
--- a/content/browser/frame_host/render_frame_host_manager.h
+++ b/content/browser/frame_host/render_frame_host_manager.h
@@ -253,10 +253,15 @@
// which one so we tell both.
void SetIsLoading(bool is_loading);
- // Confirms whether we should close the page. This is called before a
- // tab/window is closed to allow the appropriate renderer to approve or deny
- // the request. |proceed| indicates whether the user chose to proceed.
- // |proceed_time| is the time when the request was allowed to proceed.
+ // Confirms whether we should close the page. |proceed| indicates whether the
+ // user chose to proceed. |proceed_time| is the time when the request was
+ // allowed to proceed. This is called in one of the two *distinct* scenarios
+ // below:
+ // 1- The tab/window is closed after allowing the appropriate renderer to
+ // show the beforeunload prompt.
+ // 2- The FrameTreeNode is being prepared for attaching an inner Delegate,
+ // in which case beforeunload is triggered in the current frame. This
+ // only happens for child frames.
void OnBeforeUnloadACK(bool proceed, const base::TimeTicks& proceed_time);
// Determines whether a navigation to |dest_url| may be completed using an
@@ -501,6 +506,24 @@
// Helper to initialize the RenderFrame if it's not initialized.
void InitializeRenderFrameIfNecessary(RenderFrameHostImpl* render_frame_host);
+ // Prepares the FrameTreeNode for attaching an inner WebContents. This step
+ // may involve replacing |current_frame_host()| with a new RenderFrameHost
+ // in the same SiteInstance as the parent frame. Calling this method will
+ // dispatch beforeunload event if necessary.
+ void PrepareForInnerDelegateAttach(
+ RenderFrameHost::PrepareForInnerWebContentsAttachCallback callback);
+
+ // When true the FrameTreeNode is preparing a RenderFrameHost for attaching an
+ // inner Delegate. During this phase new navigation requests are ignored.
+ bool is_attaching_inner_delegate() const {
+ return attach_to_inner_delegate_state_ != AttachToInnerDelegateState::NONE;
+ }
+
+ // Called by the delegate at the end of the attaching process.
+ void set_attach_complete() {
+ attach_to_inner_delegate_state_ = AttachToInnerDelegateState::ATTACHED;
+ }
+
private:
friend class NavigatorTestWithBrowserSideNavigation;
friend class RenderFrameHostManagerTest;
@@ -514,6 +537,16 @@
RELATED,
};
+ enum class AttachToInnerDelegateState {
+ // There is no inner delegate attached through FrameTreeNode and no
+ // attaching is in progress.
+ NONE,
+ // A frame is being prepared for attaching.
+ PREPARE_FRAME,
+ // An inner delegate attached to the delegate of this manager.
+ ATTACHED
+ };
+
// Stores information regarding a SiteInstance targeted at a specific URL to
// allow for comparisons without having to actually create new instances. It
// can point to an existing one or store the details needed to create a new
@@ -748,6 +781,18 @@
// RenderWidget know about page focus.
void EnsureRenderFrameHostPageFocusConsistent();
+ // When current RenderFrameHost is not in its parent SiteInstance, this method
+ // will destroy the frame and replace it with a new RenderFrameHost in the
+ // parent frame's SiteInstance. Either way, this will eventually invoke
+ // |attach_inner_delegate_callback_| with a pointer to |render_frame_host_|
+ // which is then safe for use with WebContents::AttachToOuterWebContentsFrame.
+ void CreateNewFrameForInnerDelegateAttachIfNecessary();
+
+ // Called when the result of preparing the FrameTreeNode for attaching an
+ // inner delegate is known. When successful, |render_frame_host_| can be used
+ // for attaching the inner Delegate.
+ void NotifyPrepareForInnerDelegateAttachComplete(bool success);
+
// For use in creating RenderFrameHosts.
FrameTreeNode* frame_tree_node_;
@@ -776,6 +821,13 @@
// it.
std::unique_ptr<RenderFrameHostImpl> speculative_render_frame_host_;
+ // This callback is used when attaching an inner Delegate to |delegate_|
+ // through |frame_tree_node_|.
+ RenderFrameHost::PrepareForInnerWebContentsAttachCallback
+ attach_inner_delegate_callback_;
+ AttachToInnerDelegateState attach_to_inner_delegate_state_ =
+ AttachToInnerDelegateState::NONE;
+
base::WeakPtrFactory<RenderFrameHostManager> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(RenderFrameHostManager);
diff --git a/content/browser/site_per_process_browsertest.cc b/content/browser/site_per_process_browsertest.cc
index 62f600e7..19b004a 100644
--- a/content/browser/site_per_process_browsertest.cc
+++ b/content/browser/site_per_process_browsertest.cc
@@ -14437,4 +14437,158 @@
testing::Bool(),
/* window feature */
testing::ValuesIn({"", "noopener"})));
+
+enum class InnerWebContentsAttachChildFrameOriginType {
+ kSameOriginAboutBlank,
+ kSameOriginOther,
+ kCrossOrigin
+};
+
+class InnerWebContentsAttachTest
+ : public SitePerProcessBrowserTest,
+ public testing::WithParamInterface<
+ std::tuple<InnerWebContentsAttachChildFrameOriginType,
+ bool /* original frame has beforeunload handlers */,
+ bool /* user proceeds with attaching */>> {
+ public:
+ InnerWebContentsAttachTest() {}
+ ~InnerWebContentsAttachTest() override {}
+
+ protected:
+ // Helper class to initiate and conclude a frame preparation process for
+ // attaching an inner WebContents.
+ class PrepareFrameJob {
+ public:
+ PrepareFrameJob(RenderFrameHostImpl* original_render_frame_host,
+ bool proceed_through_beforeunload) {
+ auto* web_contents =
+ WebContents::FromRenderFrameHost(original_render_frame_host);
+ // Need user gesture for 'beforeunload' to fire.
+ PrepContentsForBeforeUnloadTest(web_contents);
+ // Simulate user choosing to stay on the page after beforeunload fired.
+ SetShouldProceedOnBeforeUnload(
+ Shell::FromRenderViewHost(web_contents->GetRenderViewHost()),
+ true /* always_proceed */, proceed_through_beforeunload);
+ RenderFrameHost::PrepareForInnerWebContentsAttachCallback callback =
+ base::BindOnce(&PrepareFrameJob::OnPrepare, base::Unretained(this));
+ original_render_frame_host->PrepareForInnerWebContentsAttach(
+ std::move(callback));
+ }
+ virtual ~PrepareFrameJob() {}
+
+ void WaitForPreparedFrame() {
+ if (did_call_prepare_)
+ return;
+ run_loop_.Run();
+ }
+
+ RenderFrameHostImpl* prepared_frame() const {
+ return new_render_frame_host_;
+ }
+
+ private:
+ void OnPrepare(RenderFrameHost* render_frame_host) {
+ did_call_prepare_ = true;
+ new_render_frame_host_ =
+ static_cast<RenderFrameHostImpl*>(render_frame_host);
+ if (run_loop_.running())
+ run_loop_.Quit();
+ }
+
+ bool did_call_prepare_ = false;
+ RenderFrameHostImpl* new_render_frame_host_ = nullptr;
+ base::RunLoop run_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(PrepareFrameJob);
+ };
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ SitePerProcessBrowserTest::SetUpCommandLine(command_line);
+ feature_list_.InitAndEnableFeature(
+ features::kMimeHandlerViewInCrossProcessFrame);
+ }
+
+ private:
+ base::test::ScopedFeatureList feature_list_;
+
+ DISALLOW_COPY_AND_ASSIGN(InnerWebContentsAttachTest);
+};
+
+// This is a test for the FrameTreeNode preparation process for various types
+// of outer WebContents RenderFrameHosts; essentially when connecting two
+// WebContents through a frame in a WebPage it is possible that the frame itself
+// has a nontrivial document (other than about:blank) with a beforeunload
+// handler, or even it is a cross-process frame. For such cases the frame first
+// needs to be sanitized to be later consumed by the WebContents attaching API.
+IN_PROC_BROWSER_TEST_P(InnerWebContentsAttachTest, PrepareFrame) {
+ ASSERT_TRUE(
+ NavigateToURL(shell(), embedded_test_server()->GetURL(
+ "a.com", "/page_with_object_fallback.html")));
+ InnerWebContentsAttachChildFrameOriginType child_frame_origin_type =
+ std::get<0>(GetParam());
+ bool test_beforeunload = std::get<1>(GetParam());
+ bool proceed_through_beforeunload = std::get<2>(GetParam());
+ GURL child_frame_url =
+ child_frame_origin_type ==
+ InnerWebContentsAttachChildFrameOriginType::kSameOriginAboutBlank
+ ? GURL(url::kAboutBlankURL)
+ : child_frame_origin_type ==
+ InnerWebContentsAttachChildFrameOriginType::kSameOriginOther
+ ? embedded_test_server()->GetURL("a.com", "/title1.html")
+ : embedded_test_server()->GetURL("b.com", "/title1.html");
+ SCOPED_TRACE(testing::Message()
+ << " Child frame URL:" << child_frame_url.spec()
+ << " 'beforeunload' modal shown: " << test_beforeunload
+ << " proceed through'beforeunload': "
+ << proceed_through_beforeunload);
+ auto* child_node = web_contents()->GetFrameTree()->root()->child_at(0);
+ NavigateFrameToURL(child_node, child_frame_url);
+ if (test_beforeunload) {
+ EXPECT_TRUE(ExecJs(child_node,
+ "window.addEventListener('beforeunload', (e) => {"
+ "e.returnValue = ''; return e; });"));
+ }
+ auto* original_child_frame = child_node->current_frame_host();
+ RenderFrameDeletedObserver original_child_frame_observer(
+ original_child_frame);
+ PrepareFrameJob prepare_job(original_child_frame,
+ proceed_through_beforeunload);
+ if (test_beforeunload)
+ WaitForAppModalDialog(shell());
+ prepare_job.WaitForPreparedFrame();
+ auto* new_render_frame_host = prepare_job.prepared_frame();
+ bool did_prepare_frame = new_render_frame_host;
+ bool same_frame_used = (new_render_frame_host == original_child_frame);
+ // If a frame was not prepared, then it has to be due to beforeunload being
+ // dismissed.
+ ASSERT_TRUE(did_prepare_frame ||
+ (test_beforeunload && !proceed_through_beforeunload));
+ // If the original frame is in the same SiteInstance as its parent, then it
+ // can be reused; otherwise a new frame is expected here.
+ bool is_same_origin =
+ child_frame_origin_type !=
+ InnerWebContentsAttachChildFrameOriginType::kCrossOrigin;
+ if (!is_same_origin && did_prepare_frame) {
+ // For the cross-origin case we expect the original RenderFrameHost to go
+ // away during preparation.
+ original_child_frame_observer.WaitUntilDeleted();
+ }
+ ASSERT_TRUE(!did_prepare_frame || (is_same_origin == same_frame_used));
+ ASSERT_TRUE(!did_prepare_frame ||
+ (original_child_frame_observer.deleted() != is_same_origin));
+ // Finally, try the WebContents attach API and make sure we are doing OK.
+ if (new_render_frame_host)
+ CreateAndAttachInnerContents(new_render_frame_host);
+}
+
+INSTANTIATE_TEST_CASE_P(
+ SitePerProcess,
+ InnerWebContentsAttachTest,
+ testing::Combine(
+ testing::ValuesIn(
+ {InnerWebContentsAttachChildFrameOriginType::kSameOriginAboutBlank,
+ InnerWebContentsAttachChildFrameOriginType::kSameOriginOther,
+ InnerWebContentsAttachChildFrameOriginType::kCrossOrigin}),
+ testing::Bool(),
+ testing::Bool()));
} // namespace content
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 79cc8128..c714e69 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -1808,28 +1808,17 @@
GetMainFrame()->DispatchBeforeUnload(before_unload_type, false);
}
-bool WebContentsImpl::CanAttachToOuterContentsFrame(
- RenderFrameHost* outer_contents_frame) {
- bool web_contents_valid = !node_.outer_web_contents() &&
- FromRenderFrameHost(outer_contents_frame) != this;
- bool rfh_valid = outer_contents_frame->GetParent()->GetSiteInstance() ==
- outer_contents_frame->GetSiteInstance();
- bool rfh_is_loading = static_cast<RenderFrameHostImpl*>(outer_contents_frame)
- ->frame_tree_node()
- ->IsLoading();
- return web_contents_valid && rfh_valid && !rfh_is_loading;
-}
-
void WebContentsImpl::AttachToOuterWebContentsFrame(
std::unique_ptr<WebContents> current_web_contents,
RenderFrameHost* outer_contents_frame) {
DCHECK(!node_.outer_web_contents());
DCHECK_EQ(current_web_contents.get(), this);
- DCHECK(CanAttachToOuterContentsFrame(outer_contents_frame));
auto* outer_contents_frame_impl =
static_cast<RenderFrameHostImpl*>(outer_contents_frame);
RenderFrameHostManager* render_manager = GetRenderManager();
+ auto* outer_contnets_render_manager =
+ outer_contents_frame_impl->frame_tree_node()->render_manager();
// When attaching a WebContents as an inner WebContents, we need to replace
// the Webcontents' view with a WebContentsViewChildFrame.
@@ -1872,6 +1861,7 @@
SetFocusedFrame(frame_tree_.root(),
outer_contents_frame->GetSiteInstance());
}
+ outer_contnets_render_manager->set_attach_complete();
}
std::unique_ptr<WebContents> WebContentsImpl::DetachFromOuterWebContents() {
diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h
index f018c03..b092ae8 100644
--- a/content/browser/web_contents/web_contents_impl.h
+++ b/content/browser/web_contents/web_contents_impl.h
@@ -359,8 +359,6 @@
Visibility GetVisibility() override;
bool NeedToFireBeforeUnload() override;
void DispatchBeforeUnload(bool auto_cancel) override;
- bool CanAttachToOuterContentsFrame(
- RenderFrameHost* outer_contents_frame) final;
void AttachToOuterWebContentsFrame(
std::unique_ptr<WebContents> current_web_contents,
RenderFrameHost* outer_contents_frame) override;