blob: 645960a3262158cff85c710b2ac56f93ecfd7c1c [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/site_per_process_browsertest.h"
#include <stddef.h>
#include <stdint.h>
#include <algorithm>
#include <list>
#include <map>
#include <memory>
#include <set>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/json/json_reader.h"
#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/sequenced_task_runner.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/pattern.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind_test_util.h"
#include "base/test/test_timeouts.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "cc/input/touch_action.h"
#include "components/network_session_configurator/common/network_switches.h"
#include "content/browser/frame_host/cross_process_frame_connector.h"
#include "content/browser/frame_host/frame_navigation_entry.h"
#include "content/browser/frame_host/frame_tree.h"
#include "content/browser/frame_host/interstitial_page_impl.h"
#include "content/browser/frame_host/navigation_controller_impl.h"
#include "content/browser/frame_host/navigation_entry_impl.h"
#include "content/browser/frame_host/navigator.h"
#include "content/browser/frame_host/render_frame_host_impl.h"
#include "content/browser/frame_host/render_frame_proxy_host.h"
#include "content/browser/gpu/compositor_util.h"
#include "content/browser/loader/resource_dispatcher_host_impl.h"
#include "content/browser/renderer_host/cursor_manager.h"
#include "content/browser/renderer_host/input/input_router.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_child_frame.h"
#include "content/browser/storage_partition_impl.h"
#include "content/browser/url_loader_factory_getter.h"
#include "content/browser/web_contents/web_contents_impl.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/renderer.mojom.h"
#include "content/common/view_messages.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/interstitial_page_delegate.h"
#include "content/public/browser/navigation_handle.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_features.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/navigation_handle_observer.h"
#include "content/public/test/test_frame_navigation_observer.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_utils.h"
#include "content/public/test/url_loader_interceptor.h"
#include "content/shell/browser/shell.h"
#include "content/shell/common/shell_switches.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "content/test/did_commit_provisional_load_interceptor.h"
#include "content/test/mock_overscroll_observer.h"
#include "ipc/constants.mojom.h"
#include "ipc/ipc_security_test_util.h"
#include "media/base/media_switches.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/WebKit/common/feature_policy/feature_policy.h"
#include "third_party/WebKit/common/sandbox_flags.h"
#include "third_party/WebKit/public/platform/WebInputEvent.h"
#include "third_party/WebKit/public/platform/WebInsecureRequestPolicy.h"
#include "ui/display/display_switches.h"
#include "ui/display/screen.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/event.h"
#include "ui/events/event_utils.h"
#include "ui/events/gesture_detection/gesture_configuration.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/latency/latency_info.h"
#include "ui/native_theme/native_theme_features.h"
#if defined(USE_AURA)
#include "content/browser/renderer_host/overscroll_controller.h"
#include "content/browser/renderer_host/render_widget_host_view_aura.h"
#include "content/public/browser/overscroll_configuration.h"
#include "content/test/mock_overscroll_controller_delegate_aura.h"
#endif
#if defined(OS_MACOSX)
#include "ui/base/test/scoped_preferred_scroller_style_mac.h"
#endif
#if defined(OS_ANDROID)
#include "base/android/jni_android.h"
#include "base/android/jni_string.h"
#include "base/android/scoped_java_ref.h"
#include "base/json/json_reader.h"
#include "content/browser/android/ime_adapter_android.h"
#include "content/browser/renderer_host/input/touch_selection_controller_client_manager_android.h"
#include "content/browser/renderer_host/render_widget_host_view_android.h"
#include "content/public/browser/android/child_process_importance.h"
#include "content/test/mock_overscroll_refresh_handler_android.h"
#include "ui/events/android/motion_event_android.h"
#include "ui/gfx/geometry/point_f.h"
#endif
using ::testing::SizeIs;
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(blink::WebInputEvent::kMouseDown,
blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::kTimeStampForTesting);
mouse_event.button = blink::WebPointerProperties::Button::kLeft;
mouse_event.SetPositionInWidget(x, 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) {
mouse_callback_ =
base::Bind(&RenderWidgetHostMouseEventMonitor::MouseEventCallback,
base::Unretained(this));
host_->AddMouseEventCallback(mouse_callback_);
}
~RenderWidgetHostMouseEventMonitor() {
host_->RemoveMouseEventCallback(mouse_callback_);
}
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::MouseEventCallback mouse_callback_;
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_;
}
const blink::WebInputEvent& event() const { return *event_; }
void OnInputEvent(const blink::WebInputEvent& event) override {
events_received_.push_back(event.GetType());
event_ = ui::WebInputEventTraits::Clone(event);
};
private:
RenderWidgetHost* host_;
std::vector<blink::WebInputEvent::Type> events_received_;
ui::WebScopedInputEvent event_;
DISALLOW_COPY_AND_ASSIGN(TestInputEventObserver);
};
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;
}
// This helper accounts for Android devices which use page scale factor
// different from 1.0. Coordinate targeting needs to be adjusted before
// hit testing.
double GetPageScaleFactor(Shell* shell) {
return RenderWidgetHostImpl::From(
shell->web_contents()->GetRenderViewHost()->GetWidget())
->last_frame_metadata()
.page_scale_factor;
}
void RouteMouseEventAndWaitUntilDispatch(
RenderWidgetHostInputEventRouter* router,
RenderWidgetHostViewBase* root_view,
RenderWidgetHostViewBase* expected_target,
blink::WebMouseEvent* event) {
InputEventAckWaiter waiter(expected_target->GetRenderWidgetHost(),
event->GetType());
router->RouteMouseEvent(root_view, event, ui::LatencyInfo());
waiter.Wait();
}
void DispatchMouseEventAndWaitUntilDispatch(
WebContentsImpl* web_contents,
RenderWidgetHostViewBase* location_view,
const gfx::PointF& location,
RenderWidgetHostViewBase* expected_target,
const gfx::PointF& expected_location) {
auto* router = web_contents->GetInputEventRouter();
RenderWidgetHostMouseEventMonitor monitor(
expected_target->GetRenderWidgetHost());
gfx::PointF root_location =
location_view->TransformPointToRootCoordSpaceF(location);
blink::WebMouseEvent down_event(blink::WebInputEvent::kMouseDown,
blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::kTimeStampForTesting);
down_event.button = blink::WebPointerProperties::Button::kLeft;
down_event.SetPositionInWidget(root_location.x(), root_location.y());
down_event.click_count = 1;
FrameTreeNode* root = web_contents->GetFrameTree()->root();
auto* root_view = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RouteMouseEventAndWaitUntilDispatch(router, root_view, expected_target,
&down_event);
EXPECT_TRUE(monitor.EventWasReceived());
EXPECT_NEAR(expected_location.x(), monitor.event().PositionInWidget().x, 2);
EXPECT_NEAR(expected_location.y(), monitor.event().PositionInWidget().y, 2);
}
// 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"));
EXPECT_TRUE(NavigateToURL(shell, main_url));
auto* web_contents = static_cast<WebContentsImpl*>(shell->web_contents());
// 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());
RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForChildFrameSurfaceReady(child_node->current_frame_host());
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_child,
gfx::PointF(5, 5), rwhv_child,
gfx::PointF(5, 5));
DispatchMouseEventAndWaitUntilDispatch(
web_contents, rwhv_root, gfx::PointF(2, 2), rwhv_root, gfx::PointF(2, 2));
}
void OverlapSurfaceHitTestHelper(
Shell* shell,
net::test_server::EmbeddedTestServer* embedded_test_server) {
GURL main_url(embedded_test_server->GetURL(
"/frame_tree/page_with_content_overlap_positioned_frame.html"));
EXPECT_TRUE(NavigateToURL(shell, main_url));
auto* web_contents = static_cast<WebContentsImpl*>(shell->web_contents());
// 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());
RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForChildFrameSurfaceReady(child_node->current_frame_host());
gfx::PointF parent_location = gfx::PointF(5, 5);
parent_location =
rwhv_child->TransformPointToRootCoordSpaceF(parent_location);
DispatchMouseEventAndWaitUntilDispatch(
web_contents, rwhv_child, gfx::PointF(5, 5), rwhv_root, parent_location);
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_child,
gfx::PointF(95, 95), rwhv_child,
gfx::PointF(95, 95));
}
// Helper function that performs a surface hittest in nested frame.
void NestedSurfaceHitTestTestHelper(
Shell* shell,
net::test_server::EmbeddedTestServer* embedded_test_server) {
auto* web_contents = static_cast<WebContentsImpl*>(shell->web_contents());
GURL main_url(embedded_test_server->GetURL(
"/frame_tree/page_with_positioned_nested_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();
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());
RenderWidgetHostViewBase* rwhv_nested =
static_cast<RenderWidgetHostViewBase*>(
nested_iframe_node->current_frame_host()
->GetRenderWidgetHost()
->GetView());
WaitForChildFrameSurfaceReady(nested_iframe_node->current_frame_host());
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_nested,
gfx::PointF(10, 10), rwhv_nested,
gfx::PointF(10, 10));
}
void HitTestLayerSquashing(
Shell* shell,
net::test_server::EmbeddedTestServer* embedded_test_server) {
GURL main_url(embedded_test_server->GetURL(
"/frame_tree/oopif_hit_test_layer_squashing.html"));
EXPECT_TRUE(NavigateToURL(shell, main_url));
auto* web_contents = static_cast<WebContentsImpl*>(shell->web_contents());
// 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());
RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForChildFrameSurfaceReady(child_node->current_frame_host());
gfx::Vector2dF child_offset = rwhv_child->GetViewBounds().origin() -
rwhv_root->GetViewBounds().origin();
// Send a mouse-down on #B. The main-frame should receive it.
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_root,
gfx::PointF(195, 11), rwhv_root,
gfx::PointF(195, 11));
// Send another event just below. The child-frame should receive it.
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_root,
gfx::PointF(195, 30), rwhv_child,
gfx::PointF(195, 30) - child_offset);
// Send a mouse-down on #C.
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_root,
gfx::PointF(35, 195), rwhv_root,
gfx::PointF(35, 195));
// Send a mouse-down to the right of #C so that it goes to the child frame.
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_root,
gfx::PointF(55, 195), rwhv_child,
gfx::PointF(55, 195) - child_offset);
// Send a mouse-down to the right-bottom edge of the iframe.
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_root,
gfx::PointF(195, 235), rwhv_child,
gfx::PointF(195, 235) - child_offset);
}
void HitTestWatermark(
Shell* shell,
net::test_server::EmbeddedTestServer* embedded_test_server) {
GURL main_url(embedded_test_server->GetURL(
"/frame_tree/oopif_hit_test_watermark.html"));
EXPECT_TRUE(NavigateToURL(shell, main_url));
auto* web_contents = static_cast<WebContentsImpl*>(shell->web_contents());
// 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());
RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForChildFrameSurfaceReady(child_node->current_frame_host());
gfx::Vector2dF child_offset = rwhv_child->GetViewBounds().origin() -
rwhv_root->GetViewBounds().origin();
const gfx::PointF child_location(100, 120);
// Send a mouse-down at the center of the iframe. This should go to the
// main-frame (since there's a translucent div on top of it).
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_child,
child_location, rwhv_root,
child_location + child_offset);
// Set 'pointer-events: none' on the div.
EXPECT_TRUE(ExecuteScript(web_contents, "W.style.pointerEvents = 'none';"));
// Dispatch another event at the same location. It should reach the oopif this
// time.
DispatchMouseEventAndWaitUntilDispatch(
web_contents, rwhv_child, child_location, rwhv_child, child_location);
}
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 run 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:
explicit 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>"; }
};
#if defined(USE_AURA) || defined(OS_ANDROID)
bool ConvertJSONToPoint(const std::string& str, gfx::PointF* point) {
std::unique_ptr<base::Value> value = base::JSONReader::Read(str);
if (!value)
return false;
base::DictionaryValue* root;
if (!value->GetAsDictionary(&root))
return false;
double x, y;
if (!root->GetDouble("x", &x))
return false;
if (!root->GetDouble("y", &y))
return false;
point->set_x(x);
point->set_y(y);
return true;
}
#endif // defined(USE_AURA) || defined (OS_ANDROID)
} // namespace
//
// SitePerProcessBrowserTest
//
SitePerProcessBrowserTest::SitePerProcessBrowserTest() {}
std::string SitePerProcessBrowserTest::DepictFrameTree(FrameTreeNode* node) {
return visualizer_.DepictFrameTree(node);
}
void SitePerProcessBrowserTest::SetUpCommandLine(
base::CommandLine* command_line) {
IsolateAllSitesForTesting(command_line);
#if !defined(OS_ANDROID)
// TODO(bokan): Needed for scrollability check in
// FrameOwnerPropertiesPropagationScrolling. crbug.com/662196.
feature_list_.InitAndDisableFeature(features::kOverlayScrollbar);
#endif
}
void SitePerProcessBrowserTest::SetUpOnMainThread() {
host_resolver()->AddRule("*", "127.0.0.1");
SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
}
//
// 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));
}
};
//
// SitePerProcessNonIntegerScaleFactorBrowserTest
//
class SitePerProcessNonIntegerScaleFactorBrowserTest
: public SitePerProcessBrowserTest {
public:
const double kDeviceScaleFactor = 1.5;
SitePerProcessNonIntegerScaleFactorBrowserTest() {}
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);
}
};
// SitePerProcessAutoplayBrowserTest
class SitePerProcessAutoplayBrowserTest : public SitePerProcessBrowserTest {
public:
SitePerProcessAutoplayBrowserTest() = default;
void SetUpCommandLine(base::CommandLine* command_line) override {
SitePerProcessBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitchASCII(
switches::kAutoplayPolicy,
switches::autoplay::kDocumentUserActivationRequiredPolicy);
command_line->AppendSwitchASCII("enable-blink-features",
"FeaturePolicyAutoplayFeature");
}
bool AutoplayAllowed(const ToRenderFrameHost& adapter,
bool with_user_gesture) {
RenderFrameHost* rfh = adapter.render_frame_host();
const char* test_script = "attemptPlay();";
bool worked = false;
if (with_user_gesture) {
EXPECT_TRUE(ExecuteScriptAndExtractBool(rfh, test_script, &worked));
} else {
EXPECT_TRUE(ExecuteScriptWithoutUserGestureAndExtractBool(
rfh, test_script, &worked));
}
return worked;
}
void NavigateFrameAndWait(FrameTreeNode* node, const GURL& url) {
NavigateFrameToURL(node, url);
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(url, node->current_url());
}
};
// SitePerProcessEmbedderCSPEnforcementBrowserTest
class SitePerProcessEmbedderCSPEnforcementBrowserTest
: public SitePerProcessBrowserTest {
public:
SitePerProcessEmbedderCSPEnforcementBrowserTest() {}
protected:
void SetUpCommandLine(base::CommandLine* command_line) override {
SitePerProcessBrowserTest::SetUpCommandLine(command_line);
// TODO(amalika): Remove this switch when the EmbedderCSPEnforcement becomes
// stable
command_line->AppendSwitchASCII(switches::kEnableBlinkFeatures,
"EmbedderCSPEnforcement");
}
};
// SitePerProcessFeaturePolicyBrowserTest
class SitePerProcessFeaturePolicyBrowserTest
: public SitePerProcessBrowserTest {
public:
SitePerProcessFeaturePolicyBrowserTest() {}
protected:
void SetUpCommandLine(base::CommandLine* command_line) override {
SitePerProcessBrowserTest::SetUpCommandLine(command_line);
// TODO(iclelland): Remove this switch when Feature Policy ships.
// https://crbug.com/623682
command_line->AppendSwitchASCII(
switches::kEnableBlinkFeatures,
"FeaturePolicy,FeaturePolicyExperimentalFeatures");
}
blink::ParsedFeaturePolicy CreateFPHeader(blink::FeaturePolicyFeature feature,
const std::vector<GURL>& origins) {
blink::ParsedFeaturePolicy result(1);
result[0].feature = feature;
result[0].matches_all_origins = false;
DCHECK(!origins.empty());
for (const GURL& origin : origins)
result[0].origins.push_back(url::Origin::Create(origin));
return result;
}
blink::ParsedFeaturePolicy CreateFPHeaderMatchesAll(
blink::FeaturePolicyFeature feature) {
blink::ParsedFeaturePolicy result(1);
result[0].feature = feature;
result[0].matches_all_origins = true;
return result;
}
};
// SitePerProcessFeaturePolicyDisabledBrowserTest
class SitePerProcessFeaturePolicyDisabledBrowserTest
: public SitePerProcessBrowserTest {
public:
SitePerProcessFeaturePolicyDisabledBrowserTest() {}
protected:
void SetUpCommandLine(base::CommandLine* command_line) override {
SitePerProcessBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitchASCII(switches::kDisableBlinkFeatures,
"FeaturePolicy");
}
};
IN_PROC_BROWSER_TEST_F(SitePerProcessHighDPIBrowserTest,
SubframeLoadsWithCorrectDeviceScaleFactor) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// On Android forcing device scale factor does not work for tests, therefore
// we ensure that make frame and iframe have the same DIP scale there, but
// not necessarily kDeviceScaleFactor.
const double expected_dip_scale =
#if defined(OS_ANDROID)
GetFrameDeviceScaleFactor(web_contents());
#else
SitePerProcessHighDPIBrowserTest::kDeviceScaleFactor;
#endif
EXPECT_EQ(expected_dip_scale, GetFrameDeviceScaleFactor(web_contents()));
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child = root->child_at(0);
EXPECT_EQ(expected_dip_scale, GetFrameDeviceScaleFactor(child));
}
// Ensure that navigating subframes in --site-per-process mode works and the
// correct documents are committed.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, CrossSiteIframe) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(a,a(a,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();
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");
{
RenderFrameDeletedObserver deleted_observer(child->current_frame_host());
NavigateFrameToURL(root->child_at(0), url);
deleted_observer.WaitUntilDeleted();
}
// 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()->GetMainFrame()->GetProcess(), 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");
{
RenderFrameDeletedObserver deleted_observer(child->current_frame_host());
NavigateFrameToURL(root->child_at(0), url);
deleted_observer.WaitUntilDeleted();
}
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()->GetMainFrame()->GetProcess(),
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());
}
// Test that the physical backing size and view bounds for a scaled out-of-
// process iframe are set and updated correctly.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, PhysicalBackingSizeTest) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/frame_tree/page_with_scaled_frame.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* parent_iframe_node = root->child_at(0);
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);
RenderFrameProxyHost* proxy_to_parent =
nested_iframe_node->render_manager()->GetProxyToParent();
CrossProcessFrameConnector* connector =
proxy_to_parent->cross_process_frame_connector();
RenderWidgetHostViewBase* rwhv_nested =
static_cast<RenderWidgetHostViewBase*>(
nested_iframe_node->current_frame_host()
->GetRenderWidgetHost()
->GetView());
WaitForChildFrameSurfaceReady(nested_iframe_node->current_frame_host());
// Verify that applying a CSS scale transform does not impact the size of the
// content of the nested iframe.
EXPECT_EQ(gfx::Size(50, 50), connector->screen_space_rect_in_dip().size());
EXPECT_EQ(gfx::Size(100, 100), rwhv_nested->GetViewBounds().size());
EXPECT_EQ(gfx::Size(100, 100), connector->local_frame_size_in_dip());
EXPECT_EQ(connector->local_frame_size_in_pixels(),
rwhv_nested->GetPhysicalBackingSize());
}
// 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.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, ViewBoundsInNestedFrameTest) {
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 = 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());
WaitForChildFrameSurfaceReady(nested_iframe_node->current_frame_host());
float scale_factor = GetPageScaleFactor(shell());
// Get 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();
scoped_refptr<UpdateResizeParamsMessageFilter> filter =
new UpdateResizeParamsMessageFilter();
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(
blink::WebInputEvent::kMouseWheel, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::kTimeStampForTesting);
scroll_event.SetPositionInWidget(
gfx::ToFlooredInt((bounds.x() - rwhv_root->GetViewBounds().x() - 5) *
scale_factor),
gfx::ToFlooredInt((bounds.y() - rwhv_root->GetViewBounds().y() - 5) *
scale_factor));
scroll_event.delta_x = 0.0f;
scroll_event.delta_y = -30.0f;
scroll_event.phase = blink::WebMouseWheelEvent::kPhaseBegan;
rwhv_root->ProcessMouseWheelEvent(scroll_event, ui::LatencyInfo());
filter->WaitForRect();
// 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());
}
// This test verifies that scroll bubbling from an OOPIF properly forwards
// GestureFlingStart events from the child frame to the parent frame. This
// test times out on failure.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
GestureFlingStartEventsBubble) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
ASSERT_EQ(1U, root->child_count());
RenderWidgetHost* root_rwh =
root->current_frame_host()->GetRenderWidgetHost();
FrameTreeNode* child_iframe_node = root->child_at(0);
RenderWidgetHost* child_rwh =
child_iframe_node->current_frame_host()->GetRenderWidgetHost();
RenderWidgetHostViewBase* child_rwhv =
static_cast<RenderWidgetHostViewBase*>(child_rwh->GetView());
// If wheel scroll latching is enabled, the fling start won't bubble since
// its corresponding GSB hasn't bubbled.
InputEventAckWaiter gesture_fling_start_ack_observer(
(child_rwhv->wheel_scroll_latching_enabled() ? child_rwh : root_rwh),
blink::WebInputEvent::kGestureFlingStart);
WaitForChildFrameSurfaceReady(child_iframe_node->current_frame_host());
gesture_fling_start_ack_observer.Reset();
// Send a GSB, GSU, GFS sequence and verify that the GFS bubbles.
blink::WebGestureEvent gesture_scroll_begin(
blink::WebGestureEvent::kGestureScrollBegin,
blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::kTimeStampForTesting);
gesture_scroll_begin.source_device = blink::kWebGestureDeviceTouchscreen;
gesture_scroll_begin.data.scroll_begin.delta_hint_units =
blink::WebGestureEvent::ScrollUnits::kPrecisePixels;
gesture_scroll_begin.data.scroll_begin.delta_x_hint = 0.f;
gesture_scroll_begin.data.scroll_begin.delta_y_hint = 5.f;
child_rwh->ForwardGestureEvent(gesture_scroll_begin);
blink::WebGestureEvent gesture_scroll_update(
blink::WebGestureEvent::kGestureScrollUpdate,
blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::kTimeStampForTesting);
gesture_scroll_update.source_device = blink::kWebGestureDeviceTouchscreen;
gesture_scroll_update.data.scroll_update.delta_units =
blink::WebGestureEvent::ScrollUnits::kPrecisePixels;
gesture_scroll_update.data.scroll_update.delta_x = 0.f;
gesture_scroll_update.data.scroll_update.delta_y = 5.f;
gesture_scroll_update.data.scroll_update.velocity_y = 5.f;
child_rwh->ForwardGestureEvent(gesture_scroll_update);
blink::WebGestureEvent gesture_fling_start(
blink::WebGestureEvent::kGestureFlingStart,
blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::kTimeStampForTesting);
gesture_fling_start.source_device = blink::kWebGestureDeviceTouchscreen;
gesture_fling_start.data.fling_start.velocity_x = 0.f;
gesture_fling_start.data.fling_start.velocity_y = 5.f;
child_rwh->ForwardGestureEvent(gesture_fling_start);
// We now wait for the fling start event to be acked by the parent
// frame. If the test fails, then the test times out.
gesture_fling_start_ack_observer.Wait();
}
// Restrict to Aura to we can use routable MouseWheel event via
// RenderWidgetHostViewAura::OnScrollEvent().
#if defined(USE_AURA)
class SitePerProcessInternalsBrowserTest : public SitePerProcessBrowserTest {
public:
SitePerProcessInternalsBrowserTest() {}
protected:
void SetUpCommandLine(base::CommandLine* command_line) override {
SitePerProcessBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitch(switches::kExposeInternalsForTesting);
// Needed to guarantee the scrollable div we're testing with is not given
// its own compositing layer.
command_line->AppendSwitch(switches::kDisablePreferCompositingToLCDText);
}
};
IN_PROC_BROWSER_TEST_F(SitePerProcessInternalsBrowserTest,
ScrollNestedLocalNonFastScrollableDiv) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
EXPECT_TRUE(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);
GURL site_url(embedded_test_server()->GetURL(
"b.com", "/tall_page_with_local_iframe.html"));
NavigateFrameToURL(parent_iframe_node, site_url);
FrameTreeNode* nested_iframe_node = parent_iframe_node->child_at(0);
WaitForChildFrameSurfaceReady(nested_iframe_node->current_frame_host());
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://b.com/",
DepictFrameTree(root));
const char* get_element_location_script_fmt =
"var rect = "
"document.getElementById('%s').getBoundingClientRect();\n"
"var point = {\n"
" x: rect.left,\n"
" y: rect.top\n"
"};\n"
"window.domAutomationController.send(JSON.stringify(point));";
// Since the nested local b-frame shares the RenderWidgetHostViewChildFrame
// with the parent frame, we need to query element offsets in both documents
// before converting to root space coordinates for the wheel event.
std::string str;
EXPECT_TRUE(ExecuteScriptAndExtractString(
nested_iframe_node->current_frame_host(),
base::StringPrintf(get_element_location_script_fmt, "scrollable_div"),
&str));
gfx::PointF nested_point_f;
ConvertJSONToPoint(str, &nested_point_f);
EXPECT_TRUE(ExecuteScriptAndExtractString(
parent_iframe_node->current_frame_host(),
base::StringPrintf(get_element_location_script_fmt, "nested_frame"),
&str));
gfx::PointF parent_offset_f;
ConvertJSONToPoint(str, &parent_offset_f);
// Compute location for wheel event.
gfx::PointF point_f(parent_offset_f.x() + nested_point_f.x() + 5.f,
parent_offset_f.y() + nested_point_f.y() + 5.f);
RenderWidgetHostViewChildFrame* rwhv_nested =
static_cast<RenderWidgetHostViewChildFrame*>(
nested_iframe_node->current_frame_host()
->GetRenderWidgetHost()
->GetView());
point_f = rwhv_nested->TransformPointToRootCoordSpaceF(point_f);
RenderWidgetHostViewAura* rwhv_root = static_cast<RenderWidgetHostViewAura*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
gfx::PointF nested_in_parent;
rwhv_root->TransformPointToCoordSpaceForView(
point_f,
parent_iframe_node->current_frame_host()
->GetRenderWidgetHost()
->GetView(),
&nested_in_parent);
// Get original scroll position.
int div_scroll_top_start;
EXPECT_TRUE(ExecuteScriptAndExtractInt(
nested_iframe_node->current_frame_host(),
"window.domAutomationController.send("
"document.getElementById('scrollable_div').scrollTop);",
&div_scroll_top_start));
EXPECT_EQ(0, div_scroll_top_start);
// Wait until renderer's compositor thread is synced. Otherwise the event
// handler won't be installed when the event arrives.
MainThreadFrameObserver observer(rwhv_root->GetRenderWidgetHost());
observer.Wait();
{
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(),
// tiny_timeout() is too small to run without flakes, but
// action_timeout() is 100 times bigger, which is overkill. We use a
// custom delay here to achieve a balance.
base::TimeDelta::FromMilliseconds(1000));
run_loop.Run();
}
// Send a wheel to scroll the div.
gfx::Point location(point_f.x(), point_f.y());
ui::ScrollEvent scroll_event(ui::ET_SCROLL, location, ui::EventTimeForNow(),
0, 0, -ui::MouseWheelEvent::kWheelDelta, 0,
ui::MouseWheelEvent::kWheelDelta,
2); // This must be '2' or it gets silently
// dropped.
rwhv_root->OnScrollEvent(&scroll_event);
InputEventAckWaiter ack_observer(
parent_iframe_node->current_frame_host()->GetRenderWidgetHost(),
blink::WebInputEvent::kGestureScrollUpdate);
ack_observer.Wait();
// Check compositor layers.
EXPECT_TRUE(ExecuteScriptAndExtractString(
nested_iframe_node->current_frame_host(),
"window.domAutomationController.send("
"window.internals.layerTreeAsText(document));",
&str));
// We expect the nested OOPIF to not have any compositor layers.
EXPECT_EQ(std::string(), str);
// Verify the div scrolled.
int div_scroll_top = div_scroll_top_start;
EXPECT_TRUE(ExecuteScriptAndExtractInt(
nested_iframe_node->current_frame_host(),
"window.domAutomationController.send("
"document.getElementById('scrollable_div').scrollTop);",
&div_scroll_top));
EXPECT_NE(div_scroll_top_start, div_scroll_top);
}
#endif // defined(USE_AURA)
// Test that scrolling a nested out-of-process iframe bubbles unused scroll
// delta to a parent frame.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, ScrollBubblingFromOOPIFTest) {
ui::GestureConfiguration::GetInstance()->set_scroll_debounce_interval_in_ms(
0);
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
EXPECT_TRUE(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. UpdateResizeParamsMessageFilter
// 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<UpdateResizeParamsMessageFilter> filter =
new UpdateResizeParamsMessageFilter();
parent_iframe_node->current_frame_host()->GetProcess()->AddFilter(
filter.get());
InputEventAckWaiter ack_observer(
parent_iframe_node->current_frame_host()->GetRenderWidgetHost(),
blink::WebInputEvent::kGestureScrollEnd);
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());
WaitForChildFrameSurfaceReady(nested_iframe_node->current_frame_host());
// Save the original offset as a point of reference.
filter->WaitForRect();
gfx::Rect update_rect = filter->last_rect();
int initial_y = update_rect.y();
filter->ResetRectRunLoop();
// Scroll the parent frame downward.
blink::WebMouseWheelEvent scroll_event(
blink::WebInputEvent::kMouseWheel, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::kTimeStampForTesting);
scroll_event.SetPositionInWidget(1, 1);
scroll_event.delta_x = 0.0f;
scroll_event.delta_y = -5.0f;
scroll_event.phase = blink::WebMouseWheelEvent::kPhaseBegan;
// Set has_precise_scroll_deltas to keep these events off the animated scroll
// pathways, which currently break this test.
// https://bugs.chromium.org/p/chromium/issues/detail?id=710513
scroll_event.has_precise_scrolling_deltas = true;
rwhv_parent->ProcessMouseWheelEvent(scroll_event, ui::LatencyInfo());
if (rwhv_parent->wheel_scroll_latching_enabled()) {
// When scroll latching is enabled the event router sends wheel events of a
// single scroll sequence to the target under the first wheel event. Send a
// wheel end event to the current target view before sending a wheel event
// to a different one.
scroll_event.delta_y = 0.0f;
scroll_event.phase = blink::WebMouseWheelEvent::kPhaseEnded;
scroll_event.dispatch_type =
blink::WebInputEvent::DispatchType::kEventNonBlocking;
rwhv_parent->ProcessMouseWheelEvent(scroll_event, ui::LatencyInfo());
}
// Ensure that the view position is propagated to the child properly.
filter->WaitForRect();
update_rect = filter->last_rect();
EXPECT_LT(update_rect.y(), initial_y);
filter->ResetRectRunLoop();
ack_observer.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.delta_y = 6.0f;
scroll_event.dispatch_type = blink::WebInputEvent::DispatchType::kBlocking;
scroll_event.phase = blink::WebMouseWheelEvent::kPhaseBegan;
rwhv_nested->ProcessMouseWheelEvent(scroll_event, ui::LatencyInfo());
filter->WaitForRect();
// 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();
}
if (rwhv_parent->wheel_scroll_latching_enabled()) {
// When scroll latching is enabled the event router sends wheel events of a
// single scroll sequence to the target under the first wheel event. Send a
// wheel end event to the current target view before sending a wheel event
// to a different one.
scroll_event.delta_y = 0.0f;
scroll_event.phase = blink::WebMouseWheelEvent::kPhaseEnded;
scroll_event.dispatch_type =
blink::WebInputEvent::DispatchType::kEventNonBlocking;
rwhv_nested->ProcessMouseWheelEvent(scroll_event, ui::LatencyInfo());
}
filter->ResetRectRunLoop();
// Once we've sent a wheel to the nested iframe that we expect to turn into
// a bubbling scroll, we need to delay to make sure the GestureScrollBegin
// from this new scroll doesn't hit the RenderWidgetHostImpl before the
// GestureScrollEnd bubbled from the child.
// This timing only seems to be needed for CrOS, but we'll enable it on
// all platforms just to lessen the possibility of tests being flakey
// on non-CrOS platforms.
ack_observer.Wait();
// Scroll the parent down again in order to test scroll bubbling from
// gestures.
scroll_event.delta_y = -5.0f;
scroll_event.dispatch_type = blink::WebInputEvent::DispatchType::kBlocking;
scroll_event.phase = blink::WebMouseWheelEvent::kPhaseBegan;
rwhv_parent->ProcessMouseWheelEvent(scroll_event, ui::LatencyInfo());
if (rwhv_parent->wheel_scroll_latching_enabled()) {
// When scroll latching is enabled the event router sends wheel events of a
// single scroll sequence to the target under the first wheel event. Send a
// wheel end event to the current target view before sending a wheel event
// to a different one.
scroll_event.delta_y = 0.0f;
scroll_event.phase = blink::WebMouseWheelEvent::kPhaseEnded;
scroll_event.dispatch_type =
blink::WebInputEvent::DispatchType::kEventNonBlocking;
rwhv_parent->ProcessMouseWheelEvent(scroll_event, ui::LatencyInfo());
}
// Ensure ensuing offset change is received, and then reset the filter.
filter->WaitForRect();
filter->ResetRectRunLoop();
// Scroll down the nested iframe via gesture. This requires 3 separate input
// events.
blink::WebGestureEvent gesture_event(
blink::WebGestureEvent::kGestureScrollBegin,
blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::kTimeStampForTesting);
gesture_event.source_device = blink::kWebGestureDeviceTouchpad;
gesture_event.x = 1;
gesture_event.y = 1;
gesture_event.data.scroll_begin.delta_x_hint = 0.0f;
gesture_event.data.scroll_begin.delta_y_hint = 6.0f;
rwhv_nested->GetRenderWidgetHost()->ForwardGestureEvent(gesture_event);
gesture_event =
blink::WebGestureEvent(blink::WebGestureEvent::kGestureScrollUpdate,
blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::kTimeStampForTesting);
gesture_event.source_device = blink::kWebGestureDeviceTouchpad;
gesture_event.x = 1;
gesture_event.y = 1;
gesture_event.data.scroll_update.delta_x = 0.0f;
gesture_event.data.scroll_update.delta_y = 6.0f;
gesture_event.data.scroll_update.velocity_x = 0;
gesture_event.data.scroll_update.velocity_y = 0;
rwhv_nested->GetRenderWidgetHost()->ForwardGestureEvent(gesture_event);
gesture_event =
blink::WebGestureEvent(blink::WebGestureEvent::kGestureScrollEnd,
blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::kTimeStampForTesting);
gesture_event.source_device = blink::kWebGestureDeviceTouchpad;
gesture_event.x = 1;
gesture_event.y = 1;
rwhv_nested->GetRenderWidgetHost()->ForwardGestureEvent(gesture_event);
filter->WaitForRect();
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->ResetRectRunLoop();
scroll_event.delta_y = -5.0f;
scroll_event.dispatch_type = blink::WebInputEvent::DispatchType::kBlocking;
scroll_event.phase = blink::WebMouseWheelEvent::kPhaseBegan;
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);
}
// Tests that wheel scroll bubbling gets cancelled when the wheel target view
// gets destroyed in the middle of a wheel scroll seqeunce. This happens in
// cases like overscroll navigation from inside an oopif.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
CancelWheelScrollBubblingOnWheelTargetDeletion) {
ui::GestureConfiguration::GetInstance()->set_scroll_debounce_interval_in_ms(
0);
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_positioned_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());
FrameTreeNode* iframe_node = root->child_at(0);
GURL site_url(embedded_test_server()->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(site_url, iframe_node->current_url());
RenderWidgetHostViewBase* root_view = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* child_rwhv = static_cast<RenderWidgetHostViewBase*>(
iframe_node->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostInputEventRouter* router =
static_cast<WebContentsImpl*>(shell()->web_contents())
->GetInputEventRouter();
WaitForChildFrameSurfaceReady(iframe_node->current_frame_host());
InputEventAckWaiter scroll_begin_observer(
root->current_frame_host()->GetRenderWidgetHost(),
blink::WebInputEvent::kGestureScrollBegin);
InputEventAckWaiter scroll_end_observer(
root->current_frame_host()->GetRenderWidgetHost(),
blink::WebInputEvent::kGestureScrollEnd);
// Scroll the iframe upward, scroll events get bubbled up to the root.
blink::WebMouseWheelEvent scroll_event(
blink::WebInputEvent::kMouseWheel, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::kTimeStampForTesting);
gfx::Rect bounds = child_rwhv->GetViewBounds();
float scale_factor = GetPageScaleFactor(shell());
scroll_event.SetPositionInWidget(
gfx::ToCeiledInt((bounds.x() - root_view->GetViewBounds().x() + 5) *
scale_factor),
gfx::ToCeiledInt((bounds.y() - root_view->GetViewBounds().y() + 5) *
scale_factor));
scroll_event.delta_x = 0.0f;
scroll_event.delta_y = 5.0f;
scroll_event.phase = blink::WebMouseWheelEvent::kPhaseBegan;
scroll_event.has_precise_scrolling_deltas = true;
router->RouteMouseWheelEvent(root_view, &scroll_event, ui::LatencyInfo());
scroll_begin_observer.Wait();
// Now destroy the child_rwhv, scroll bubbling stops and a GSE gets sent to
// the root_view.
RenderProcessHost* rph =
iframe_node->current_frame_host()->GetSiteInstance()->GetProcess();
RenderProcessHostWatcher crash_observer(
rph, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
EXPECT_TRUE(rph->Shutdown(0, false));
crash_observer.Wait();
scroll_event.delta_y = 0.0f;
scroll_event.phase = blink::WebMouseWheelEvent::kPhaseEnded;
scroll_event.dispatch_type =
blink::WebInputEvent::DispatchType::kEventNonBlocking;
router->RouteMouseWheelEvent(root_view, &scroll_event, ui::LatencyInfo());
scroll_end_observer.Wait();
}
class ScrollObserver : public RenderWidgetHost::InputEventObserver {
public:
ScrollObserver(double delta_x, double delta_y) { Reset(delta_x, delta_y); }
~ScrollObserver() override {}
void OnInputEvent(const blink::WebInputEvent& event) override {
if (event.GetType() == blink::WebInputEvent::kGestureScrollUpdate) {
blink::WebGestureEvent received_update =
*static_cast<const blink::WebGestureEvent*>(&event);
remaining_delta_x_ -= received_update.data.scroll_update.delta_x;
remaining_delta_y_ -= received_update.data.scroll_update.delta_y;
} else if (event.GetType() == blink::WebInputEvent::kGestureScrollEnd) {
if (message_loop_runner_->loop_running())
message_loop_runner_->Quit();
DCHECK_EQ(0, remaining_delta_x_);
DCHECK_EQ(0, remaining_delta_y_);
scroll_end_received_ = true;
}
}
void Wait() {
if (!scroll_end_received_) {
message_loop_runner_->Run();
}
}
void Reset(double delta_x, double delta_y) {
message_loop_runner_ = new content::MessageLoopRunner;
remaining_delta_x_ = delta_x;
remaining_delta_y_ = delta_y;
scroll_end_received_ = false;
}
private:
scoped_refptr<content::MessageLoopRunner> message_loop_runner_;
double remaining_delta_x_;
double remaining_delta_y_;
bool scroll_end_received_;
DISALLOW_COPY_AND_ASSIGN(ScrollObserver);
};
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
ScrollBubblingFromNestedOOPIFTest) {
ui::GestureConfiguration::GetInstance()->set_scroll_debounce_interval_in_ms(
0);
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_positioned_nested_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();
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());
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());
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());
WaitForChildFrameSurfaceReady(nested_iframe_node->current_frame_host());
InputEventAckWaiter ack_observer(
root->current_frame_host()->GetRenderWidgetHost(),
blink::WebInputEvent::kGestureScrollBegin);
std::unique_ptr<ScrollObserver> scroll_observer;
if (root_view->wheel_scroll_latching_enabled()) {
// All GSU events will be wrapped between a single GSB-GSE pair. The
// expected delta value is equal to summation of all scroll update deltas.
scroll_observer = std::make_unique<ScrollObserver>(0, 15);
} else {
// Each GSU will be wrapped betweeen its own GSB-GSE pair. The expected
// delta value is the delta of the first GSU event.
scroll_observer = std::make_unique<ScrollObserver>(0, 5);
}
root->current_frame_host()->GetRenderWidgetHost()->AddInputEventObserver(
scroll_observer.get());
// Now scroll the nested frame upward, this must bubble all the way up to the
// root.
blink::WebMouseWheelEvent scroll_event(
blink::WebInputEvent::kMouseWheel, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::kTimeStampForTesting);
gfx::Rect bounds = rwhv_nested->GetViewBounds();
float scale_factor = GetPageScaleFactor(shell());
scroll_event.SetPositionInWidget(
gfx::ToCeiledInt((bounds.x() - root_view->GetViewBounds().x() + 10) *
scale_factor),
gfx::ToCeiledInt((bounds.y() - root_view->GetViewBounds().y() + 10) *
scale_factor));
scroll_event.delta_x = 0.0f;
scroll_event.delta_y = 5.0f;
scroll_event.phase = blink::WebMouseWheelEvent::kPhaseBegan;
scroll_event.has_precise_scrolling_deltas = true;
rwhv_nested->ProcessMouseWheelEvent(scroll_event, ui::LatencyInfo());
ack_observer.Wait();
// When wheel scroll latching is disabled, each wheel event will have its own
// complete scroll seqeunce.
if (!root_view->wheel_scroll_latching_enabled())
scroll_observer->Wait();
// Send 10 wheel events with delta_y = 1 to the nested oopif. When scroll
// latching is disabled, each wheel event will have its own scroll sequence.
scroll_event.delta_y = 1.0f;
scroll_event.phase = blink::WebMouseWheelEvent::kPhaseChanged;
for (int i = 0; i < 10; i++) {
if (!root_view->wheel_scroll_latching_enabled())
scroll_observer->Reset(0, 1);
rwhv_nested->ProcessMouseWheelEvent(scroll_event, ui::LatencyInfo());
if (!root_view->wheel_scroll_latching_enabled())
scroll_observer->Wait();
}
// Send a wheel end event to complete the scrolling sequence when wheel scroll
// latching is enabled.
if (root_view->wheel_scroll_latching_enabled()) {
scroll_event.delta_y = 0.0f;
scroll_event.phase = blink::WebMouseWheelEvent::kPhaseEnded;
rwhv_nested->ProcessMouseWheelEvent(scroll_event, ui::LatencyInfo());
scroll_observer->Wait();
}
}
// Ensure that the scrollability of a local subframe in an OOPIF is considered
// when acknowledging GestureScrollBegin events sent to OOPIFs.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, ScrollLocalSubframeInOOPIF) {
ui::GestureConfiguration::GetInstance()->set_scroll_debounce_interval_in_ms(
0);
// This must be tall enough such that the outer iframe is not scrollable.
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/frame_tree/page_with_tall_positioned_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());
FrameTreeNode* parent_iframe_node = root->child_at(0);
GURL outer_frame_url(embedded_test_server()->GetURL(
"baz.com", "/frame_tree/page_with_positioned_frame.html"));
NavigateFrameToURL(parent_iframe_node, outer_frame_url);
// This must be tall enough such that the inner iframe is scrollable.
FrameTreeNode* nested_iframe_node = parent_iframe_node->child_at(0);
GURL inner_frame_url(
embedded_test_server()->GetURL("baz.com", "/tall_page.html"));
NavigateFrameToURL(nested_iframe_node, inner_frame_url);
ASSERT_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://baz.com/",
DepictFrameTree(root));
RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>(
nested_iframe_node->current_frame_host()
->GetRenderWidgetHost()
->GetView());
WaitForChildFrameSurfaceReady(parent_iframe_node->current_frame_host());
// When we scroll the inner frame, we should have the GSB be consumed.
// The outer iframe not being scrollable should not cause the GSB to go
// unconsumed.
InputEventAckWaiter ack_observer(
parent_iframe_node->current_frame_host()->GetRenderWidgetHost(),
base::BindRepeating([](content::InputEventAckSource,
content::InputEventAckState state,
const blink::WebInputEvent& event) {
return event.GetType() == blink::WebGestureEvent::kGestureScrollBegin &&
state == content::INPUT_EVENT_ACK_STATE_CONSUMED;
}));
// Wait until renderer's compositor thread is synced. Otherwise the non fast
// scrollable regions won't be set when the event arrives.
MainThreadFrameObserver observer(rwhv_child->GetRenderWidgetHost());
observer.Wait();
// Now scroll the inner frame downward,
blink::WebMouseWheelEvent scroll_event(
blink::WebInputEvent::kMouseWheel, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::kTimeStampForTesting);
scroll_event.SetPositionInWidget(90, 110);
scroll_event.delta_x = 0.0f;
scroll_event.delta_y = -50.0f;
scroll_event.phase = blink::WebMouseWheelEvent::kPhaseBegan;
scroll_event.has_precise_scrolling_deltas = true;
rwhv_child->ProcessMouseWheelEvent(scroll_event, ui::LatencyInfo());
ack_observer.Wait();
}
// This test verifies that scrolling an element to view works across OOPIFs.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, ScrollElementIntoView) {
GURL url_domain_a(
embedded_test_server()->GetURL("a.com", "/iframe_out_of_view.html"));
EXPECT_TRUE(NavigateToURL(shell(), url_domain_a));
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
GURL url_domain_b(
embedded_test_server()->GetURL("b.com", "/iframe_out_of_view.html"));
NavigateFrameToURL(root->child_at(0), url_domain_b);
GURL url_domain_c(embedded_test_server()->GetURL("c.com", "/title1.html"));
NavigateFrameToURL(root->child_at(0)->child_at(0), url_domain_c);
RenderFrameHostImpl* main_frame = root->current_frame_host();
RenderFrameHostImpl* child_frame_b = root->child_at(0)->current_frame_host();
RenderFrameHostImpl* child_frame_c =
root->child_at(0)->child_at(0)->current_frame_host();
RenderWidgetHostView *main_frame_rwhv = main_frame->GetView(),
*child_frame_b_rwhv = child_frame_b->GetView(),
*child_frame_c_rwhv = child_frame_c->GetView();
// Wait until <iframe> 'b' is not visible (in main frame).
while (main_frame_rwhv->GetViewBounds().Intersects(
child_frame_b_rwhv->GetViewBounds())) {
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
run_loop.Run();
}
// Sanity check: <iframe> 'c' should not be visible either.
EXPECT_FALSE(main_frame_rwhv->GetViewBounds().Intersects(
child_frame_c_rwhv->GetViewBounds()));
// Scroll the inner most frame's body into view.
EXPECT_TRUE(ExecuteScript(child_frame_c, "document.body.scrollIntoView();"));
// Wait until <iframe> 'c' is in view bounds of parent frame and therefore
// visible.
while (!main_frame_rwhv->GetViewBounds().Intersects(
child_frame_c_rwhv->GetViewBounds())) {
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
run_loop.Run();
}
// Sanity check: <iframe> 'b' should also be visible inside parent frame.
EXPECT_TRUE(main_frame_rwhv->GetViewBounds().Intersects(
child_frame_b_rwhv->GetViewBounds()));
// Sanity check: <iframe> 'c' should be visible inside <iframe> 'b'.
EXPECT_TRUE(child_frame_b_rwhv->GetViewBounds().Intersects(
child_frame_c_rwhv->GetViewBounds()));
}
// This test verifies that Scrolling a focused editable element into view works
// when the element is inside an OOPIF.
// Flaky test, see crbug.com/793616
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
DISABLED_ScrollFocusedEditableElementIntoView) {
GURL main_frame_url(
embedded_test_server()->GetURL("a.com", "/iframe_out_of_view.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_frame_url));
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
GURL child_frame_url(
embedded_test_server()->GetURL("b.com", "/page_with_input_field.html"));
NavigateFrameToURL(root->child_at(0), child_frame_url);
RenderFrameHostImpl* main_frame = root->current_frame_host();
RenderFrameHostImpl* child_frame = root->child_at(0)->current_frame_host();
// Focus the input field.
std::string result;
ASSERT_TRUE(
ExecuteScriptAndExtractString(child_frame, "focusInputField()", &result));
ASSERT_EQ(result, "input-focus");
// Wait and verify that before scrolling the child <iframe> is not visible.
while (main_frame->GetView()->GetViewBounds().Intersects(
child_frame->GetView()->GetViewBounds())) {
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
run_loop.Run();
}
child_frame->GetFrameInputHandler()->ScrollFocusedEditableNodeIntoRect(
gfx::Rect());
// Wait until the child frame is visible.
while (!root->current_frame_host()->GetView()->GetViewBounds().Intersects(
child_frame->GetView()->GetViewBounds())) {
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
run_loop.Run();
}
// Verify that the bounding box of the <input> is visible inside the main
// frame.
ASSERT_TRUE(ExecuteScriptAndExtractString(
child_frame,
" var rect = document.querySelector('input').getBoundingClientRect();"
"domAutomationController.send(rect.x + ',' + rect.y + ','"
"+ rect.width + ',' + rect.height);",
&result));
std::vector<std::string> tokens = base::SplitString(
result, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
ASSERT_EQ(4U, tokens.size());
double x, y, width, height;
ASSERT_TRUE(base::StringToDouble(tokens[0], &x));
ASSERT_TRUE(base::StringToDouble(tokens[1], &y));
ASSERT_TRUE(base::StringToDouble(tokens[2], &width));
ASSERT_TRUE(base::StringToDouble(tokens[3], &height));
gfx::Rect test_rect(static_cast<int>(x), static_cast<int>(y),
static_cast<int>(width), static_cast<int>(height));
test_rect += child_frame->GetView()->GetViewBounds().OffsetFromOrigin();
EXPECT_TRUE(main_frame->GetView()->GetViewBounds().Intersects(test_rect));
}
#if defined(USE_AURA) || defined(OS_ANDROID)
// When unconsumed scrolls in a child bubble to the root and start an
// overscroll gesture, the subsequent gesture scroll update events should be
// consumed by the root. The child should not be able to scroll during the
// overscroll gesture.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
RootConsumesScrollDuringOverscrollGesture) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
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* child_node = root->child_at(0);
#if defined(USE_AURA)
// The child must be horizontally scrollable.
GURL child_url(embedded_test_server()->GetURL("b.com", "/wide_page.html"));
#elif defined(OS_ANDROID)
// The child must be vertically scrollable.
GURL child_url(embedded_test_server()->GetURL("b.com", "/tall_page.html"));
#endif
NavigateFrameToURL(child_node, child_url);
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));
RenderWidgetHostViewChildFrame* rwhv_child =
static_cast<RenderWidgetHostViewChildFrame*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForChildFrameSurfaceReady(child_node->current_frame_host());
ASSERT_EQ(gfx::Vector2dF(), rwhv_root->GetLastScrollOffset());
ASSERT_EQ(gfx::Vector2dF(), rwhv_child->GetLastScrollOffset());
RenderWidgetHostInputEventRouter* router =
static_cast<WebContentsImpl*>(shell()->web_contents())
->GetInputEventRouter();
{
// Set up the RenderWidgetHostInputEventRouter to send the gesture stream
// to the child.
const gfx::Rect root_bounds = rwhv_root->GetViewBounds();
const gfx::Rect child_bounds = rwhv_child->GetViewBounds();
const float page_scale_factor = GetPageScaleFactor(shell());
const gfx::PointF point_in_child(
(child_bounds.x() - root_bounds.x() + 10) * page_scale_factor,
(child_bounds.y() - root_bounds.y() + 10) * page_scale_factor);
gfx::PointF dont_care;
ASSERT_EQ(rwhv_child->GetRenderWidgetHost(),
router->GetRenderWidgetHostAtPoint(rwhv_root, point_in_child,
&dont_care));
blink::WebTouchEvent touch_event(
blink::WebInputEvent::kTouchStart, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::kTimeStampForTesting);
touch_event.touches_length = 1;
touch_event.touches[0].state = blink::WebTouchPoint::kStatePressed;
touch_event.touches[0].SetPositionInWidget(point_in_child.x(),
point_in_child.y());
touch_event.unique_touch_event_id = 1;
InputEventAckWaiter waiter(rwhv_child->GetRenderWidgetHost(),
blink::WebInputEvent::kTouchStart);
router->RouteTouchEvent(rwhv_root, &touch_event,
ui::LatencyInfo(ui::SourceEventType::TOUCH));
// With async hit testing, make sure the target for the initial TouchStart
// is resolved before sending the rest of the stream.
waiter.Wait();
blink::WebGestureEvent gesture_event(
blink::WebInputEvent::kGestureTapDown,
blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::kTimeStampForTesting);
gesture_event.source_device = blink::kWebGestureDeviceTouchscreen;
gesture_event.unique_touch_event_id = touch_event.unique_touch_event_id;
router->RouteGestureEvent(rwhv_root, &gesture_event,
ui::LatencyInfo(ui::SourceEventType::TOUCH));
}
#if defined(USE_AURA)
RenderWidgetHostViewAura* rwhva =
static_cast<RenderWidgetHostViewAura*>(rwhv_root);
std::unique_ptr<MockOverscrollControllerDelegateAura>
mock_overscroll_delegate =
std::make_unique<MockOverscrollControllerDelegateAura>(rwhva);
rwhva->overscroll_controller()->set_delegate(mock_overscroll_delegate.get());
MockOverscrollObserver* mock_overscroll_observer =
mock_overscroll_delegate.get();
#elif defined(OS_ANDROID)
RenderWidgetHostViewAndroid* rwhv_android =
static_cast<RenderWidgetHostViewAndroid*>(rwhv_root);
std::unique_ptr<MockOverscrollRefreshHandlerAndroid> mock_overscroll_handler =
std::make_unique<MockOverscrollRefreshHandlerAndroid>();
rwhv_android->SetOverscrollControllerForTesting(
mock_overscroll_handler.get());
MockOverscrollObserver* mock_overscroll_observer =
mock_overscroll_handler.get();
#endif // defined(USE_AURA)
InputEventAckWaiter gesture_begin_observer_child(
child_node->current_frame_host()->GetRenderWidgetHost(),
blink::WebInputEvent::kGestureScrollBegin);
InputEventAckWaiter gesture_end_observer_child(
child_node->current_frame_host()->GetRenderWidgetHost(),
blink::WebInputEvent::kGestureScrollEnd);
#if defined(USE_AURA)
const float overscroll_threshold =
GetOverscrollConfig(OverscrollConfig::THRESHOLD_START_TOUCHSCREEN);
#elif defined(OS_ANDROID)
const float overscroll_threshold = 0.f;
#endif
// First we need our scroll to initiate an overscroll gesture in the root
// via unconsumed scrolls in the child.
blink::WebGestureEvent gesture_scroll_begin(
blink::WebGestureEvent::kGestureScrollBegin,
blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::kTimeStampForTesting);
gesture_scroll_begin.source_device = blink::kWebGestureDeviceTouchscreen;
gesture_scroll_begin.unique_touch_event_id = 1;
gesture_scroll_begin.data.scroll_begin.delta_hint_units =
blink::WebGestureEvent::ScrollUnits::kPrecisePixels;
gesture_scroll_begin.data.scroll_begin.delta_x_hint = 0.f;
gesture_scroll_begin.data.scroll_begin.delta_y_hint = 0.f;
#if defined(USE_AURA)
// For aura, we scroll horizontally to activate an overscroll navigation.
gesture_scroll_begin.data.scroll_begin.delta_x_hint =
overscroll_threshold + 1;
#elif defined(OS_ANDROID)
// For android, we scroll vertically to activate pull-to-refresh.
gesture_scroll_begin.data.scroll_begin.delta_y_hint =
overscroll_threshold + 1;
#endif
router->RouteGestureEvent(rwhv_root, &gesture_scroll_begin,
ui::LatencyInfo(ui::SourceEventType::TOUCH));
// Make sure the child is indeed receiving the gesture stream.
gesture_begin_observer_child.Wait();
blink::WebGestureEvent gesture_scroll_update(
blink::WebGestureEvent::kGestureScrollUpdate,
blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::kTimeStampForTesting);
gesture_scroll_update.source_device = blink::kWebGestureDeviceTouchscreen;
gesture_scroll_update.unique_touch_event_id = 1;
gesture_scroll_update.data.scroll_update.delta_units =
blink::WebGestureEvent::ScrollUnits::kPrecisePixels;
gesture_scroll_update.data.scroll_update.delta_x = 0.f;
gesture_scroll_update.data.scroll_update.delta_y = 0.f;
#if defined(USE_AURA)
float* delta = &gesture_scroll_update.data.scroll_update.delta_x;
#elif defined(OS_ANDROID)
float* delta = &gesture_scroll_update.data.scroll_update.delta_y;
#endif
*delta = overscroll_threshold + 1;
mock_overscroll_observer->Reset();
// This will bring us into an overscroll gesture.
router->RouteGestureEvent(rwhv_root, &gesture_scroll_update,
ui::LatencyInfo(ui::SourceEventType::TOUCH));
// Note that in addition to verifying that we get the overscroll update, it
// is necessary to wait before sending the next event to prevent our multiple
// GestureScrollUpdates from being coalesced.
mock_overscroll_observer->WaitForUpdate();
// This scroll is in the same direction and so it will contribute to the
// overscroll.
*delta = 10.0f;
mock_overscroll_observer->Reset();
router->RouteGestureEvent(rwhv_root, &gesture_scroll_update,
ui::LatencyInfo(ui::SourceEventType::TOUCH));
mock_overscroll_observer->WaitForUpdate();
// Now we reverse direction. The child could scroll in this direction, but
// since we're in an overscroll gesture, the root should consume it.
*delta = -5.0f;
mock_overscroll_observer->Reset();
router->RouteGestureEvent(rwhv_root, &gesture_scroll_update,
ui::LatencyInfo(ui::SourceEventType::TOUCH));
mock_overscroll_observer->WaitForUpdate();
blink::WebGestureEvent gesture_scroll_end(
blink::WebGestureEvent::kGestureScrollEnd,
blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::kTimeStampForTesting);
gesture_scroll_end.source_device = blink::kWebGestureDeviceTouchscreen;
gesture_scroll_end.unique_touch_event_id = 1;
gesture_scroll_end.data.scroll_end.delta_units =
blink::WebGestureEvent::ScrollUnits::kPrecisePixels;
mock_overscroll_observer->Reset();
router->RouteGestureEvent(rwhv_root, &gesture_scroll_end,
ui::LatencyInfo(ui::SourceEventType::TOUCH));
mock_overscroll_observer->WaitForEnd();
// Ensure that the method of providing the child's scroll events to the root
// does not leave the child in an invalid state.
gesture_end_observer_child.Wait();
}
#endif // defined(USE_AURA) || defined(OS_ANDROID)
// 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)
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, ScrollEventToOOPIF) {
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_positioned_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 = 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());
WaitForChildFrameSurfaceReady(child_node->current_frame_host());
// 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.
InputEventAckWaiter waiter(
child_node->current_frame_host()->GetRenderWidgetHost(),
blink::WebInputEvent::kMouseWheel);
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);
waiter.Wait();
// 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::kMouseWheel);
}
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
InputEventRouterWheelCoalesceTest) {
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_positioned_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 = 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());
WaitForChildFrameSurfaceReady(child_node->current_frame_host());
RenderWidgetHostInputEventRouter* router =
web_contents()->GetInputEventRouter();
// Create listener for input events.
TestInputEventObserver child_frame_monitor(
child_node->current_frame_host()->GetRenderWidgetHost());
InputEventAckWaiter waiter(
child_node->current_frame_host()->GetRenderWidgetHost(),
blink::WebInputEvent::kMouseWheel);
// Send a mouse wheel event to child.
blink::WebMouseWheelEvent wheel_event(
blink::WebInputEvent::kMouseWheel, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::kTimeStampForTesting);
wheel_event.SetPositionInWidget(75, 75);
wheel_event.delta_x = 10;
wheel_event.delta_y = 20;
wheel_event.phase = blink::WebMouseWheelEvent::kPhaseBegan;
router->RouteMouseWheelEvent(rwhv_parent, &wheel_event, ui::LatencyInfo());
// Send more mouse wheel events to the child. Since we are waiting for the
// async targeting on the first event, these new mouse wheel events should
// be coalesced properly.
blink::WebMouseWheelEvent wheel_event1(
blink::WebInputEvent::kMouseWheel, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::kTimeStampForTesting);
wheel_event1.SetPositionInWidget(70, 70);
wheel_event1.delta_x = 12;
wheel_event1.delta_y = 22;
wheel_event1.phase = blink::WebMouseWheelEvent::kPhaseChanged;
router->RouteMouseWheelEvent(rwhv_parent, &wheel_event1, ui::LatencyInfo());
blink::WebMouseWheelEvent wheel_event2(
blink::WebInputEvent::kMouseWheel, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::kTimeStampForTesting);
wheel_event2.SetPositionInWidget(65, 65);
wheel_event2.delta_x = 14;
wheel_event2.delta_y = 24;
wheel_event2.phase = blink::WebMouseWheelEvent::kPhaseChanged;
router->RouteMouseWheelEvent(rwhv_parent, &wheel_event2, ui::LatencyInfo());
// Since we are targeting child, event dispatch should not happen
// synchronously. Validate that the expected target does not receive the
// event immediately.
EXPECT_FALSE(child_frame_monitor.EventWasReceived());
waiter.Wait();
EXPECT_TRUE(child_frame_monitor.EventWasReceived());
EXPECT_EQ(child_frame_monitor.EventType(), blink::WebInputEvent::kMouseWheel);
// Check if the two mouse-wheel update events are coalesced correctly.
const auto& gesture_event =
static_cast<const blink::WebGestureEvent&>(child_frame_monitor.event());
EXPECT_EQ(26 /* wheel_event1.delta_x + wheel_event2.delta_x */,
gesture_event.data.scroll_update.delta_x);
EXPECT_EQ(46 /* wheel_event1.delta_y + wheel_event2.delta_y */,
gesture_event.data.scroll_update.delta_y);
}
#endif
// Test that mouse events are being routed to the correct RenderWidgetHostView
// based on coordinates.
#if defined(THREAD_SANITIZER)
// 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)
// High DPI browser tests are not needed on Android, and confuse some of the
// coordinate calculations. Android uses fixed device scale factor.
// 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.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, NestedSurfaceHitTestTest) {
NestedSurfaceHitTestTestHelper(shell(), embedded_test_server());
}
IN_PROC_BROWSER_TEST_F(SitePerProcessHighDPIBrowserTest,
NestedSurfaceHitTestTest) {
NestedSurfaceHitTestTestHelper(shell(), embedded_test_server());
}
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, OverlapSurfaceHitTestTest) {
OverlapSurfaceHitTestHelper(shell(), embedded_test_server());
}
IN_PROC_BROWSER_TEST_F(SitePerProcessHighDPIBrowserTest,
OverlapSurfaceHitTestTest) {
OverlapSurfaceHitTestHelper(shell(), embedded_test_server());
}
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, HitTestLayerSquashing) {
HitTestLayerSquashing(shell(), embedded_test_server());
}
IN_PROC_BROWSER_TEST_F(SitePerProcessHighDPIBrowserTest,
HitTestLayerSquashing) {
HitTestLayerSquashing(shell(), embedded_test_server());
}
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, HitTestWatermark) {
HitTestWatermark(shell(), embedded_test_server());
}
IN_PROC_BROWSER_TEST_F(SitePerProcessHighDPIBrowserTest, HitTestWatermark) {
HitTestWatermark(shell(), embedded_test_server());
}
// This test tests that browser process hittesting ignores frames with
// pointer-events: none.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
SurfaceHitTestPointerEventsNone) {
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_positioned_frame_pointer-events_none.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());
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());
WaitForChildFrameSurfaceReady(child_node->current_frame_host());
// Target input event to child frame.
blink::WebMouseEvent child_event(blink::WebInputEvent::kMouseDown,
blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::kTimeStampForTesting);
child_event.button = blink::WebPointerProperties::Button::kLeft;
child_event.SetPositionInWidget(75, 75);
child_event.click_count = 1;
main_frame_monitor.ResetEventReceived();
child_frame_monitor.ResetEventReceived();
router->RouteMouseEvent(root_view, &child_event, ui::LatencyInfo());
EXPECT_TRUE(main_frame_monitor.EventWasReceived());
EXPECT_NEAR(75, main_frame_monitor.event().PositionInWidget().x, 2);
EXPECT_NEAR(75, main_frame_monitor.event().PositionInWidget().y, 2);
EXPECT_FALSE(child_frame_monitor.EventWasReceived());
}
// Verify that an event is properly retargeted to the main frame when an
// asynchronous hit test to the child frame times out.
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
AsynchronousHitTestChildTimeout) {
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_positioned_busy_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());
FrameTreeNode* child_node = root->child_at(0);
// 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();
// Shorten the timeout for purposes of this test.
router->GetRenderWidgetTargeterForTests()
->set_async_hit_test_timeout_delay_for_testing(
TestTimeouts::tiny_timeout());
RenderWidgetHostViewBase* root_view = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForChildFrameSurfaceReady(child_node->current_frame_host());
// Target input event to child frame. It should get delivered to the main
// frame instead because the child frame main thread is non-responsive.
blink::WebMouseEvent child_event(blink::WebInputEvent::kMouseDown,
blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent