| // 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()); |
| |
| |