blob: 5defc7244818fce585d4768cb1700cef83c95a21 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/site_per_process_browsertest.h"
#include <stddef.h>
#include <stdint.h>
#include <algorithm>
#include <vector>
#include "base/command_line.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/path_service.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/pattern.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/test_timeouts.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "content/browser/frame_host/cross_process_frame_connector.h"
#include "content/browser/frame_host/frame_tree.h"
#include "content/browser/frame_host/interstitial_page_impl.h"
#include "content/browser/frame_host/navigator.h"
#include "content/browser/frame_host/render_frame_proxy_host.h"
#include "content/browser/frame_host/render_widget_host_view_child_frame.h"
#include "content/browser/gpu/compositor_util.h"
#include "content/browser/loader/resource_dispatcher_host_impl.h"
#include "content/browser/renderer_host/input/synthetic_tap_gesture.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_input_event_router.h"
#include "content/browser/renderer_host/render_widget_host_view_aura.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/child_process_messages.h"
#include "content/common/frame_messages.h"
#include "content/common/input/synthetic_tap_gesture_params.h"
#include "content/common/input_messages.h"
#include "content/common/view_messages.h"
#include "content/public/browser/interstitial_page_delegate.h"
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/resource_dispatcher_host.h"
#include "content/public/common/browser_side_navigation_policy.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/url_constants.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_utils.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "content/test/test_frame_navigation_observer.h"
#include "ipc/ipc_security_test_util.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/WebKit/public/platform/WebInsecureRequestPolicy.h"
#include "third_party/WebKit/public/web/WebInputEvent.h"
#include "third_party/WebKit/public/web/WebSandboxFlags.h"
#include "ui/display/display_switches.h"
#include "ui/display/screen.h"
#include "ui/events/event.h"
#include "ui/events/event_utils.h"
#include "ui/gfx/geometry/point.h"
#if defined(USE_AURA)
#include "content/browser/renderer_host/render_widget_host_view_aura.h"
#endif
#if defined(OS_MACOSX)
#include "ui/base/test/scoped_preferred_scroller_style_mac.h"
#endif
namespace content {
namespace {
// Helper function to send a postMessage and wait for a reply message. The
// |post_message_script| is executed on the |sender_ftn| frame, and the sender
// frame is expected to post |reply_status| from the DOMAutomationController
// when it receives a reply.
void PostMessageAndWaitForReply(FrameTreeNode* sender_ftn,
const std::string& post_message_script,
const std::string& reply_status) {
// Subtle: msg_queue needs to be declared before the ExecuteScript below, or
// else it might miss the message of interest. See https://crbug.com/518729.
DOMMessageQueue msg_queue;
bool success = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
sender_ftn,
"window.domAutomationController.send(" + post_message_script + ");",
&success));
EXPECT_TRUE(success);
std::string status;
while (msg_queue.WaitForMessage(&status)) {
if (status == reply_status)
break;
}
}
// Helper function to extract and return "window.receivedMessages" from the
// |sender_ftn| frame. This variable is used in post_message.html to count the
// number of messages received via postMessage by the current window.
int GetReceivedMessages(FrameTreeNode* ftn) {
int received_messages = 0;
EXPECT_TRUE(ExecuteScriptAndExtractInt(
ftn, "window.domAutomationController.send(window.receivedMessages);",
&received_messages));
return received_messages;
}
// Helper function to perform a window.open from the |caller_frame| targeting a
// frame with the specified name.
void NavigateNamedFrame(const ToRenderFrameHost& caller_frame,
const GURL& url,
const std::string& name) {
bool success = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
caller_frame,
"window.domAutomationController.send("
" !!window.open('" + url.spec() + "', '" + name + "'));",
&success));
EXPECT_TRUE(success);
}
// Helper function to generate a click on the given RenderWidgetHost. The
// mouse event is forwarded directly to the RenderWidgetHost without any
// hit-testing.
void SimulateMouseClick(RenderWidgetHost* rwh, int x, int y) {
blink::WebMouseEvent mouse_event;
mouse_event.type = blink::WebInputEvent::MouseDown;
mouse_event.button = blink::WebPointerProperties::Button::Left;
mouse_event.x = x;
mouse_event.y = y;
rwh->ForwardMouseEvent(mouse_event);
}
// Retrieve document.origin for the frame |ftn|.
std::string GetDocumentOrigin(FrameTreeNode* ftn) {
std::string origin;
EXPECT_TRUE(ExecuteScriptAndExtractString(
ftn, "domAutomationController.send(document.origin)", &origin));
return origin;
}
class RenderWidgetHostMouseEventMonitor {
public:
explicit RenderWidgetHostMouseEventMonitor(RenderWidgetHost* host)
: host_(host), event_received_(false) {
host_->AddMouseEventCallback(
base::Bind(&RenderWidgetHostMouseEventMonitor::MouseEventCallback,
base::Unretained(this)));
}
~RenderWidgetHostMouseEventMonitor() {
host_->RemoveMouseEventCallback(
base::Bind(&RenderWidgetHostMouseEventMonitor::MouseEventCallback,
base::Unretained(this)));
}
bool EventWasReceived() const { return event_received_; }
void ResetEventReceived() { event_received_ = false; }
const blink::WebMouseEvent& event() const { return event_; }
private:
bool MouseEventCallback(const blink::WebMouseEvent& event) {
event_received_ = true;
event_ = event;
return false;
}
RenderWidgetHost* host_;
bool event_received_;
blink::WebMouseEvent event_;
DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostMouseEventMonitor);
};
class TestInputEventObserver : public RenderWidgetHost::InputEventObserver {
public:
explicit TestInputEventObserver(RenderWidgetHost* host) : host_(host) {
host_->AddInputEventObserver(this);
}
~TestInputEventObserver() override { host_->RemoveInputEventObserver(this); }
bool EventWasReceived() const { return !events_received_.empty(); }
void ResetEventsReceived() { events_received_.clear(); }
blink::WebInputEvent::Type EventType() const {
DCHECK(EventWasReceived());
return events_received_.front();
}
const std::vector<blink::WebInputEvent::Type>& events_received() {
return events_received_;
}
void OnInputEvent(const blink::WebInputEvent& event) override {
events_received_.push_back(event.type);
};
private:
RenderWidgetHost* host_;
std::vector<blink::WebInputEvent::Type> events_received_;
DISALLOW_COPY_AND_ASSIGN(TestInputEventObserver);
};
// Helper function that performs a surface hittest.
void SurfaceHitTestTestHelper(
Shell* shell,
net::test_server::EmbeddedTestServer* embedded_test_server) {
GURL main_url(embedded_test_server->GetURL(
"/frame_tree/page_with_positioned_frame.html"));
NavigateToURL(shell, main_url);
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell->web_contents())
->GetFrameTree()
->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child_node = root->child_at(0);
GURL site_url(embedded_test_server->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(site_url, child_node->current_url());
EXPECT_NE(shell->web_contents()->GetSiteInstance(),
child_node->current_frame_host()->GetSiteInstance());
// Create listeners for mouse events.
RenderWidgetHostMouseEventMonitor main_frame_monitor(
root->current_frame_host()->GetRenderWidgetHost());
RenderWidgetHostMouseEventMonitor child_frame_monitor(
child_node->current_frame_host()->GetRenderWidgetHost());
RenderWidgetHostInputEventRouter* router =
static_cast<WebContentsImpl*>(shell->web_contents())
->GetInputEventRouter();
RenderWidgetHostViewBase* root_view = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
SurfaceHitTestReadyNotifier notifier(
static_cast<RenderWidgetHostViewChildFrame*>(rwhv_child));
notifier.WaitForSurfaceReady();
// Target input event to child frame.
blink::WebMouseEvent child_event;
child_event.type = blink::WebInputEvent::MouseDown;
child_event.button = blink::WebPointerProperties::Button::Left;
child_event.x = 75;
child_event.y = 75;
child_event.clickCount = 1;
main_frame_monitor.ResetEventReceived();
child_frame_monitor.ResetEventReceived();
router->RouteMouseEvent(root_view, &child_event);
EXPECT_TRUE(child_frame_monitor.EventWasReceived());
EXPECT_EQ(23, child_frame_monitor.event().x);
EXPECT_EQ(23, child_frame_monitor.event().y);
EXPECT_FALSE(main_frame_monitor.EventWasReceived());
child_frame_monitor.ResetEventReceived();
main_frame_monitor.ResetEventReceived();
// Target input event to main frame.
blink::WebMouseEvent main_event;
main_event.type = blink::WebInputEvent::MouseDown;
main_event.button = blink::WebPointerProperties::Button::Left;
main_event.x = 1;
main_event.y = 1;
main_event.clickCount = 1;
// Ladies and gentlemen, THIS is the main_event!
router->RouteMouseEvent(root_view, &main_event);
EXPECT_FALSE(child_frame_monitor.EventWasReceived());
EXPECT_TRUE(main_frame_monitor.EventWasReceived());
EXPECT_EQ(1, main_frame_monitor.event().x);
EXPECT_EQ(1, main_frame_monitor.event().y);
}
class RedirectNotificationObserver : public NotificationObserver {
public:
// Register to listen for notifications of the given type from either a
// specific source, or from all sources if |source| is
// NotificationService::AllSources().
RedirectNotificationObserver(int notification_type,
const NotificationSource& source);
~RedirectNotificationObserver() override;
// Wait until the specified notification occurs. If the notification was
// emitted between the construction of this object and this call then it
// returns immediately.
void Wait();
// Returns NotificationService::AllSources() if we haven't observed a
// notification yet.
const NotificationSource& source() const {
return source_;
}
const NotificationDetails& details() const {
return details_;
}
// NotificationObserver:
void Observe(int type,
const NotificationSource& source,
const NotificationDetails& details) override;
private:
bool seen_;
bool seen_twice_;
bool running_;
NotificationRegistrar registrar_;
NotificationSource source_;
NotificationDetails details_;
scoped_refptr<MessageLoopRunner> message_loop_runner_;
DISALLOW_COPY_AND_ASSIGN(RedirectNotificationObserver);
};
RedirectNotificationObserver::RedirectNotificationObserver(
int notification_type,
const NotificationSource& source)
: seen_(false),
running_(false),
source_(NotificationService::AllSources()) {
registrar_.Add(this, notification_type, source);
}
RedirectNotificationObserver::~RedirectNotificationObserver() {}
void RedirectNotificationObserver::Wait() {
if (seen_ && seen_twice_)
return;
running_ = true;
message_loop_runner_ = new MessageLoopRunner;
message_loop_runner_->Run();
EXPECT_TRUE(seen_);
}
void RedirectNotificationObserver::Observe(
int type,
const NotificationSource& source,
const NotificationDetails& details) {
source_ = source;
details_ = details;
seen_twice_ = seen_;
seen_ = true;
if (!running_)
return;
message_loop_runner_->Quit();
running_ = false;
}
// This observer keeps track of the number of created RenderFrameHosts. Tests
// can use this to ensure that a certain number of child frames has been
// created after navigating.
class RenderFrameHostCreatedObserver : public WebContentsObserver {
public:
RenderFrameHostCreatedObserver(WebContents* web_contents,
int expected_frame_count)
: WebContentsObserver(web_contents),
expected_frame_count_(expected_frame_count),
frames_created_(0),
message_loop_runner_(new MessageLoopRunner) {}
~RenderFrameHostCreatedObserver() override;
// Runs a nested message loop and blocks until the expected number of
// RenderFrameHosts is created.
void Wait();
private:
// WebContentsObserver
void RenderFrameCreated(RenderFrameHost* render_frame_host) override;
// The number of RenderFrameHosts to wait for.
int expected_frame_count_;
// The number of RenderFrameHosts that have been created.
int frames_created_;
// The MessageLoopRunner used to spin the message loop.
scoped_refptr<MessageLoopRunner> message_loop_runner_;
DISALLOW_COPY_AND_ASSIGN(RenderFrameHostCreatedObserver);
};
RenderFrameHostCreatedObserver::~RenderFrameHostCreatedObserver() {
}
void RenderFrameHostCreatedObserver::Wait() {
message_loop_runner_->Run();
}
void RenderFrameHostCreatedObserver::RenderFrameCreated(
RenderFrameHost* render_frame_host) {
frames_created_++;
if (frames_created_ == expected_frame_count_) {
message_loop_runner_->Quit();
}
}
// This observer detects when WebContents receives notification of a user
// gesture having occurred, following a user input event targeted to
// a RenderWidgetHost under that WebContents.
class UserInteractionObserver : public WebContentsObserver {
public:
explicit UserInteractionObserver(WebContents* web_contents)
: WebContentsObserver(web_contents), user_interaction_received_(false) {}
~UserInteractionObserver() override {}
// Retrieve the flag. There is no need to wait on a loop since
// DidGetUserInteraction() should be called synchronously with the input
// event processing in the browser process.
bool WasUserInteractionReceived() { return user_interaction_received_; }
void Reset() { user_interaction_received_ = false; }
private:
// WebContentsObserver
void DidGetUserInteraction(const blink::WebInputEvent::Type type) override {
user_interaction_received_ = true;
}
bool user_interaction_received_;
DISALLOW_COPY_AND_ASSIGN(UserInteractionObserver);
};
// This observer is used to wait for its owner FrameTreeNode to become deleted.
class FrameDeletedObserver : public FrameTreeNode::Observer {
public:
FrameDeletedObserver(FrameTreeNode* owner)
: owner_(owner), message_loop_runner_(new MessageLoopRunner) {
owner->AddObserver(this);
}
void Wait() { message_loop_runner_->Run(); }
private:
// FrameTreeNode::Observer
void OnFrameTreeNodeDestroyed(FrameTreeNode* node) override {
if (node == owner_)
message_loop_runner_->Quit();
}
FrameTreeNode* owner_;
scoped_refptr<MessageLoopRunner> message_loop_runner_;
DISALLOW_COPY_AND_ASSIGN(FrameDeletedObserver);
};
// Helper function to focus a frame by sending it a mouse click and then
// waiting for it to become focused.
void FocusFrame(FrameTreeNode* frame) {
FrameFocusedObserver focus_observer(frame->current_frame_host());
SimulateMouseClick(frame->current_frame_host()->GetRenderWidgetHost(), 1, 1);
focus_observer.Wait();
}
// A BrowserMessageFilter that drops SwapOut ACK messages.
class SwapoutACKMessageFilter : public BrowserMessageFilter {
public:
SwapoutACKMessageFilter() : BrowserMessageFilter(FrameMsgStart) {}
protected:
~SwapoutACKMessageFilter() override {}
private:
// BrowserMessageFilter:
bool OnMessageReceived(const IPC::Message& message) override {
return message.type() == FrameHostMsg_SwapOut_ACK::ID;
}
DISALLOW_COPY_AND_ASSIGN(SwapoutACKMessageFilter);
};
class RenderWidgetHostVisibilityObserver : public NotificationObserver {
public:
explicit RenderWidgetHostVisibilityObserver(RenderWidgetHostImpl* rwhi,
bool expected_visibility_state)
: expected_visibility_state_(expected_visibility_state),
was_observed_(false),
did_fail_(false),
source_(rwhi) {
registrar_.Add(this, NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED,
source_);
message_loop_runner_ = new MessageLoopRunner;
}
bool WaitUntilSatisfied() {
if (!was_observed_)
message_loop_runner_->Run();
registrar_.Remove(this, NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED,
source_);
return !did_fail_;
}
private:
void Observe(int type,
const NotificationSource& source,
const NotificationDetails& details) override {
was_observed_ = true;
did_fail_ = expected_visibility_state_ !=
(*static_cast<const Details<bool>&>(details).ptr());
if (message_loop_runner_->loop_running())
message_loop_runner_->Quit();
}
bool expected_visibility_state_;
scoped_refptr<MessageLoopRunner> message_loop_runner_;
NotificationRegistrar registrar_;
bool was_observed_;
bool did_fail_;
Source<RenderWidgetHost> source_;
DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostVisibilityObserver);
};
class TestInterstitialDelegate : public InterstitialPageDelegate {
private:
// InterstitialPageDelegate:
std::string GetHTMLContents() override { return "<p>Interstitial</p>"; }
};
} // namespace
//
// SitePerProcessBrowserTest
//
SitePerProcessBrowserTest::SitePerProcessBrowserTest() {
};
std::string SitePerProcessBrowserTest::DepictFrameTree(FrameTreeNode* node) {
return visualizer_.DepictFrameTree(node);
}
void SitePerProcessBrowserTest::SetUpCommandLine(
base::CommandLine* command_line) {
IsolateAllSitesForTesting(command_line);
};
void SitePerProcessBrowserTest::SetUpOnMainThread() {
host_resolver()->AddRule("*", "127.0.0.1");
ASSERT_TRUE(embedded_test_server()->Start());
SetupCrossSiteRedirector(embedded_test_server());
}
//
// SitePerProcessHighDPIBrowserTest
//
class SitePerProcessHighDPIBrowserTest : public SitePerProcessBrowserTest {
public:
const double kDeviceScaleFactor = 2.0;
SitePerProcessHighDPIBrowserTest() {}
protected:
void SetUpCommandLine(base::CommandLine* command_line) override {
SitePerProcessBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitchASCII(
switches::kForceDeviceScaleFactor,
base::StringPrintf("%f", kDeviceScaleFactor));
}
};
// SitePerProcessIgnoreCertErrorsBrowserTest
class SitePerProcessIgnoreCertErrorsBrowserTest
: public SitePerProcessBrowserTest {
public:
SitePerProcessIgnoreCertErrorsBrowserTest() {}
protected:
void SetUpCommandLine(base::CommandLine* command_line) override {
SitePerProcessBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitch(switches::kIgnoreCertificateErrors);
}
};
double GetFrameDeviceScaleFactor(const ToRenderFrameHost& adapter) {
double device_scale_factor;
const char kGetFrameDeviceScaleFactor[] =
"window.domAutomationController.send(window.devicePixelRatio);";
EXPECT_TRUE(ExecuteScriptAndExtractDouble(adapter, kGetFrameDeviceScaleFactor,
&device_scale_factor));
return device_scale_factor;
}
IN_PROC_BROWSER_TEST_F(SitePerProcessHighDPIBrowserTest,
SubframeLoadsWithCorrectDeviceScaleFactor) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
NavigateToURL(shell(), main_url);
EXPECT_EQ(SitePerProcessHighDPIBrowserTest::kDeviceScaleFactor,
GetFrameDeviceScaleFactor(web_contents()));
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
FrameTreeNode* child = root->child_at(0);
EXPECT_EQ(SitePerProcessHighDPIBrowserTest::kDeviceScaleFactor,
GetFrameDeviceScaleFactor(child));
}
// Ensure that navigating subframes in --site-per-process mode works and the
// correct documents are committed.
#if defined(OS_WIN)
// This test is flaky on Windows, see https://crbug.com/629419.
#define MAYBE_CrossSiteIframe DISABLED_CrossSiteIframe
#else
#define MAYBE_CrossSiteIframe CrossSiteIframe
#endif
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, MAYBE_CrossSiteIframe) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(a,a(a,a(a)))"));
NavigateToURL(shell(), main_url);
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
TestNavigationObserver observer(shell()->web_contents());
// Load same-site page into iframe.
FrameTreeNode* child = root->child_at(0);
GURL http_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
NavigateFrameToURL(child, http_url);
EXPECT_EQ(http_url, observer.last_navigation_url());
EXPECT_TRUE(observer.last_navigation_succeeded());
{
// There should be only one RenderWidgetHost when there are no
// cross-process iframes.
std::set<RenderWidgetHostView*> views_set =
web_contents()->GetRenderWidgetHostViewsInTree();
EXPECT_EQ(1U, views_set.size());
}
EXPECT_EQ(
" Site A\n"
" |--Site A\n"
" +--Site A\n"
" |--Site A\n"
" +--Site A\n"
" +--Site A\n"
"Where A = http://a.com/",
DepictFrameTree(root));
// Load cross-site page into iframe.
GURL url = embedded_test_server()->GetURL("foo.com", "/title2.html");
NavigateFrameToURL(root->child_at(0), url);
// Verify that the navigation succeeded and the expected URL was loaded.
EXPECT_TRUE(observer.last_navigation_succeeded());
EXPECT_EQ(url, observer.last_navigation_url());
// Ensure that we have created a new process for the subframe.
ASSERT_EQ(2U, root->child_count());
SiteInstance* site_instance = child->current_frame_host()->GetSiteInstance();
RenderViewHost* rvh = child->current_frame_host()->render_view_host();
RenderProcessHost* rph = child->current_frame_host()->GetProcess();
EXPECT_NE(shell()->web_contents()->GetRenderViewHost(), rvh);
EXPECT_NE(shell()->web_contents()->GetSiteInstance(), site_instance);
EXPECT_NE(shell()->web_contents()->GetRenderProcessHost(), rph);
{
// There should be now two RenderWidgetHosts, one for each process
// rendering a frame.
std::set<RenderWidgetHostView*> views_set =
web_contents()->GetRenderWidgetHostViewsInTree();
EXPECT_EQ(2U, views_set.size());
}
RenderFrameProxyHost* proxy_to_parent =
child->render_manager()->GetProxyToParent();
EXPECT_TRUE(proxy_to_parent);
EXPECT_TRUE(proxy_to_parent->cross_process_frame_connector());
// The out-of-process iframe should have its own RenderWidgetHost,
// independent of any RenderViewHost.
EXPECT_NE(
rvh->GetWidget()->GetView(),
proxy_to_parent->cross_process_frame_connector()->get_view_for_testing());
EXPECT_TRUE(child->current_frame_host()->GetRenderWidgetHost());
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" |--Site B ------- proxies for A\n"
" +--Site A ------- proxies for B\n"
" |--Site A -- proxies for B\n"
" +--Site A -- proxies for B\n"
" +--Site A -- proxies for B\n"
"Where A = http://a.com/\n"
" B = http://foo.com/",
DepictFrameTree(root));
// Load another cross-site page into the same iframe.
url = embedded_test_server()->GetURL("bar.com", "/title3.html");
NavigateFrameToURL(root->child_at(0), url);
EXPECT_TRUE(observer.last_navigation_succeeded());
EXPECT_EQ(url, observer.last_navigation_url());
// Check again that a new process is created and is different from the
// top level one and the previous one.
ASSERT_EQ(2U, root->child_count());
child = root->child_at(0);
EXPECT_NE(shell()->web_contents()->GetRenderViewHost(),
child->current_frame_host()->render_view_host());
EXPECT_NE(rvh, child->current_frame_host()->render_view_host());
EXPECT_NE(shell()->web_contents()->GetSiteInstance(),
child->current_frame_host()->GetSiteInstance());
EXPECT_NE(site_instance,
child->current_frame_host()->GetSiteInstance());
EXPECT_NE(shell()->web_contents()->GetRenderProcessHost(),
child->current_frame_host()->GetProcess());
EXPECT_NE(rph, child->current_frame_host()->GetProcess());
{
std::set<RenderWidgetHostView*> views_set =
web_contents()->GetRenderWidgetHostViewsInTree();
EXPECT_EQ(2U, views_set.size());
}
EXPECT_EQ(proxy_to_parent, child->render_manager()->GetProxyToParent());
EXPECT_TRUE(proxy_to_parent->cross_process_frame_connector());
EXPECT_NE(
child->current_frame_host()->render_view_host()->GetWidget()->GetView(),
proxy_to_parent->cross_process_frame_connector()->get_view_for_testing());
EXPECT_TRUE(child->current_frame_host()->GetRenderWidgetHost());
EXPECT_EQ(
" Site A ------------ proxies for C\n"
" |--Site C ------- proxies for A\n"
" +--Site A ------- proxies for C\n"
" |--Site A -- proxies for C\n"
" +--Site A -- proxies for C\n"
" +--Site A -- proxies for C\n"
"Where A = http://a.com/\n"
" C = http://bar.com/",
DepictFrameTree(root));
}
// Ensure that title updates affect the correct NavigationEntry after a new
// subframe navigation with an out-of-process iframe. https://crbug.com/616609.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, TitleAfterCrossSiteIframe) {
// Start at an initial page.
GURL initial_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), initial_url));
// Navigate to a same-site page with a same-site iframe.
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(a)"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
// Make the main frame update its title after the subframe loads.
EXPECT_TRUE(ExecuteScript(shell()->web_contents(),
"document.querySelector('iframe').onload = "
" function() { document.title = 'loaded'; };"));
EXPECT_TRUE(
ExecuteScript(shell()->web_contents(), "document.title = 'not loaded';"));
base::string16 expected_title(base::UTF8ToUTF16("loaded"));
TitleWatcher title_watcher(shell()->web_contents(), expected_title);
// Navigate the iframe cross-site.
TestNavigationObserver load_observer(shell()->web_contents());
GURL frame_url = embedded_test_server()->GetURL("b.com", "/title2.html");
EXPECT_TRUE(
ExecuteScript(root->child_at(0)->current_frame_host(),
"window.location.href = '" + frame_url.spec() + "';"));
load_observer.Wait();
// Wait for the title to update and ensure it affects the right NavEntry.
EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle());
NavigationEntry* entry =
shell()->web_contents()->GetController().GetLastCommittedEntry();
EXPECT_EQ(expected_title, entry->GetTitle());
}
// Class to sniff incoming IPCs for FrameHostMsg_FrameRectChanged messages.
class FrameRectChangedMessageFilter : public content::BrowserMessageFilter {
public:
FrameRectChangedMessageFilter()
: content::BrowserMessageFilter(FrameMsgStart),
message_loop_runner_(new content::MessageLoopRunner),
frame_rect_received_(false) {}
bool OnMessageReceived(const IPC::Message& message) override {
IPC_BEGIN_MESSAGE_MAP(FrameRectChangedMessageFilter, message)
IPC_MESSAGE_HANDLER(FrameHostMsg_FrameRectChanged, OnFrameRectChanged)
IPC_END_MESSAGE_MAP()
return false;
}
gfx::Rect last_rect() const { return last_rect_; }
void Wait() {
message_loop_runner_->Run();
}
void Reset() {
last_rect_ = gfx::Rect();
message_loop_runner_ = new content::MessageLoopRunner;
frame_rect_received_ = false;
}
private:
~FrameRectChangedMessageFilter() override {}
void OnFrameRectChanged(const gfx::Rect& rect) {
content::BrowserThread::PostTask(
content::BrowserThread::UI, FROM_HERE,
base::Bind(&FrameRectChangedMessageFilter::OnFrameRectChangedOnUI, this,
rect));
}
void OnFrameRectChangedOnUI(const gfx::Rect& rect) {
last_rect_ = rect;
if (!frame_rect_received_) {
frame_rect_received_ = true;
message_loop_runner_->Quit();
}
}
scoped_refptr<content::MessageLoopRunner> message_loop_runner_;
bool frame_rect_received_;
gfx::Rect last_rect_;
DISALLOW_COPY_AND_ASSIGN(FrameRectChangedMessageFilter);
};
// Test that the view bounds for an out-of-process iframe are set and updated
// correctly, including accounting for local frame offsets in the parent and
// scroll positions.
#if defined(OS_ANDROID)
// Browser process hit testing is not implemented on Android.
// https://crbug.com/491334
#define MAYBE_ViewBoundsInNestedFrameTest DISABLED_ViewBoundsInNestedFrameTest
#else
#define MAYBE_ViewBoundsInNestedFrameTest ViewBoundsInNestedFrameTest
#endif
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
MAYBE_ViewBoundsInNestedFrameTest) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(a)"));
NavigateToURL(shell(), main_url);
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* parent_iframe_node = root->child_at(0);
GURL site_url(embedded_test_server()->GetURL(
"a.com", "/frame_tree/page_with_positioned_frame.html"));
NavigateFrameToURL(parent_iframe_node, site_url);
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" +--Site A ------- proxies for B\n"
" +--Site B -- proxies for A\n"
"Where A = http://a.com/\n"
" B = http://baz.com/",
DepictFrameTree(root));
FrameTreeNode* nested_iframe_node = parent_iframe_node->child_at(0);
RenderWidgetHostViewBase* rwhv_nested =
static_cast<RenderWidgetHostViewBase*>(
nested_iframe_node->current_frame_host()
->GetRenderWidgetHost()
->GetView());
SurfaceHitTestReadyNotifier notifier(
static_cast<RenderWidgetHostViewChildFrame*>(rwhv_nested));
notifier.WaitForSurfaceReady();
// Verify the view bounds of the nested iframe, which should account for the
// relative offset of its direct parent within the root frame.
gfx::Rect bounds = rwhv_nested->GetViewBounds();
EXPECT_EQ(bounds.x() - rwhv_root->GetViewBounds().x(), 397);
EXPECT_EQ(bounds.y() - rwhv_root->GetViewBounds().y(), 112);
scoped_refptr<FrameRectChangedMessageFilter> filter =
new FrameRectChangedMessageFilter();
root->current_frame_host()->GetProcess()->AddFilter(filter.get());
// Scroll the parent frame downward to verify that the child rect gets updated
// correctly.
blink::WebMouseWheelEvent scroll_event;
scroll_event.type = blink::WebInputEvent::MouseWheel;
scroll_event.x = 387;
scroll_event.y = 110;
scroll_event.deltaX = 0.0f;
scroll_event.deltaY = -30.0f;
rwhv_root->ProcessMouseWheelEvent(scroll_event, ui::LatencyInfo());
filter->Wait();
// The precise amount of scroll for the first view position update is not
// deterministic, so this simply verifies that the OOPIF moved from its
// earlier position.
gfx::Rect update_rect = filter->last_rect();
EXPECT_LT(update_rect.y(), bounds.y() - rwhv_root->GetViewBounds().y());
}
// Test that scrolling a nested out-of-process iframe bubbles unused scroll
// delta to a parent frame.
// Browser process hit testing is not implemented on Android.
// https://crbug.com/491334
// Flaky: https://crbug.com/627238
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
DISABLED_ScrollBubblingFromOOPIFTest) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
NavigateToURL(shell(), main_url);
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* parent_iframe_node = root->child_at(0);
// This test uses the position of the nested iframe within the parent iframe
// to infer the scroll position of the parent. FrameRectChangedMessageFilter
// catches updates to the position in order to avoid busy waiting.
// It gets created early to catch the initial rects from the navigation.
scoped_refptr<FrameRectChangedMessageFilter> filter =
new FrameRectChangedMessageFilter();
parent_iframe_node->current_frame_host()->GetProcess()->AddFilter(
filter.get());
GURL site_url(embedded_test_server()->GetURL(
"b.com", "/frame_tree/page_with_positioned_frame.html"));
NavigateFrameToURL(parent_iframe_node, site_url);
// Navigate the nested frame to a page large enough to have scrollbars.
FrameTreeNode* nested_iframe_node = parent_iframe_node->child_at(0);
GURL nested_site_url(embedded_test_server()->GetURL(
"baz.com", "/tall_page.html"));
NavigateFrameToURL(nested_iframe_node, nested_site_url);
EXPECT_EQ(
" Site A ------------ proxies for B C\n"
" +--Site B ------- proxies for A C\n"
" +--Site C -- proxies for A B\n"
"Where A = http://a.com/\n"
" B = http://b.com/\n"
" C = http://baz.com/",
DepictFrameTree(root));
RenderWidgetHostViewBase* rwhv_parent =
static_cast<RenderWidgetHostViewBase*>(
parent_iframe_node->current_frame_host()
->GetRenderWidgetHost()
->GetView());
RenderWidgetHostViewBase* rwhv_nested =
static_cast<RenderWidgetHostViewBase*>(
nested_iframe_node->current_frame_host()
->GetRenderWidgetHost()
->GetView());
SurfaceHitTestReadyNotifier notifier(
static_cast<RenderWidgetHostViewChildFrame*>(rwhv_nested));
notifier.WaitForSurfaceReady();
// Save the original offset as a point of reference.
filter->Wait();
gfx::Rect update_rect = filter->last_rect();
int initial_y = update_rect.y();
filter->Reset();
// Scroll the parent frame downward.
blink::WebMouseWheelEvent scroll_event;
scroll_event.type = blink::WebInputEvent::MouseWheel;
scroll_event.x = 1;
scroll_event.y = 1;
scroll_event.deltaX = 0.0f;
scroll_event.deltaY = -5.0f;
rwhv_parent->ProcessMouseWheelEvent(scroll_event, ui::LatencyInfo());
// Ensure that the view position is propagated to the child properly.
filter->Wait();
update_rect = filter->last_rect();
EXPECT_LT(update_rect.y(), initial_y);
filter->Reset();
// Now scroll the nested frame upward, which should bubble to the parent.
// The upscroll exceeds the amount that the frame was initially scrolled
// down to account for rounding.
scroll_event.deltaY = 6.0f;
rwhv_nested->ProcessMouseWheelEvent(scroll_event, ui::LatencyInfo());
filter->Wait();
// This loop isn't great, but it accounts for the possibility of multiple
// incremental updates happening as a result of the scroll animation.
// A failure condition of this test is that the loop might not terminate
// due to bubbling not working properly. If the overscroll bubbles to the
// parent iframe then the nested frame's y coord will return to its
// initial position.
update_rect = filter->last_rect();
while (update_rect.y() > initial_y) {
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
run_loop.Run();
update_rect = filter->last_rect();
}
filter->Reset();
// Scroll the parent down again in order to test scroll bubbling from
// gestures.
scroll_event.deltaY = -5.0f;
rwhv_parent->ProcessMouseWheelEvent(scroll_event, ui::LatencyInfo());
// Ensure ensuing offset change is received, and then reset the filter.
filter->Wait();
filter->Reset();
// Scroll down the nested iframe via gesture. This requires 3 separate input
// events.
blink::WebGestureEvent gesture_event;
gesture_event.type = blink::WebGestureEvent::GestureScrollBegin;
gesture_event.sourceDevice = blink::WebGestureDeviceTouchpad;
gesture_event.x = 1;
gesture_event.y = 1;
rwhv_nested->GetRenderWidgetHost()->ForwardGestureEvent(gesture_event);
gesture_event.type = blink::WebGestureEvent::GestureScrollUpdate;
gesture_event.data.scrollUpdate.deltaX = 0.0f;
gesture_event.data.scrollUpdate.deltaY = 6.0f;
gesture_event.data.scrollUpdate.velocityX = 0;
gesture_event.data.scrollUpdate.velocityY = 0;
rwhv_nested->GetRenderWidgetHost()->ForwardGestureEvent(gesture_event);
gesture_event.type = blink::WebGestureEvent::GestureScrollEnd;
rwhv_nested->GetRenderWidgetHost()->ForwardGestureEvent(gesture_event);
filter->Wait();
update_rect = filter->last_rect();
// As above, if this loop does not terminate then it indicates an issue
// with scroll bubbling.
while (update_rect.y() > initial_y) {
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
run_loop.Run();
update_rect = filter->last_rect();
}
// Test that when the child frame absorbs all of the scroll delta, it does
// not propagate to the parent (see https://crbug.com/621624).
filter->Reset();
scroll_event.deltaY = -5.0f;
rwhv_nested->ProcessMouseWheelEvent(scroll_event, ui::LatencyInfo());
// It isn't possible to busy loop waiting on the renderer here because we
// are explicitly testing that something does *not* happen. This creates a
// small chance of false positives but shouldn't result in false negatives,
// so flakiness implies this test is failing.
{
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), TestTimeouts::action_timeout());
run_loop.Run();
}
DCHECK_EQ(filter->last_rect().x(), 0);
DCHECK_EQ(filter->last_rect().y(), 0);
}
// Test that an ET_SCROLL event sent to an out-of-process iframe correctly
// results in a scroll. This is only handled by RenderWidgetHostViewAura
// and is needed for trackpad scrolling on Chromebooks.
#if !defined(USE_AURA)
#define MAYBE_ScrollEventToOOPIF DISABLED_ScrollEventToOOPIF
#else
#define MAYBE_ScrollEventToOOPIF ScrollEventToOOPIF
#endif
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, MAYBE_ScrollEventToOOPIF) {
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_positioned_frame.html"));
NavigateToURL(shell(), main_url);
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child_node = root->child_at(0);
GURL site_url(embedded_test_server()->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(site_url, child_node->current_url());
EXPECT_NE(shell()->web_contents()->GetSiteInstance(),
child_node->current_frame_host()->GetSiteInstance());
RenderWidgetHostViewAura* rwhv_parent =
static_cast<RenderWidgetHostViewAura*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
SurfaceHitTestReadyNotifier notifier(
static_cast<RenderWidgetHostViewChildFrame*>(rwhv_child));
notifier.WaitForSurfaceReady();
// Create listener for input events.
TestInputEventObserver child_frame_monitor(
child_node->current_frame_host()->GetRenderWidgetHost());
// Send a ui::ScrollEvent that will hit test to the child frame.
ui::ScrollEvent scroll_event(ui::ET_SCROLL, gfx::Point(75, 75),
ui::EventTimeForNow(), ui::EF_NONE,
0, 10, // Offsets
0, 10, // Offset ordinals
2);
rwhv_parent->OnScrollEvent(&scroll_event);
// Verify that this a mouse wheel event was sent to the child frame renderer.
EXPECT_TRUE(child_frame_monitor.EventWasReceived());
EXPECT_EQ(child_frame_monitor.EventType(), blink::WebInputEvent::MouseWheel);
}
// Test that mouse events are being routed to the correct RenderWidgetHostView
// based on coordinates.
#if defined(OS_ANDROID) || defined(THREAD_SANITIZER)
// Browser process hit testing is not implemented on Android.
// https://crbug.com/491334
// The test times out often on TSAN bot.
// https://crbug.com/591170.
#define MAYBE_SurfaceHitTestTest DISABLED_SurfaceHitTestTest
#else
#define MAYBE_SurfaceHitTestTest SurfaceHitTestTest
#endif
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, MAYBE_SurfaceHitTestTest) {
SurfaceHitTestTestHelper(shell(), embedded_test_server());
}
// Same test as above, but runs in high-dpi mode.
#if defined(OS_ANDROID) || defined(OS_WIN)
// Browser process hit testing is not implemented on Android.
// https://crbug.com/491334
// Windows is disabled because of https://crbug.com/545547.
#define MAYBE_HighDPISurfaceHitTestTest DISABLED_SurfaceHitTestTest
#else
#define MAYBE_HighDPISurfaceHitTestTest SurfaceHitTestTest
#endif
IN_PROC_BROWSER_TEST_F(SitePerProcessHighDPIBrowserTest,
MAYBE_HighDPISurfaceHitTestTest) {
SurfaceHitTestTestHelper(shell(), embedded_test_server());
}
// Test that mouse events are being routed to the correct RenderWidgetHostView
// when there are nested out-of-process iframes.
#if defined(OS_ANDROID)
// Browser process hit testing is not implemented on Android.
// https://crbug.com/491334
#define MAYBE_NestedSurfaceHitTestTest DISABLED_NestedSurfaceHitTestTest
#else
#define MAYBE_NestedSurfaceHitTestTest NestedSurfaceHitTestTest
#endif
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
MAYBE_NestedSurfaceHitTestTest) {
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_positioned_nested_frames.html"));
NavigateToURL(shell(), main_url);
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* parent_iframe_node = root->child_at(0);
GURL site_url(embedded_test_server()->GetURL(
"a.com", "/frame_tree/page_with_positioned_frame.html"));
EXPECT_EQ(site_url, parent_iframe_node->current_url());
EXPECT_NE(shell()->web_contents()->GetSiteInstance(),
parent_iframe_node->current_frame_host()->GetSiteInstance());
FrameTreeNode* nested_iframe_node = parent_iframe_node->child_at(0);
GURL nested_site_url(
embedded_test_server()->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(nested_site_url, nested_iframe_node->current_url());
EXPECT_NE(shell()->web_contents()->GetSiteInstance(),
nested_iframe_node->current_frame_host()->GetSiteInstance());
EXPECT_NE(parent_iframe_node->current_frame_host()->GetSiteInstance(),
nested_iframe_node->current_frame_host()->GetSiteInstance());
// Create listeners for mouse events.
RenderWidgetHostMouseEventMonitor main_frame_monitor(
root->current_frame_host()->GetRenderWidgetHost());
RenderWidgetHostMouseEventMonitor nested_frame_monitor(
nested_iframe_node->current_frame_host()->GetRenderWidgetHost());
RenderWidgetHostInputEventRouter* router =
web_contents()->GetInputEventRouter();
RenderWidgetHostViewBase* root_view = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* rwhv_nested =
static_cast<RenderWidgetHostViewBase*>(
nested_iframe_node->current_frame_host()
->GetRenderWidgetHost()
->GetView());
SurfaceHitTestReadyNotifier notifier(
static_cast<RenderWidgetHostViewChildFrame*>(rwhv_nested));
notifier.WaitForSurfaceReady();
// Target input event to nested frame.
blink::WebMouseEvent nested_event;
nested_event.type = blink::WebInputEvent::MouseDown;
nested_event.button = blink::WebPointerProperties::Button::Left;
nested_event.x = 125;
nested_event.y = 125;
nested_event.clickCount = 1;
nested_frame_monitor.ResetEventReceived();
main_frame_monitor.ResetEventReceived();
router->RouteMouseEvent(root_view, &nested_event);
EXPECT_TRUE(nested_frame_monitor.EventWasReceived());
EXPECT_EQ(21, nested_frame_monitor.event().x);
EXPECT_EQ(21, nested_frame_monitor.event().y);
EXPECT_FALSE(main_frame_monitor.EventWasReceived());
}
// This test tests that browser process hittesting ignores frames with
// pointer-events: none.
#if defined(OS_ANDROID)
// Browser process hit testing is not implemented on Android.
// https://crbug.com/491334
#define MAYBE_SurfaceHitTestPointerEventsNone \
DISABLED_SurfaceHitTestPointerEventsNone
#elif defined(THREAD_SANITIZER)
// Flaky on TSAN. https://crbug.com/582277
#define MAYBE_SurfaceHitTestPointerEventsNone \
DISABLED_SurfaceHitTestPointerEventsNone
#else
#define MAYBE_SurfaceHitTestPointerEventsNone SurfaceHitTestPointerEventsNone
#endif
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
MAYBE_SurfaceHitTestPointerEventsNone) {
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_positioned_frame_pointer-events_none.html"));
NavigateToURL(shell(), main_url);
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child_node = root->child_at(0);
GURL site_url(embedded_test_server()->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(site_url, child_node->current_url());
EXPECT_NE(shell()->web_contents()->GetSiteInstance(),
child_node->current_frame_host()->GetSiteInstance());
// Create listeners for mouse events.
RenderWidgetHostMouseEventMonitor main_frame_monitor(
root->current_frame_host()->GetRenderWidgetHost());
RenderWidgetHostMouseEventMonitor child_frame_monitor(
child_node->current_frame_host()->GetRenderWidgetHost());
RenderWidgetHostInputEventRouter* router =
web_contents()->GetInputEventRouter();
RenderWidgetHostViewBase* root_view = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
SurfaceHitTestReadyNotifier notifier(
static_cast<RenderWidgetHostViewChildFrame*>(rwhv_child));
notifier.WaitForSurfaceReady();
// Target input event to child frame.
blink::WebMouseEvent child_event;
child_event.type = blink::WebInputEvent::MouseDown;
child_event.button = blink::WebPointerProperties::Button::Left;
child_event.x = 75;
child_event.y = 75;
child_event.clickCount = 1;
main_frame_monitor.ResetEventReceived();
child_frame_monitor.ResetEventReceived();
router->RouteMouseEvent(root_view, &child_event);
EXPECT_TRUE(main_frame_monitor.EventWasReceived());
EXPECT_EQ(75, main_frame_monitor.event().x);
EXPECT_EQ(75, main_frame_monitor.event().y);
EXPECT_FALSE(child_frame_monitor.EventWasReceived());
}
// Tests OOPIF rendering by checking that the RWH of the iframe generates
// OnSwapCompositorFrame message.
#if defined(OS_ANDROID)
// http://crbug.com/471850
#define MAYBE_CompositorFrameSwapped DISABLED_CompositorFrameSwapped
#else
#define MAYBE_CompositorFrameSwapped CompositorFrameSwapped
#endif
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
MAYBE_CompositorFrameSwapped) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(baz)"));
NavigateToURL(shell(), main_url);
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child_node = root->child_at(0);
GURL site_url(embedded_test_server()->GetURL(
"baz.com", "/cross_site_iframe_factory.html?baz()"));
EXPECT_EQ(site_url, child_node->current_url());
EXPECT_NE(shell()->web_contents()->GetSiteInstance(),
child_node->current_frame_host()->GetSiteInstance());
RenderWidgetHostViewBase* rwhv_base = static_cast<RenderWidgetHostViewBase*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
// Wait for OnSwapCompositorFrame message.
while (rwhv_base->RendererFrameNumber() <= 0) {
// TODO(lazyboy): Find a better way to avoid sleeping like this. See
// http://crbug.com/405282 for details.
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(),
base::TimeDelta::FromMilliseconds(10));
run_loop.Run();
}
}
// Ensure that OOPIFs are deleted after navigating to a new main frame.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, CleanupCrossSiteIframe) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(a,a(a,a(a)))"));
NavigateToURL(shell(), main_url);
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
TestNavigationObserver observer(shell()->web_contents());
// Load a cross-site page into both iframes.
GURL foo_url = embedded_test_server()->GetURL("foo.com", "/title2.html");
NavigateFrameToURL(root->child_at(0), foo_url);
EXPECT_TRUE(observer.last_navigation_succeeded());
EXPECT_EQ(foo_url, observer.last_navigation_url());
NavigateFrameToURL(root->child_at(1), foo_url);
EXPECT_TRUE(observer.last_navigation_succeeded());
EXPECT_EQ(foo_url, observer.last_navigation_url());
// Ensure that we have created a new process for the subframes.
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" |--Site B ------- proxies for A\n"
" +--Site B ------- proxies for A\n"
"Where A = http://a.com/\n"
" B = http://foo.com/",
DepictFrameTree(root));
int subframe_process_id = root->child_at(0)
->current_frame_host()
->GetSiteInstance()
->GetProcess()
->GetID();
int subframe_rvh_id = root->child_at(0)
->current_frame_host()
->render_view_host()
->GetRoutingID();
EXPECT_TRUE(RenderViewHost::FromID(subframe_process_id, subframe_rvh_id));
// Use Javascript in the parent to remove one of the frames and ensure that
// the subframe goes away.
EXPECT_TRUE(ExecuteScript(shell(),
"document.body.removeChild("
"document.querySelectorAll('iframe')[0])"));
ASSERT_EQ(1U, root->child_count());
// Load a new same-site page in the top-level frame and ensure the other
// subframe goes away.
GURL new_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
NavigateToURL(shell(), new_url);
ASSERT_EQ(0U, root->child_count());
// Ensure the RVH for the subframe gets cleaned up when the frame goes away.
EXPECT_FALSE(RenderViewHost::FromID(subframe_process_id, subframe_rvh_id));
}
// Ensure that root frames cannot be detached.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, RestrictFrameDetach) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(a,a(a,a(a)))"));
NavigateToURL(shell(), main_url);
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
TestNavigationObserver observer(shell()->web_contents());
// Load cross-site pages into both iframes.
GURL foo_url = embedded_test_server()->GetURL("foo.com", "/title2.html");
NavigateFrameToURL(root->child_at(0), foo_url);
EXPECT_TRUE(observer.last_navigation_succeeded());
EXPECT_EQ(foo_url, observer.last_navigation_url());
GURL bar_url = embedded_test_server()->GetURL("bar.com", "/title2.html");
NavigateFrameToURL(root->child_at(1), bar_url);
EXPECT_TRUE(observer.last_navigation_succeeded());
EXPECT_EQ(bar_url, observer.last_navigation_url());
// Ensure that we have created new processes for the subframes.
ASSERT_EQ(2U, root->child_count());
FrameTreeNode* foo_child = root->child_at(0);
SiteInstance* foo_site_instance =
foo_child->current_frame_host()->GetSiteInstance();
EXPECT_NE(shell()->web_contents()->GetSiteInstance(), foo_site_instance);
FrameTreeNode* bar_child = root->child_at(1);
SiteInstance* bar_site_instance =
bar_child->current_frame_host()->GetSiteInstance();
EXPECT_NE(shell()->web_contents()->GetSiteInstance(), bar_site_instance);
EXPECT_EQ(
" Site A ------------ proxies for B C\n"
" |--Site B ------- proxies for A C\n"
" +--Site C ------- proxies for A B\n"
"Where A = http://a.com/\n"
" B = http://foo.com/\n"
" C = http://bar.com/",
DepictFrameTree(root));
// Simulate an attempt to detach the root frame from foo_site_instance. This
// should kill foo_site_instance's process.
RenderFrameProxyHost* foo_mainframe_rfph =
root->render_manager()->GetRenderFrameProxyHost(foo_site_instance);
content::RenderProcessHostWatcher foo_terminated(
foo_mainframe_rfph->GetProcess(),
content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
FrameHostMsg_Detach evil_msg2(foo_mainframe_rfph->GetRoutingID());
IPC::IpcSecurityTestUtil::PwnMessageReceived(
foo_mainframe_rfph->GetProcess()->GetChannel(), evil_msg2);
foo_terminated.Wait();
EXPECT_EQ(
" Site A ------------ proxies for B C\n"
" |--Site B ------- proxies for A C\n"
" +--Site C ------- proxies for A B\n"
"Where A = http://a.com/\n"
" B = http://foo.com/ (no process)\n"
" C = http://bar.com/",
DepictFrameTree(root));
}
#if defined(OS_WIN)
// This test is flaky on Windows, see https://crbug.com/629419.
#define MAYBE_NavigateRemoteFrame DISABLED_NavigateRemoteFrame
#else
#define MAYBE_NavigateRemoteFrame NavigateRemoteFrame
#endif
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, MAYBE_NavigateRemoteFrame) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(a,a(a,a(a)))"));
NavigateToURL(shell(), main_url);
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
TestNavigationObserver observer(shell()->web_contents());
// Load same-site page into iframe.
FrameTreeNode* child = root->child_at(0);
GURL http_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
NavigateFrameToURL(child, http_url);
EXPECT_EQ(http_url, observer.last_navigation_url());
EXPECT_TRUE(observer.last_navigation_succeeded());
// Load cross-site page into iframe.
GURL url = embedded_test_server()->GetURL("foo.com", "/title2.html");
NavigateFrameToURL(root->child_at(0), url);
EXPECT_TRUE(observer.last_navigation_succeeded());
EXPECT_EQ(url, observer.last_navigation_url());
// Ensure that we have created a new process for the subframe.
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" |--Site B ------- proxies for A\n"
" +--Site A ------- proxies for B\n"
" |--Site A -- proxies for B\n"
" +--Site A -- proxies for B\n"
" +--Site A -- proxies for B\n"
"Where A = http://a.com/\n"
" B = http://foo.com/",
DepictFrameTree(root));
SiteInstance* site_instance = child->current_frame_host()->GetSiteInstance();
EXPECT_NE(shell()->web_contents()->GetSiteInstance(), site_instance);
// Emulate the main frame changing the src of the iframe such that it
// navigates cross-site.
url = embedded_test_server()->GetURL("bar.com", "/title3.html");
NavigateIframeToURL(shell()->web_contents(), "child-0", url);
EXPECT_TRUE(observer.last_navigation_succeeded());
EXPECT_EQ(url, observer.last_navigation_url());
// Check again that a new process is created and is different from the
// top level one and the previous one.
EXPECT_EQ(
" Site A ------------ proxies for C\n"
" |--Site C ------- proxies for A\n"
" +--Site A ------- proxies for C\n"
" |--Site A -- proxies for C\n"
" +--Site A -- proxies for C\n"
" +--Site A -- proxies for C\n"
"Where A = http://a.com/\n"
" C = http://bar.com/",
DepictFrameTree(root));
// Navigate back to the parent's origin and ensure we return to the
// parent's process.
NavigateFrameToURL(child, http_url);
EXPECT_EQ(http_url, observer.last_navigation_url());
EXPECT_TRUE(observer.last_navigation_succeeded());
EXPECT_EQ(shell()->web_contents()->GetSiteInstance(),
child->current_frame_host()->GetSiteInstance());
}
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
NavigateRemoteFrameToBlankAndDataURLs) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(a,a(a))"));
NavigateToURL(shell(), main_url);
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
TestNavigationObserver observer(shell()->web_contents());
// Load same-site page into iframe.
FrameTreeNode* child = root->child_at(0);
GURL http_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
NavigateFrameToURL(child, http_url);
EXPECT_EQ(http_url, observer.last_navigation_url());
EXPECT_TRUE(observer.last_navigation_succeeded());
EXPECT_EQ(
" Site A\n"
" |--Site A\n"
" +--Site A\n"
" +--Site A\n"
"Where A = http://a.com/",
DepictFrameTree(root));
// Load cross-site page into iframe.
GURL url = embedded_test_server()->GetURL("foo.com", "/title2.html");
NavigateFrameToURL(child, url);
EXPECT_TRUE(observer.last_navigation_succeeded());
EXPECT_EQ(url, observer.last_navigation_url());
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" |--Site B ------- proxies for A\n"
" +--Site A ------- proxies for B\n"
" +--Site A -- proxies for B\n"
"Where A = http://a.com/\n"
" B = http://foo.com/",
DepictFrameTree(root));
// Navigate iframe to a data URL. The navigation happens from a script in the
// parent frame, so the data URL should be committed in the same SiteInstance
// as the parent frame.
RenderFrameDeletedObserver deleted_observer1(
root->child_at(0)->current_frame_host());
GURL data_url("data:text/html,dataurl");
NavigateIframeToURL(shell()->web_contents(), "child-0", data_url);
EXPECT_TRUE(observer.last_navigation_succeeded());
EXPECT_EQ(data_url, observer.last_navigation_url());
// Wait for the old process to exit, to verify that the proxies go away.
deleted_observer1.WaitUntilDeleted();
// Ensure that we have navigated using the top level process.
EXPECT_EQ(
" Site A\n"
" |--Site A\n"
" +--Site A\n"
" +--Site A\n"
"Where A = http://a.com/",
DepictFrameTree(root));
// Load cross-site page into iframe.
url = embedded_test_server()->GetURL("bar.com", "/title2.html");
NavigateFrameToURL(child, url);
EXPECT_TRUE(observer.last_navigation_succeeded());
EXPECT_EQ(url, observer.last_navigation_url());
EXPECT_EQ(
" Site A ------------ proxies for C\n"
" |--Site C ------- proxies for A\n"
" +--Site A ------- proxies for C\n"
" +--Site A -- proxies for C\n"
"Where A = http://a.com/\n"
" C = http://bar.com/",
DepictFrameTree(root));
// Navigate iframe to about:blank. The navigation happens from a script in the
// parent frame, so it should be committed in the same SiteInstance as the
// parent frame.
RenderFrameDeletedObserver deleted_observer2(
root->child_at(0)->current_frame_host());
GURL about_blank_url("about:blank");
NavigateIframeToURL(shell()->web_contents(), "child-0", about_blank_url);
EXPECT_TRUE(observer.last_navigation_succeeded());
EXPECT_EQ(about_blank_url, observer.last_navigation_url());
// Wait for the old process to exit, to verify that the proxies go away.
deleted_observer2.WaitUntilDeleted();
// Ensure that we have navigated using the top level process.
EXPECT_EQ(
" Site A\n"
" |--Site A\n"
" +--Site A\n"
" +--Site A\n"
"Where A = http://a.com/",
DepictFrameTree(root));
// Load cross-site page into iframe again.
url = embedded_test_server()->GetURL("f00.com", "/title3.html");
NavigateFrameToURL(child, url);
EXPECT_TRUE(observer.last_navigation_succeeded());
EXPECT_EQ(url, observer.last_navigation_url());
EXPECT_EQ(
" Site A ------------ proxies for D\n"
" |--Site D ------- proxies for A\n"
" +--Site A ------- proxies for D\n"
" +--Site A -- proxies for D\n"
"Where A = http://a.com/\n"
" D = http://f00.com/",
DepictFrameTree(root));
// Navigate the iframe itself to about:blank using a script executing in its
// own context. It should stay in the same SiteInstance as before, not the
// parent one.
std::string script(
"window.domAutomationController.send("
"window.location.href = 'about:blank');");
TestFrameNavigationObserver frame_observer(child);
EXPECT_TRUE(ExecuteScript(child, script));
frame_observer.Wait();
EXPECT_EQ(about_blank_url, child->current_url());
// Ensure that we have navigated using the top level process.
EXPECT_EQ(
" Site A ------------ proxies for D\n"
" |--Site D ------- proxies for A\n"
" +--Site A ------- proxies for D\n"
" +--Site A -- proxies for D\n"
"Where A = http://a.com/\n"
" D = http://f00.com/",
DepictFrameTree(root));
}
// This test checks that killing a renderer process of a remote frame
// and then navigating some other frame to the same SiteInstance of the killed
// process works properly.
// This can be illustrated as follows,
// where 1/2/3 are FrameTreeNode-s and A/B are processes and B* is the killed
// B process:
//
// 1 A A A
// / \ -> / \ -> Kill B -> / \ -> Navigate 3 to B -> / \ .
// 2 3 B A B* A B* B
//
// Initially, node1.proxy_hosts_ = {B}
// After we kill B, we make sure B stays in node1.proxy_hosts_, then we navigate
// 3 to B and we expect that to complete normally.
// See http://crbug.com/432107.
//
// Note that due to http://crbug.com/450681, node2 cannot be re-navigated to
// site B and stays in not rendered state.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
NavigateRemoteFrameToKilledProcess) {
GURL main_url(embedded_test_server()->GetURL(
"foo.com", "/cross_site_iframe_factory.html?foo.com(bar.com, foo.com)"));
NavigateToURL(shell(), main_url);
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
TestNavigationObserver observer(shell()->web_contents());
ASSERT_EQ(2U, root->child_count());
// Make sure node2 points to the correct cross-site page.
GURL site_b_url = embedded_test_server()->GetURL(
"bar.com", "/cross_site_iframe_factory.html?bar.com()");
FrameTreeNode* node2 = root->child_at(0);
EXPECT_EQ(site_b_url, node2->current_url());
// Kill that cross-site renderer.
RenderProcessHost* child_process =
node2->current_frame_host()->GetProcess();
RenderProcessHostWatcher crash_observer(
child_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
child_process->Shutdown(0, false);
crash_observer.Wait();
// Now navigate the second iframe (node3) to the same site as the node2.
FrameTreeNode* node3 = root->child_at(1);
NavigateFrameToURL(node3, site_b_url);
EXPECT_TRUE(observer.last_navigation_succeeded());
EXPECT_EQ(site_b_url, observer.last_navigation_url());
}
// This test is similar to
// SitePerProcessBrowserTest.NavigateRemoteFrameToKilledProcess with
// addition that node2 also has a cross-origin frame to site C.
//
// 1 A A A
// / \ / \ / \ / \ .
// 2 3 -> B A -> Kill B -> B* A -> Navigate 3 -> B* B
// / /
// 4 C
//
// Initially, node1.proxy_hosts_ = {B, C}
// After we kill B, we make sure B stays in node1.proxy_hosts_, but
// C gets cleared from node1.proxy_hosts_.
//
// Note that due to http://crbug.com/450681, node2 cannot be re-navigated to
// site B and stays in not rendered state.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
NavigateRemoteFrameToKilledProcessWithSubtree) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(bar(baz), a)"));
NavigateToURL(shell(), main_url);
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
TestNavigationObserver observer(shell()->web_contents());
ASSERT_EQ(2U, root->child_count());
GURL site_b_url(embedded_test_server()->GetURL(
"bar.com", "/cross_site_iframe_factory.html?bar(baz())"));
// We can't use a TestNavigationObserver to verify the URL here,
// since the frame has children that may have clobbered it in the observer.
EXPECT_EQ(site_b_url, root->child_at(0)->current_url());
// Ensure that a new process is created for node2.
EXPECT_NE(shell()->web_contents()->GetSiteInstance(),
root->child_at(0)->current_frame_host()->GetSiteInstance());
// Ensure that a new process is *not* created for node3.
EXPECT_EQ(shell()->web_contents()->GetSiteInstance(),
root->child_at(1)->current_frame_host()->GetSiteInstance());
ASSERT_EQ(1U, root->child_at(0)->child_count());
// Make sure node4 points to the correct cross-site page.
FrameTreeNode* node4 = root->child_at(0)->child_at(0);
GURL site_c_url(embedded_test_server()->GetURL(
"baz.com", "/cross_site_iframe_factory.html?baz()"));
EXPECT_EQ(site_c_url, node4->current_url());
// |site_instance_c| is expected to go away once we kill |child_process_b|
// below, so create a local scope so we can extend the lifetime of
// |site_instance_c| with a refptr.
{
// Initially each frame has proxies for the other sites.
EXPECT_EQ(
" Site A ------------ proxies for B C\n"
" |--Site B ------- proxies for A C\n"
" | +--Site C -- proxies for A B\n"
" +--Site A ------- proxies for B C\n"
"Where A = http://a.com/\n"
" B = http://bar.com/\n"
" C = http://baz.com/",
DepictFrameTree(root));
// Kill the render process for Site B.
RenderProcessHost* child_process_b =
root->child_at(0)->current_frame_host()->GetProcess();
RenderProcessHostWatcher crash_observer(
child_process_b, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
child_process_b->Shutdown(0, false);
crash_observer.Wait();
// The Site C frame (a child of the crashed Site B frame) should go away,
// and there should be no remaining proxies for site C anywhere.
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" |--Site B ------- proxies for A\n"
" +--Site A ------- proxies for B\n"
"Where A = http://a.com/\n"
" B = http://bar.com/ (no process)",
DepictFrameTree(root));
}
// Now navigate the second iframe (node3) to Site B also.
FrameTreeNode* node3 = root->child_at(1);
GURL url = embedded_test_server()->GetURL("bar.com", "/title1.html");
NavigateFrameToURL(node3, url);
EXPECT_TRUE(observer.last_navigation_succeeded());
EXPECT_EQ(url, observer.last_navigation_url());
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" |--Site B ------- proxies for A\n"
" +--Site B ------- proxies for A\n"
"Where A = http://a.com/\n"
" B = http://bar.com/",
DepictFrameTree(root));
}
// Ensure that the renderer process doesn't crash when the main frame navigates
// a remote child to a page that results in a network error.
// See https://crbug.com/558016.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, NavigateRemoteAfterError) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(a)"));
NavigateToURL(shell(), main_url);
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
// Load same-site page into iframe.
{
TestNavigationObserver observer(shell()->web_contents());
FrameTreeNode* child = root->child_at(0);
GURL http_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
NavigateFrameToURL(child, http_url);
EXPECT_EQ(http_url, observer.last_navigation_url());
EXPECT_TRUE(observer.last_navigation_succeeded());
observer.Wait();
}
// Load cross-site page into iframe.
{
TestNavigationObserver observer(shell()->web_contents());
FrameTreeNode* child = root->child_at(0);
GURL url = embedded_test_server()->GetURL("foo.com", "/title2.html");
NavigateFrameToURL(root->child_at(0), url);
EXPECT_TRUE(observer.last_navigation_succeeded());
EXPECT_EQ(url, observer.last_navigation_url());
observer.Wait();
// Ensure that we have created a new process for the subframe.
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" +--Site B ------- proxies for A\n"
"Where A = http://a.com/\n"
" B = http://foo.com/",
DepictFrameTree(root));
SiteInstance* site_instance =
child->current_frame_host()->GetSiteInstance();
EXPECT_NE(shell()->web_contents()->GetSiteInstance(), site_instance);
}
// Stop the test server and try to navigate the remote frame.
{
GURL url = embedded_test_server()->GetURL("bar.com", "/title3.html");
EXPECT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());
NavigateIframeToURL(shell()->web_contents(), "child-0", url);
}
}
// Ensure that a cross-site page ends up in the correct process when it
// successfully loads after earlier encountering a network error for it.
// See https://crbug.com/560511.
// TODO(creis): Make the net error page show in the correct process as well,
// per https://crbug.com/588314.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, ProcessTransferAfterError) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(a)"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
FrameTreeNode* child = root->child_at(0);
GURL url_a = child->current_url();
// Disable host resolution in the test server and try to navigate the subframe
// cross-site, which will lead to a committed net error (which looks like
// success to the TestNavigationObserver).
GURL url_b = embedded_test_server()->GetURL("b.com", "/title3.html");
host_resolver()->ClearRules();
TestNavigationObserver observer(shell()->web_contents());
NavigateIframeToURL(shell()->web_contents(), "child-0", url_b);
EXPECT_TRUE(observer.last_navigation_succeeded());
EXPECT_EQ(url_b, observer.last_navigation_url());
EXPECT_EQ(2, shell()->web_contents()->GetController().GetEntryCount());
// PlzNavigate: Ensure that we have created a new process for the subframe.
if (IsBrowserSideNavigationEnabled()) {
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" +--Site B ------- proxies for A\n"
"Where A = http://a.com/\n"
" B = http://b.com/",
DepictFrameTree(root));
EXPECT_NE(shell()->web_contents()->GetSiteInstance(),
child->current_frame_host()->GetSiteInstance());
}
// The FrameTreeNode should update its URL (so that we don't affect other uses
// of the API), but the frame's last_successful_url shouldn't change and the
// origin should be empty.
// PlzNavigate: We have switched RenderFrameHosts for the subframe, so the
// last succesful url should be empty (since the frame only loaded an error
// page).
if (IsBrowserSideNavigationEnabled())
EXPECT_EQ(GURL(), child->current_frame_host()->last_successful_url());
else
EXPECT_EQ(url_a, child->current_frame_host()->last_successful_url());
EXPECT_EQ(url_b, child->current_url());
EXPECT_EQ("null", child->current_origin().Serialize());
// Try again after re-enabling host resolution.
host_resolver()->AddRule("*", "127.0.0.1");
NavigateIframeToURL(shell()->web_contents(), "child-0", url_b);
EXPECT_TRUE(observer.last_navigation_succeeded());
EXPECT_EQ(url_b, observer.last_navigation_url());
// The FrameTreeNode should have updated its URL and origin.
EXPECT_EQ(url_b, child->current_frame_host()->last_successful_url());
EXPECT_EQ(url_b, child->current_url());
EXPECT_EQ(url_b.GetOrigin().spec(),
child->current_origin().Serialize() + '/');
// Ensure that we have created a new process for the subframe.
// PlzNavigate: the subframe should still be in its separate process.
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" +--Site B ------- proxies for A\n"
"Where A = http://a.com/\n"
" B = http://b.com/",
DepictFrameTree(root));
EXPECT_NE(shell()->web_contents()->GetSiteInstance(),
child->current_frame_host()->GetSiteInstance());
// Make sure that the navigation replaced the error page and that going back
// ends up on the original site.
EXPECT_EQ(2, shell()->web_contents()->GetController().GetEntryCount());
{
RenderFrameDeletedObserver deleted_observer(child->current_frame_host());
TestNavigationObserver back_load_observer(shell()->web_contents());
shell()->web_contents()->GetController().GoBack();
back_load_observer.Wait();
// Wait for the old process to exit, to verify that the proxies go away.
deleted_observer.WaitUntilDeleted();
}
EXPECT_EQ(
" Site A\n"
" +--Site A\n"
"Where A = http://a.com/",
DepictFrameTree(root));
EXPECT_EQ(shell()->web_contents()->GetSiteInstance(),
child->current_frame_host()->GetSiteInstance());
EXPECT_EQ(url_a, child->current_frame_host()->last_successful_url());
EXPECT_EQ(url_a, child->current_url());
EXPECT_EQ(url_a.GetOrigin().spec(),
child->current_origin().Serialize() + '/');
}
// Verify that killing a cross-site frame's process B and then navigating a
// frame to B correctly recreates all proxies in B.
//
// 1 A A A
// / | \ / | \ / | \ / | \ .
// 2 3 4 -> B A A -> Kill B -> B* A A -> B* B A
//
// After the last step, the test sends a postMessage from node 3 to node 4,
// verifying that a proxy for node 4 has been recreated in process B. This
// verifies the fix for https://crbug.com/478892.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
NavigatingToKilledProcessRestoresAllProxies) {
// Navigate to a page with three frames: one cross-site and two same-site.
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/frame_tree/page_with_three_frames.html"));
NavigateToURL(shell(), main_url);
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
TestNavigationObserver observer(shell()->web_contents());
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" |--Site B ------- proxies for A\n"
" |--Site A ------- proxies for B\n"
" +--Site A ------- proxies for B\n"
"Where A = http://a.com/\n"
" B = http://b.com/",
DepictFrameTree(root));
// Kill the first subframe's b.com renderer.
RenderProcessHost* child_process =
root->child_at(0)->current_frame_host()->GetProcess();
RenderProcessHostWatcher crash_observer(
child_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
child_process->Shutdown(0, false);
crash_observer.Wait();
// Navigate the second subframe to b.com to recreate the b.com process.
GURL b_url = embedded_test_server()->GetURL("b.com", "/post_message.html");
NavigateFrameToURL(root->child_at(1), b_url);
EXPECT_TRUE(observer.last_navigation_succeeded());
EXPECT_EQ(b_url, observer.last_navigation_url());
EXPECT_TRUE(root->child_at(1)->current_frame_host()->IsRenderFrameLive());
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" |--Site B ------- proxies for A\n"
" |--Site B ------- proxies for A\n"
" +--Site A ------- proxies for B\n"
"Where A = http://a.com/\n"
" B = http://b.com/",
DepictFrameTree(root));
// Check that third subframe's proxy is available in the b.com process by
// sending it a postMessage from second subframe, and waiting for a reply.
PostMessageAndWaitForReply(root->child_at(1),
"postToSibling('subframe-msg','frame3')",
"\"done-frame2\"");
}
// Verify that proxy creation doesn't recreate a crashed process if no frame
// will be created in it.
//
// 1 A A A
// / | \ / | \ / | \ / | \ .
// 2 3 4 -> B A A -> Kill B -> B* A A -> B* A A
// \ .
// A
//
// The test kills process B (node 2), creates a child frame of node 4 in
// process A, and then checks that process B isn't resurrected to create a
// proxy for the new child frame. See https://crbug.com/476846.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
CreateChildFrameAfterKillingProcess) {
// Navigate to a page with three frames: one cross-site and two same-site.
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/frame_tree/page_with_three_frames.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" |--Site B ------- proxies for A\n"
" |--Site A ------- proxies for B\n"
" +--Site A ------- proxies for B\n"
"Where A = http://a.com/\n"
" B = http://b.com/",
DepictFrameTree(root));
SiteInstance* b_site_instance =
root->child_at(0)->current_frame_host()->GetSiteInstance();
// Kill the first subframe's renderer (B).
RenderProcessHost* child_process =
root->child_at(0)->current_frame_host()->GetProcess();
RenderProcessHostWatcher crash_observer(
child_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
child_process->Shutdown(0, false);
crash_observer.Wait();
// Add a new child frame to the third subframe.
RenderFrameHostCreatedObserver frame_observer(shell()->web_contents(), 1);
EXPECT_TRUE(ExecuteScript(
root->child_at(2),
"document.body.appendChild(document.createElement('iframe'));"));
frame_observer.Wait();
// The new frame should have a RenderFrameProxyHost for B, but it should not
// be alive, and B should still not have a process (verified by last line of
// expected DepictFrameTree output).
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" |--Site B ------- proxies for A\n"
" |--Site A ------- proxies for B\n"
" +--Site A ------- proxies for B\n"
" +--Site A -- proxies for B\n"
"Where A = http://a.com/\n"
" B = http://b.com/ (no process)",
DepictFrameTree(root));
FrameTreeNode* grandchild = root->child_at(2)->child_at(0);
RenderFrameProxyHost* grandchild_rfph =
grandchild->render_manager()->GetRenderFrameProxyHost(b_site_instance);
EXPECT_FALSE(grandchild_rfph->is_render_frame_proxy_live());
// Navigate the second subframe to b.com to recreate process B.
TestNavigationObserver observer(shell()->web_contents());
GURL b_url = embedded_test_server()->GetURL("b.com", "/title1.html");
NavigateFrameToURL(root->child_at(1), b_url);
EXPECT_TRUE(observer.last_navigation_succeeded());
EXPECT_EQ(b_url, observer.last_navigation_url());
// Ensure that the grandchild RenderFrameProxy in B was created when process
// B was restored.
EXPECT_TRUE(grandchild_rfph->is_render_frame_proxy_live());
}
// Verify that creating a child frame after killing and reloading an opener
// process doesn't crash. See https://crbug.com/501152.
// 1. Navigate to site A.
// 2. Open a popup with window.open and navigate it cross-process to site B.
// 3. Kill process A for the original tab.
// 4. Reload the original tab to resurrect process A.
// 5. Add a child frame to the top-level frame in the popup tab B.
// In step 5, we try to create proxies for the child frame in all SiteInstances
// for which its parent has proxies. This includes A. However, even though
// process A is live (step 4), the parent proxy in A is not live (which was
// incorrectly assumed previously). This is because step 4 does not resurrect
// proxies for popups opened before the crash.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
CreateChildFrameAfterKillingOpener) {
GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
SiteInstance* site_instance_a = root->current_frame_host()->GetSiteInstance();
// Open a popup and navigate it cross-process to b.com.
ShellAddedObserver new_shell_observer;
EXPECT_TRUE(ExecuteScript(root, "popup = window.open('about:blank');"));
Shell* popup = new_shell_observer.GetShell();
GURL popup_url(embedded_test_server()->GetURL("b.com", "/title2.html"));
EXPECT_TRUE(NavigateToURL(popup, popup_url));
// Verify that each top-level frame has proxies in the other's SiteInstance.
FrameTreeNode* popup_root =
static_cast<WebContentsImpl*>(popup->web_contents())
->GetFrameTree()
->root();
EXPECT_EQ(
" Site A ------------ proxies for B\n"
"Where A = http://a.com/\n"
" B = http://b.com/",
DepictFrameTree(root));
EXPECT_EQ(
" Site B ------------ proxies for A\n"
"Where A = http://a.com/\n"
" B = http://b.com/",
DepictFrameTree(popup_root));
// Kill the first window's renderer (a.com).
RenderProcessHost* child_process = root->current_frame_host()->GetProcess();
RenderProcessHostWatcher crash_observer(
child_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
child_process->Shutdown(0, false);
crash_observer.Wait();
EXPECT_FALSE(root->current_frame_host()->IsRenderFrameLive());
// The proxy for the popup in a.com should've died.
RenderFrameProxyHost* rfph =
popup_root->render_manager()->GetRenderFrameProxyHost(site_instance_a);
EXPECT_FALSE(rfph->is_render_frame_proxy_live());
// Recreate the a.com renderer.
EXPECT_TRUE(NavigateToURL(shell(), main_url));
EXPECT_TRUE(root->current_frame_host()->IsRenderFrameLive());
// The popup's proxy in a.com should still not be live. Re-navigating the
// main window to a.com doesn't reinitialize a.com proxies for popups
// previously opened from the main window.
EXPECT_FALSE(rfph->is_render_frame_proxy_live());
// Add a new child frame on the popup.
RenderFrameHostCreatedObserver frame_observer(popup->web_contents(), 1);
EXPECT_TRUE(ExecuteScript(
popup, "document.body.appendChild(document.createElement('iframe'));"));
frame_observer.Wait();
// Both the child frame's and its parent's proxies should still not be live.
// The main page can't reach them since it lost reference to the popup after
// it crashed, so there is no need to create them.
EXPECT_FALSE(rfph->is_render_frame_proxy_live());
RenderFrameProxyHost* child_rfph =
popup_root->child_at(0)->render_manager()->GetRenderFrameProxyHost(
site_instance_a);
EXPECT_TRUE(child_rfph);
EXPECT_FALSE(child_rfph->is_render_frame_proxy_live());
}
// In A-embed-B-embed-C scenario, verify that killing process B clears proxies
// of C from the tree.
//
// 1 A A
// / \ / \ / \ .
// 2 3 -> B A -> Kill B -> B* A
// / /
// 4 C
//
// node1 is the root.
// Initially, both node1.proxy_hosts_ and node3.proxy_hosts_ contain C.
// After we kill B, make sure proxies for C are cleared.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
KillingRendererClearsDescendantProxies) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/frame_tree/page_with_two_frames_nested.html"));
NavigateToURL(shell(), main_url);
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
ASSERT_EQ(2U, root->child_count());
GURL site_b_url(
embedded_test_server()->GetURL(
"bar.com", "/frame_tree/page_with_one_frame.html"));
// We can't use a TestNavigationObserver to verify the URL here,
// since the frame has children that may have clobbered it in the observer.
EXPECT_EQ(site_b_url, root->child_at(0)->current_url());
// Ensure that a new process is created for node2.
EXPECT_NE(shell()->web_contents()->GetSiteInstance(),
root->child_at(0)->current_frame_host()->GetSiteInstance());
// Ensure that a new process is *not* created for node3.
EXPECT_EQ(shell()->web_contents()->GetSiteInstance(),
root->child_at(1)->current_frame_host()->GetSiteInstance());
ASSERT_EQ(1U, root->child_at(0)->child_count());
// Make sure node4 points to the correct cross-site-page.
FrameTreeNode* node4 = root->child_at(0)->child_at(0);
GURL site_c_url(embedded_test_server()->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(site_c_url, node4->current_url());
// |site_instance_c|'s frames and proxies are expected to go away once we kill
// |child_process_b| below.
scoped_refptr<SiteInstanceImpl> site_instance_c =
node4->current_frame_host()->GetSiteInstance();
// Initially proxies for both B and C will be present in the root.
EXPECT_EQ(
" Site A ------------ proxies for B C\n"
" |--Site B ------- proxies for A C\n"
" | +--Site C -- proxies for A B\n"
" +--Site A ------- proxies for B C\n"
"Where A = http://a.com/\n"
" B = http://bar.com/\n"
" C = http://baz.com/",
DepictFrameTree(root));
EXPECT_GT(site_instance_c->active_frame_count(), 0U);
// Kill process B.
RenderProcessHost* child_process_b =
root->child_at(0)->current_frame_host()->GetProcess();
RenderProcessHostWatcher crash_observer(
child_process_b, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
child_process_b->Shutdown(0, false);
crash_observer.Wait();
// Make sure proxy C has gone from root.
// Make sure proxy C has gone from node3 as well.
// Make sure proxy B stays around in root and node3.
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" |--Site B ------- proxies for A\n"
" +--Site A ------- proxies for B\n"
"Where A = http://a.com/\n"
" B = http://bar.com/ (no process)",
DepictFrameTree(root));
EXPECT_EQ(0U, site_instance_c->active_frame_count());
}
// Crash a subframe and ensures its children are cleared from the FrameTree.
// See http://crbug.com/338508.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, CrashSubframe) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
NavigateToURL(shell(), main_url);
// Check the subframe process.
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" +--Site B ------- proxies for A\n"
"Where A = http://a.com/\n"
" B = http://b.com/",
DepictFrameTree(root));
FrameTreeNode* child = root->child_at(0);
EXPECT_TRUE(
child->current_frame_host()->render_view_host()->IsRenderViewLive());
EXPECT_TRUE(child->current_frame_host()->IsRenderFrameLive());
// Crash the subframe process.
RenderProcessHost* root_process = root->current_frame_host()->GetProcess();
RenderProcessHost* child_process = child->current_frame_host()->GetProcess();
{
RenderProcessHostWatcher crash_observer(
child_process,
RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
child_process->Shutdown(0, false);
crash_observer.Wait();
}
// Ensure that the child frame still exists but has been cleared.
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" +--Site B ------- proxies for A\n"
"Where A = http://a.com/\n"
" B = http://b.com/ (no process)",
DepictFrameTree(root));
EXPECT_EQ(1U, root->child_count());
EXPECT_EQ(main_url, root->current_url());
EXPECT_EQ(GURL(), child->current_url());
EXPECT_FALSE(
child->current_frame_host()->render_view_host()->IsRenderViewLive());
EXPECT_FALSE(child->current_frame_host()->IsRenderFrameLive());
EXPECT_FALSE(child->current_frame_host()->render_frame_created_);
// Now crash the top-level page to clear the child frame.
{
RenderProcessHostWatcher crash_observer(
root_process,
RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
root_process->Shutdown(0, false);
crash_observer.Wait();
}
EXPECT_EQ(0U, root->child_count());
EXPECT_EQ(GURL(), root->current_url());
}
// When a new subframe is added, related SiteInstances that can reach the
// subframe should create proxies for it (https://crbug.com/423587). This test
// checks that if A embeds B and later adds a new subframe A2, A2 gets a proxy
// in B's process.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, CreateProxiesForNewFrames) {
GURL main_url(embedded_test_server()->GetURL(
"b.com", "/frame_tree/page_with_one_frame.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
// Make sure the frame starts out at the correct cross-site URL.
EXPECT_EQ(embedded_test_server()->GetURL("baz.com", "/title1.html"),
root->child_at(0)->current_url());
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" +--Site B ------- proxies for A\n"
"Where A = http://b.com/\n"
" B = http://baz.com/",
DepictFrameTree(root));
// Add a new child frame to the top-level frame.
RenderFrameHostCreatedObserver frame_observer(shell()->web_contents(), 1);
EXPECT_TRUE(ExecuteScript(shell(), "addFrame('data:text/html,foo');"));
frame_observer.Wait();
// The new frame should have a proxy in Site B, for use by the old frame.
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" |--Site B ------- proxies for A\n"
" +--Site A ------- proxies for B\n"
"Where A = http://b.com/\n"
" B = http://baz.com/",
DepictFrameTree(root));
}
// TODO(nasko): Disable this test until out-of-process iframes is ready and the
// security checks are back in place.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
DISABLED_CrossSiteIframeRedirectOnce) {
net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
https_server.ServeFilesFromSourceDirectory("content/test/data");
ASSERT_TRUE(https_server.Start());
GURL main_url(embedded_test_server()->GetURL("/site_per_process_main.html"));
GURL http_url(embedded_test_server()->GetURL("/title1.html"));
GURL https_url(https_server.GetURL("/title1.html"));
NavigateToURL(shell(), main_url);
TestNavigationObserver observer(shell()->web_contents());
{
// Load cross-site client-redirect page into Iframe.
// Should be blocked.
GURL client_redirect_https_url(
https_server.GetURL("/client-redirect?/title1.html"));
EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test",
client_redirect_https_url));
// DidFailProvisionalLoad when navigating to client_redirect_https_url.
EXPECT_EQ(observer.last_navigation_url(), client_redirect_https_url);
EXPECT_FALSE(observer.last_navigation_succeeded());
}
{
// Load cross-site server-redirect page into Iframe,
// which redirects to same-site page.
GURL server_redirect_http_url(
https_server.GetURL("/server-redirect?" + http_url.spec()));
EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test",
server_redirect_http_url));
EXPECT_EQ(observer.last_navigation_url(), http_url);
EXPECT_TRUE(observer.last_navigation_succeeded());
}
{
// Load cross-site server-redirect page into Iframe,
// which redirects to cross-site page.
GURL server_redirect_http_url(
https_server.GetURL("/server-redirect?/title1.html"));
EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test",
server_redirect_http_url));
// DidFailProvisionalLoad when navigating to https_url.
EXPECT_EQ(observer.last_navigation_url(), https_url);
EXPECT_FALSE(observer.last_navigation_succeeded());
}
{
// Load same-site server-redirect page into Iframe,
// which redirects to cross-site page.
GURL server_redirect_http_url(
embedded_test_server()->GetURL("/server-redirect?" + https_url.spec()));
EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test",
server_redirect_http_url));
EXPECT_EQ(observer.last_navigation_url(), https_url);
EXPECT_FALSE(observer.last_navigation_succeeded());
}
{
// Load same-site client-redirect page into Iframe,
// which redirects to cross-site page.
GURL client_redirect_http_url(
embedded_test_server()->GetURL("/client-redirect?" + https_url.spec()));
RedirectNotificationObserver load_observer2(
NOTIFICATION_LOAD_STOP,
Source<NavigationController>(
&shell()->web_contents()->GetController()));
EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test",
client_redirect_http_url));
// Same-site Client-Redirect Page should be loaded successfully.
EXPECT_EQ(observer.last_navigation_url(), client_redirect_http_url);
EXPECT_TRUE(observer.last_navigation_succeeded());
// Redirecting to Cross-site Page should be blocked.
load_observer2.Wait();
EXPECT_EQ(observer.last_navigation_url(), https_url);
EXPECT_FALSE(observer.last_navigation_succeeded());
}
{
// Load same-site server-redirect page into Iframe,
// which redirects to same-site page.
GURL server_redirect_http_url(
embedded_test_server()->GetURL("/server-redirect?/title1.html"));
EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test",
server_redirect_http_url));
EXPECT_EQ(observer.last_navigation_url(), http_url);
EXPECT_TRUE(observer.last_navigation_succeeded());
}
{
// Load same-site client-redirect page into Iframe,
// which redirects to same-site page.
GURL client_redirect_http_url(
embedded_test_server()->GetURL("/client-redirect?" + http_url.spec()));
RedirectNotificationObserver load_observer2(
NOTIFICATION_LOAD_STOP,
Source<NavigationController>(
&shell()->web_contents()->GetController()));
EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test",
client_redirect_http_url));
// Same-site Client-Redirect Page should be loaded successfully.
EXPECT_EQ(observer.last_navigation_url(), client_redirect_http_url);
EXPECT_TRUE(observer.last_navigation_succeeded());
// Redirecting to Same-site Page should be loaded successfully.
load_observer2.Wait();
EXPECT_EQ(observer.last_navigation_url(), http_url);
EXPECT_TRUE(observer.last_navigation_succeeded());
}
}
// TODO(nasko): Disable this test until out-of-process iframes is ready and the
// security checks are back in place.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
DISABLED_CrossSiteIframeRedirectTwice) {
net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
https_server.ServeFilesFromSourceDirectory("content/test/data");
ASSERT_TRUE(https_server.Start());
GURL main_url(embedded_test_server()->GetURL("/site_per_process_main.html"));
GURL http_url(embedded_test_server()->GetURL("/title1.html"));
GURL https_url(https_server.GetURL("/title1.html"));
NavigateToURL(shell(), main_url);
TestNavigationObserver observer(shell()->web_contents());
{
// Load client-redirect page pointing to a cross-site client-redirect page,
// which eventually redirects back to same-site page.
GURL client_redirect_https_url(
https_server.GetURL("/client-redirect?" + http_url.spec()));
GURL client_redirect_http_url(embedded_test_server()->GetURL(
"/client-redirect?" + client_redirect_https_url.spec()));
// We should wait until second client redirect get cancelled.
RedirectNotificationObserver load_observer2(
NOTIFICATION_LOAD_STOP,
Source<NavigationController>(
&shell()->web_contents()->GetController()));
EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test",
client_redirect_http_url));
// DidFailProvisionalLoad when navigating to client_redirect_https_url.
load_observer2.Wait();
EXPECT_EQ(observer.last_navigation_url(), client_redirect_https_url);
EXPECT_FALSE(observer.last_navigation_succeeded());
}
{
// Load server-redirect page pointing to a cross-site server-redirect page,
// which eventually redirect back to same-site page.
GURL server_redirect_https_url(
https_server.GetURL("/server-redirect?" + http_url.spec()));
GURL server_redirect_http_url(embedded_test_server()->GetURL(
"/server-redirect?" + server_redirect_https_url.spec()));
EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test",
server_redirect_http_url));
EXPECT_EQ(observer.last_navigation_url(), http_url);
EXPECT_TRUE(observer.last_navigation_succeeded());
}
{
// Load server-redirect page pointing to a cross-site server-redirect page,
// which eventually redirects back to cross-site page.
GURL server_redirect_https_url(
https_server.GetURL("/server-redirect?" + https_url.spec()));
GURL server_redirect_http_url(embedded_test_server()->GetURL(
"/server-redirect?" + server_redirect_https_url.spec()));
EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test",
server_redirect_http_url));
// DidFailProvisionalLoad when navigating to https_url.
EXPECT_EQ(observer.last_navigation_url(), https_url);
EXPECT_FALSE(observer.last_navigation_succeeded());
}
{
// Load server-redirect page pointing to a cross-site client-redirect page,
// which eventually redirects back to same-site page.
GURL client_redirect_http_url(
https_server.GetURL("/client-redirect?" + http_url.spec()));
GURL server_redirect_http_url(embedded_test_server()->GetURL(
"/server-redirect?" + client_redirect_http_url.spec()));
EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test",
server_redirect_http_url));
// DidFailProvisionalLoad when navigating to client_redirect_http_url.
EXPECT_EQ(observer.last_navigation_url(), client_redirect_http_url);
EXPECT_FALSE(observer.last_navigation_succeeded());
}
}
// Ensure that when navigating a frame cross-process RenderFrameProxyHosts are
// created in the FrameTree skipping the subtree of the navigating frame.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
ProxyCreationSkipsSubtree) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(a,a(a,a(a)))"));
NavigateToURL(shell(), main_url);
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
EXPECT_TRUE(root->child_at(1) != NULL);
EXPECT_EQ(2U, root->child_at(1)->child_count());
{
// Load same-site page into iframe.
TestNavigationObserver observer(shell()->web_contents());
GURL http_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
NavigateFrameToURL(root->child_at(0), http_url);
EXPECT_EQ(http_url, observer.last_navigation_url());
EXPECT_TRUE(observer.last_navigation_succeeded());
EXPECT_EQ(
" Site A\n"
" |--Site A\n"
" +--Site A\n"
" |--Site A\n"
" +--Site A\n"
" +--Site A\n"
"Where A = http://a.com/",
DepictFrameTree(root));
}
// Create the cross-site URL to navigate to.
GURL cross_site_url =
embedded_test_server()->GetURL("foo.com", "/frame_tree/title2.html");
// Load cross-site page into the second iframe without waiting for the
// navigation to complete. Once LoadURLWithParams returns, we would expect
// proxies to have been created in the frame tree, but children of the
// navigating frame to still be present. The reason is that we don't run the
// message loop, so no IPCs that alter the frame tree can be processed.
FrameTreeNode* child = root->child_at(1);
SiteInstance* site = NULL;
bool browser_side_navigation = IsBrowserSideNavigationEnabled();
std::string cross_site_rfh_type =
browser_side_navigation ? "speculative" : "pending";
{
TestNavigationObserver observer(shell()->web_contents());
TestFrameNavigationObserver navigation_observer(child);
NavigationController::LoadURLParams params(cross_site_url);
params.transition_type = PageTransitionFromInt(ui::PAGE_TRANSITION_LINK);
params.frame_tree_node_id = child->frame_tree_node_id();
child->navigator()->GetController()->LoadURLWithParams(params);
if (browser_side_navigation) {
site = child->render_manager()
->speculative_frame_host()
->GetSiteInstance();
} else {
site = child->render_manager()->pending_frame_host()->GetSiteInstance();
}
EXPECT_NE(shell()->web_contents()->GetSiteInstance(), site);
std::string tree = base::StringPrintf(
" Site A ------------ proxies for B\n"
" |--Site A ------- proxies for B\n"
" +--Site A (B %s)\n"
" |--Site A\n"
" +--Site A\n"
" +--Site A\n"
"Where A = http://a.com/\n"
" B = http://foo.com/",
cross_site_rfh_type.c_str());
EXPECT_EQ(tree, DepictFrameTree(root));
// Now that the verification is done, run the message loop and wait for the
// navigation to complete.
navigation_observer.Wait();
EXPECT_FALSE(child->render_manager()->pending_frame_host());
EXPECT_TRUE(observer.last_navigation_succeeded());
EXPECT_EQ(cross_site_url, observer.last_navigation_url());
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" |--Site A ------- proxies for B\n"
" +--Site B ------- proxies for A\n"
"Where A = http://a.com/\n"
" B = http://foo.com/",
DepictFrameTree(root));
}
// Load another cross-site page into the same iframe.
cross_site_url = embedded_test_server()->GetURL("bar.com", "/title3.html");
{
// Perform the same checks as the first cross-site navigation, since
// there have been issues in subsequent cross-site navigations. Also ensure
// that the SiteInstance has properly changed.
// TODO(nasko): Once we have proper cleanup of resources, add code to
// verify that the intermediate SiteInstance/RenderFrameHost have been
// properly cleaned up.
TestNavigationObserver observer(shell()->web_contents());
TestFrameNavigationObserver navigation_observer(child);
NavigationController::LoadURLParams params(cross_site_url);
params.transition_type = PageTransitionFromInt(ui::PAGE_TRANSITION_LINK);
params.frame_tree_node_id = child->frame_tree_node_id();
child->navigator()->GetController()->LoadURLWithParams(params);
SiteInstance* site2;
if (browser_side_navigation) {
site2 = child->render_manager()
->speculative_frame_host()
->GetSiteInstance();
} else {
site2 = child->render_manager()->pending_frame_host()->GetSiteInstance();
}
EXPECT_NE(shell()->web_contents()->GetSiteInstance(), site2);
EXPECT_NE(site, site2);
std::string tree = base::StringPrintf(
" Site A ------------ proxies for B C\n"
" |--Site A ------- proxies for B C\n"
" +--Site B (C %s) -- proxies for A\n"
"Where A = http://a.com/\n"
" B = http://foo.com/\n"
" C = http://bar.com/",
cross_site_rfh_type.c_str());
EXPECT_EQ(tree, DepictFrameTree(root));
navigation_observer.Wait();
EXPECT_TRUE(observer.last_navigation_succeeded());
EXPECT_EQ(cross_site_url, observer.last_navigation_url());
EXPECT_EQ(0U, child->child_count());
}
}
// Verify that "scrolling" property on frame elements propagates to child frames
// correctly.
// Does not work on android since android has scrollbars overlayed.
#if defined(OS_ANDROID)
#define MAYBE_FrameOwnerPropertiesPropagationScrolling \
DISABLED_FrameOwnerPropertiesPropagationScrolling
#else
#define MAYBE_FrameOwnerPropertiesPropagationScrolling \
FrameOwnerPropertiesPropagationScrolling
#endif
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
MAYBE_FrameOwnerPropertiesPropagationScrolling) {
#if defined(OS_MACOSX)
ui::test::ScopedPreferredScrollerStyle scroller_style_override(false);
#endif
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/frame_owner_properties_scrolling.html"));
NavigateToURL(shell(), main_url);
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
ASSERT_EQ(1u, root->child_count());
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" +--Site B ------- proxies for A\n"
"Where A = http://a.com/\n"
" B = http://b.com/",
DepictFrameTree(root));
FrameTreeNode* child = root->child_at(0);
// If the available client width within the iframe is smaller than the
// frame element's width, we assume there's a scrollbar.
// Also note that just comparing clientHeight and scrollHeight of the frame's
// document will not work.
auto has_scrollbar = [](RenderFrameHostImpl* rfh) {
int client_width;
EXPECT_TRUE(ExecuteScriptAndExtractInt(rfh,
"window.domAutomationController.send(document.body.clientWidth);",
&client_width));
const int kFrameElementWidth = 200;
return client_width < kFrameElementWidth;
};
auto set_scrolling_property = [](RenderFrameHostImpl* parent_rfh,
const std::string& value) {
EXPECT_TRUE(ExecuteScript(
parent_rfh,
base::StringPrintf(
"document.getElementById('child-1').setAttribute("
" 'scrolling', '%s');", value.c_str())));
};
// Run the test over variety of parent/child cases.
GURL urls[] = {
// Remote to remote.
embedded_test_server()->GetURL("c.com", "/tall_page.html"),
// Remote to local.
embedded_test_server()->GetURL("a.com", "/tall_page.html"),
// Local to remote.
embedded_test_server()->GetURL("b.com", "/tall_page.html")
};
const std::string scrolling_values[] = {
"yes", "auto", "no"
};
for (size_t i = 0; i < arraysize(scrolling_values); ++i) {
bool expect_scrollbar = scrolling_values[i] != "no";
set_scrolling_property(root->current_frame_host(), scrolling_values[i]);
for (size_t j = 0; j < arraysize(urls); ++j) {
NavigateFrameToURL(child, urls[j]);
EXPECT_EQ(expect_scrollbar, has_scrollbar(child->current_frame_host()));
}
}
}
// Verify that "marginwidth" and "marginheight" properties on frame elements
// propagate to child frames correctly.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
FrameOwnerPropertiesPropagationMargin) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/frame_owner_properties_margin.html"));
NavigateToURL(shell(), main_url);
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
ASSERT_EQ(1u, root->child_count());
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" +--Site B ------- proxies for A\n"
"Where A = http://a.com/\n"
" B = http://b.com/",
DepictFrameTree(root));
FrameTreeNode* child = root->child_at(0);
std::string margin_width;
EXPECT_TRUE(ExecuteScriptAndExtractString(
child,
"window.domAutomationController.send("
"document.body.getAttribute('marginwidth'));",
&margin_width));
EXPECT_EQ("10", margin_width);
std::string margin_height;
EXPECT_TRUE(ExecuteScriptAndExtractString(
child,
"window.domAutomationController.send("
"document.body.getAttribute('marginheight'));",
&margin_height));
EXPECT_EQ("50", margin_height);
// Run the test over variety of parent/child cases.
GURL urls[] = {
// Remote to remote.
embedded_test_server()->GetURL("c.com", "/title2.html"),
// Remote to local.
embedded_test_server()->GetURL("a.com", "/title1.html"),
// Local to remote.
embedded_test_server()->GetURL("b.com", "/title2.html")
};
int current_margin_width = 15;
int current_margin_height = 25;
// Before each navigation, we change the marginwidth and marginheight
// properties of the frame. We then check whether those properties are applied
// correctly after the navigation has completed.
for (size_t i = 0; i < arraysize(urls); ++i) {
// Change marginwidth and marginheight before navigating.
EXPECT_TRUE(ExecuteScript(
root,
base::StringPrintf("document.getElementById('child-1').setAttribute("
" 'marginwidth', '%d');",
current_margin_width)));
EXPECT_TRUE(ExecuteScript(
root,
base::StringPrintf("document.getElementById('child-1').setAttribute("
" 'marginheight', '%d');",
current_margin_height)));
NavigateFrameToURL(child, urls[i]);
std::string actual_margin_width;
EXPECT_TRUE(ExecuteScriptAndExtractString(
child,
"window.domAutomationController.send("
"document.body.getAttribute('marginwidth'));",
&actual_margin_width));
EXPECT_EQ(base::IntToString(current_margin_width), actual_margin_width);
std::string actual_margin_height;
EXPECT_TRUE(ExecuteScriptAndExtractString(
child,
"window.domAutomationController.send("
"document.body.getAttribute('marginheight'));",
&actual_margin_height));
EXPECT_EQ(base::IntToString(current_margin_height), actual_margin_height);
current_margin_width += 5;
current_margin_height += 10;
}
}
// Verify origin replication with an A-embed-B-embed-C-embed-A hierarchy.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, OriginReplication) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b(c(a),b), a)"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
EXPECT_EQ(
" Site A ------------ proxies for B C\n"
" |--Site B ------- proxies for A C\n" // tiptop_child
" | |--Site C -- proxies for A B\n" // middle_child
" | | +--Site A -- proxies for B C\n" // lowest_child
" | +--Site B -- proxies for A C\n"
" +--Site A ------- proxies for B C\n"
"Where A = http://a.com/\n"
" B = http://b.com/\n"
" C = http://c.com/",
DepictFrameTree(root));
std::string a_origin = embedded_test_server()->GetURL("a.com", "/").spec();
std::string b_origin = embedded_test_server()->GetURL("b.com", "/").spec();
std::string c_origin = embedded_test_server()->GetURL("c.com", "/").spec();
FrameTreeNode* tiptop_child = root->child_at(0);
FrameTreeNode* middle_child = root->child_at(0)->child_at(0);
FrameTreeNode* lowest_child = root->child_at(0)->child_at(0)->child_at(0);
// Check that b.com frame's location.ancestorOrigins contains the correct
// origin for the parent. The origin should have been replicated as part of
// the ViewMsg_New message that created the parent's RenderFrameProxy in
// b.com's process.
int ancestor_origins_length = 0;
EXPECT_TRUE(ExecuteScriptAndExtractInt(
tiptop_child,
"window.domAutomationController.send(location.ancestorOrigins.length);",
&ancestor_origins_length));
EXPECT_EQ(1, ancestor_origins_length);
std::string result;
EXPECT_TRUE(ExecuteScriptAndExtractString(
tiptop_child,
"window.domAutomationController.send(location.ancestorOrigins[0]);",
&result));
EXPECT_EQ(a_origin, result + "/");
// Check that c.com frame's location.ancestorOrigins contains the correct
// origin for its two ancestors. The topmost parent origin should be
// replicated as part of ViewMsg_New, and the middle frame (b.com's) origin
// should be replicated as part of FrameMsg_NewFrameProxy sent for b.com's
// frame in c.com's process.
EXPECT_TRUE(ExecuteScriptAndExtractInt(
middle_child,
"window.domAutomationController.send(location.ancestorOrigins.length);",
&ancestor_origins_length));
EXPECT_EQ(2, ancestor_origins_length);
EXPECT_TRUE(ExecuteScriptAndExtractString(
middle_child,
"window.domAutomationController.send(location.ancestorOrigins[0]);",
&result));
EXPECT_EQ(b_origin, result + "/");
EXPECT_TRUE(ExecuteScriptAndExtractString(
middle_child,
"window.domAutomationController.send(location.ancestorOrigins[1]);",
&result));
EXPECT_EQ(a_origin, result + "/");
// Check that the nested a.com frame's location.ancestorOrigins contains the
// correct origin for its three ancestors.
EXPECT_TRUE(ExecuteScriptAndExtractInt(
lowest_child,
"window.domAutomationController.send(location.ancestorOrigins.length);",
&ancestor_origins_length));
EXPECT_EQ(3, ancestor_origins_length);
EXPECT_TRUE(ExecuteScriptAndExtractString(
lowest_child,
"window.domAutomationController.send(location.ancestorOrigins[0]);",
&result));
EXPECT_EQ(c_origin, result + "/");
EXPECT_TRUE(ExecuteScriptAndExtractString(
lowest_child,
"window.domAutomationController.send(location.ancestorOrigins[1]);",
&result));
EXPECT_EQ(b_origin, result + "/");
EXPECT_TRUE(ExecuteScriptAndExtractString(
lowest_child,
"window.domAutomationController.send(location.ancestorOrigins[2]);",
&result));
EXPECT_EQ(a_origin, result + "/");
}
// Check that iframe sandbox flags are replicated correctly.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, SandboxFlagsReplication) {
GURL main_url(embedded_test_server()->GetURL("/sandboxed_frames.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
TestNavigationObserver observer(shell()->web_contents());
// Navigate the second (sandboxed) subframe to a cross-site page with a
// subframe.
GURL foo_url(
embedded_test_server()->GetURL("foo.com", "/frame_tree/1-1.html"));
NavigateFrameToURL(root->child_at(1), foo_url);
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
// We can't use a TestNavigationObserver to verify the URL here,
// since the frame has children that may have clobbered it in the observer.
EXPECT_EQ(foo_url, root->child_at(1)->current_url());
// Load cross-site page into subframe's subframe.
ASSERT_EQ(2U, root->child_at(1)->child_count());
GURL bar_url(embedded_test_server()->GetURL("bar.com", "/title1.html"));
NavigateFrameToURL(root->child_at(1)->child_at(0), bar_url);
EXPECT_TRUE(observer.last_navigation_succeeded());
EXPECT_EQ(bar_url, observer.last_navigation_url());
// Opening a popup in the sandboxed foo.com iframe should fail.
bool success = false;
EXPECT_TRUE(
ExecuteScriptAndExtractBool(root->child_at(1),
"window.domAutomationController.send("
"!window.open('data:text/html,dataurl'));",
&success));
EXPECT_TRUE(success);
EXPECT_EQ(1u, Shell::windows().size());
// Opening a popup in a frame whose parent is sandboxed should also fail.
// Here, bar.com frame's sandboxed parent frame is a remote frame in
// bar.com's process.
success = false;
EXPECT_TRUE(
ExecuteScriptAndExtractBool(root->child_at(1)->child_at(0),
"window.domAutomationController.send("
"!window.open('data:text/html,dataurl'));",
&success));
EXPECT_TRUE(success);
EXPECT_EQ(1u, Shell::windows().size());
// Same, but now try the case where bar.com frame's sandboxed parent is a
// local frame in bar.com's process.
success = false;
EXPECT_TRUE(
ExecuteScriptAndExtractBool(root->child_at(2)->child_at(0),
"window.domAutomationController.send("
"!window.open('data:text/html,dataurl'));",
&success));
EXPECT_TRUE(success);
EXPECT_EQ(1u, Shell::windows().size());
// Check that foo.com frame's location.ancestorOrigins contains the correct
// origin for the parent, which should be unaffected by sandboxing.
int ancestor_origins_length = 0;
EXPECT_TRUE(ExecuteScriptAndExtractInt(
root->child_at(1),
"window.domAutomationController.send(location.ancestorOrigins.length);",
&ancestor_origins_length));
EXPECT_EQ(1, ancestor_origins_length);
std::string result;
EXPECT_TRUE(ExecuteScriptAndExtractString(
root->child_at(1),
"window.domAutomationController.send(location.ancestorOrigins[0]);",
&result));
EXPECT_EQ(result + "/", main_url.GetOrigin().spec());
// Now check location.ancestorOrigins for the bar.com frame. The middle frame
// (foo.com's) origin should be unique, since that frame is sandboxed, and
// the top frame should match |main_url|.
FrameTreeNode* bottom_child = root->child_at(1)->child_at(0);
EXPECT_TRUE(ExecuteScriptAndExtractInt(
bottom_child,
"window.domAutomationController.send(location.ancestorOrigins.length);",
&ancestor_origins_length));
EXPECT_EQ(2, ancestor_origins_length);
EXPECT_TRUE(ExecuteScriptAndExtractString(
bottom_child,
"window.domAutomationController.send(location.ancestorOrigins[0]);",
&result));
EXPECT_EQ("null", result);
EXPECT_TRUE(ExecuteScriptAndExtractString(
bottom_child,
"window.domAutomationController.send(location.ancestorOrigins[1]);",
&result));
EXPECT_EQ(main_url.GetOrigin().spec(), result + "/");
}
// Check that dynamic updates to iframe sandbox flags are propagated correctly.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, DynamicSandboxFlags) {
GURL main_url(
embedded_test_server()->GetURL("/frame_tree/page_with_two_frames.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
TestNavigationObserver observer(shell()->web_contents());
ASSERT_EQ(2U, root->child_count());
// Make sure first frame starts out at the correct cross-site page.
EXPECT_EQ(embedded_test_server()->GetURL("bar.com", "/title1.html"),
root->child_at(0)->current_url());
// Navigate second frame to another cross-site page.
GURL baz_url(embedded_test_server()->GetURL("baz.com", "/title1.html"));
NavigateFrameToURL(root->child_at(1), baz_url);
EXPECT_TRUE(observer.last_navigation_succeeded());
EXPECT_EQ(baz_url, observer.last_navigation_url());
// Both frames should not be sandboxed to start with.
EXPECT_EQ(blink::WebSandboxFlags::None,
root->child_at(0)->pending_sandbox_flags());
EXPECT_EQ(blink::WebSandboxFlags::None,
root->child_at(0)->effective_sandbox_flags());
EXPECT_EQ(blink::WebSandboxFlags::None,
root->child_at(1)->pending_sandbox_flags());
EXPECT_EQ(blink::WebSandboxFlags::None,
root->child_at(1)->effective_sandbox_flags());
// Dynamically update sandbox flags for the first frame.
EXPECT_TRUE(ExecuteScript(
shell(),
"document.querySelector('iframe').sandbox='allow-scripts';"));
// Check that updated sandbox flags are propagated to browser process.
// The new flags should be reflected in pending_sandbox_flags(), while
// effective_sandbox_flags() should still reflect the old flags, because
// sandbox flag updates take place only after navigations. "allow-scripts"
// resets both SandboxFlags::Scripts and SandboxFlags::AutomaticFeatures bits
// per blink::parseSandboxPolicy().
blink::WebSandboxFlags expected_flags =
blink::WebSandboxFlags::All & ~blink::WebSandboxFlags::Scripts &
~blink::WebSandboxFlags::AutomaticFeatures;
EXPECT_EQ(expected_flags, root->child_at(0)->pending_sandbox_flags());
EXPECT_EQ(blink::WebSandboxFlags::None,
root->child_at(0)->effective_sandbox_flags());