Index: content/browser/site_per_process_browsertest.cc |
diff --git a/content/browser/site_per_process_browsertest.cc b/content/browser/site_per_process_browsertest.cc |
index e4b3872a05b16ecd5cd4b8a96bf3a3f949716bf4..e00fcc013015746c8576d365723a08be6ca66db9 100644 |
--- a/content/browser/site_per_process_browsertest.cc |
+++ b/content/browser/site_per_process_browsertest.cc |
@@ -10197,4 +10197,142 @@ IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, |
EXPECT_EQ(foo_url, web_contents()->GetMainFrame()->GetLastCommittedURL()); |
} |
+// Class to sniff incoming IPCs for FrameHostMsg_SetIsInert messages. |
+class SetIsInertMessageFilter : public content::BrowserMessageFilter { |
+ public: |
+ SetIsInertMessageFilter() |
+ : content::BrowserMessageFilter(FrameMsgStart), |
+ message_loop_runner_(new content::MessageLoopRunner), |
+ msg_received_(false) {} |
+ |
+ bool OnMessageReceived(const IPC::Message& message) override { |
+ IPC_BEGIN_MESSAGE_MAP(SetIsInertMessageFilter, message) |
+ IPC_MESSAGE_HANDLER(FrameHostMsg_SetIsInert, OnSetIsInert) |
+ IPC_END_MESSAGE_MAP() |
+ return false; |
+ } |
+ |
+ bool is_inert() const { return is_inert_; } |
+ |
+ void Wait() { message_loop_runner_->Run(); } |
+ |
+ private: |
+ ~SetIsInertMessageFilter() override {} |
+ |
+ void OnSetIsInert(bool is_inert) { |
+ content::BrowserThread::PostTask( |
+ content::BrowserThread::UI, FROM_HERE, |
+ base::Bind(&SetIsInertMessageFilter::OnSetIsInertOnUI, this, is_inert)); |
+ } |
+ void OnSetIsInertOnUI(bool is_inert) { |
+ is_inert_ = is_inert; |
+ if (!msg_received_) { |
+ msg_received_ = true; |
+ message_loop_runner_->Quit(); |
+ } |
+ } |
+ scoped_refptr<content::MessageLoopRunner> message_loop_runner_; |
+ bool msg_received_; |
+ bool is_inert_; |
+ DISALLOW_COPY_AND_ASSIGN(SetIsInertMessageFilter); |
+}; |
+ |
+// Tests that when a frame contains a modal <dialog> element, out-of-process |
+// iframe children cannot take focus, because they are inert. |
+IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, CrossProcessInertSubframe) { |
+ // This uses a(b,b) instead of a(b) to preserve the b.com process even when |
+ // the first subframe is navigated away from it. |
+ GURL main_url(embedded_test_server()->GetURL( |
+ "a.com", "/cross_site_iframe_factory.html?a(b,b)")); |
+ EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
+ |
+ FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) |
+ ->GetFrameTree() |
+ ->root(); |
+ ASSERT_EQ(2U, root->child_count()); |
+ |
+ FrameTreeNode* iframe_node = root->child_at(0); |
+ |
+ EXPECT_TRUE(ExecuteScript( |
+ iframe_node, |
+ "document.head.innerHTML = '';" |
+ "document.body.innerHTML = '<input id=\"text1\"> <input id=\"text2\">';" |
+ "text1.focus();")); |
+ |
+ // Add a filter to the parent frame's process to monitor for inert bit |
+ // updates. These are sent through the proxy for b.com child frame. |
+ scoped_refptr<SetIsInertMessageFilter> filter = new SetIsInertMessageFilter(); |
+ root->current_frame_host()->GetProcess()->AddFilter(filter.get()); |
+ |
+ // Add a <dialog> to the root frame and call showModal on it. |
+ EXPECT_TRUE(ExecuteScript(root, |
+ "let dialog = " |
+ "document.body.appendChild(document.createElement('" |
+ "dialog'));" |
+ "dialog.innerHTML = 'Modal dialog <input>';" |
+ "dialog.showModal();")); |
+ filter->Wait(); |
+ EXPECT_TRUE(filter->is_inert()); |
+ |
+ // This yields the UI thread to ensure that the real SetIsInert message |
+ // handler runs, in order to guarantee that the update arrives at the |
+ // renderer process before the script below. |
+ { |
+ base::RunLoop loop; |
+ base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, |
+ loop.QuitClosure()); |
+ loop.Run(); |
+ } |
+ |
+ std::string focused_element; |
+ |
+ // Attempt to change focus in the inert subframe. This should fail. |
+ // The setTimeout ensures that the inert bit can propagate before the |
+ // test JS code runs. |
+ EXPECT_TRUE(ExecuteScriptAndExtractString( |
+ iframe_node, |
+ "window.setTimeout(() => {text2.focus();" |
+ "domAutomationController.send(document.activeElement.id);}, 0)", |
+ &focused_element)); |
+ EXPECT_EQ("", focused_element); |
+ |
+ // Navigate the child frame to another site, so that it moves into a new |
+ // process. |
+ GURL site_url(embedded_test_server()->GetURL("c.com", "/title1.html")); |
+ NavigateFrameToURL(iframe_node, site_url); |
+ |
+ EXPECT_TRUE(ExecuteScript( |
+ iframe_node, |
+ "document.head.innerHTML = '';" |
+ "document.body.innerHTML = '<input id=\"text1\"> <input id=\"text2\">';" |
+ "text1.focus();")); |
+ |
+ // Verify that inertness was preserved across the navigation. |
+ EXPECT_TRUE(ExecuteScriptAndExtractString( |
+ iframe_node, |
+ "text2.focus();" |
+ "domAutomationController.send(document.activeElement.id);", |
+ &focused_element)); |
+ EXPECT_EQ("", focused_element); |
+ |
+ // Navigate the subframe back into its parent process to verify that the |
+ // new local frame remains inert. |
+ GURL same_site_url(embedded_test_server()->GetURL("a.com", "/title1.html")); |
+ NavigateFrameToURL(iframe_node, same_site_url); |
+ |
+ EXPECT_TRUE(ExecuteScript( |
+ iframe_node, |
+ "document.head.innerHTML = '';" |
+ "document.body.innerHTML = '<input id=\"text1\"> <input id=\"text2\">';" |
+ "text1.focus();")); |
+ |
+ // Verify that inertness was preserved across the navigation. |
+ EXPECT_TRUE(ExecuteScriptAndExtractString( |
+ iframe_node, |
+ "text2.focus();" |
+ "domAutomationController.send(document.activeElement.id);", |
+ &focused_element)); |
+ EXPECT_EQ("", focused_element); |
+} |
+ |
} // namespace content |