// 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 #include #include #include #include #include #include #include #include #include #include #include #include "base/bind.h" #include "base/callback.h" #include "base/callback_helpers.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/memory/scoped_refptr.h" #include "base/path_service.h" #include "base/run_loop.h" #include "base/scoped_observer.h" #include "base/sequenced_task_runner.h" #include "base/single_thread_task_runner.h" #include "base/stl_util.h" #include "base/strings/pattern.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "base/test/bind_test_util.h" #include "base/test/metrics/histogram_tester.h" #include "base/test/test_timeouts.h" #include "base/threading/sequenced_task_runner_handle.h" #include "base/threading/thread_task_runner_handle.h" #include "base/time/time.h" #include "base/timer/timer.h" #include "build/build_config.h" #include "cc/input/touch_action.h" #include "components/network_session_configurator/common/network_switches.h" #include "components/viz/common/features.h" #include "content/browser/child_process_security_policy_impl.h" #include "content/browser/gpu/compositor_util.h" #include "content/browser/renderer_host/agent_scheduling_group_host.h" #include "content/browser/renderer_host/cross_process_frame_connector.h" #include "content/browser/renderer_host/frame_navigation_entry.h" #include "content/browser/renderer_host/frame_tree.h" #include "content/browser/renderer_host/input/input_router.h" #include "content/browser/renderer_host/input/synthetic_gesture.h" #include "content/browser/renderer_host/input/synthetic_gesture_target.h" #include "content/browser/renderer_host/input/synthetic_smooth_scroll_gesture.h" #include "content/browser/renderer_host/input/synthetic_tap_gesture.h" #include "content/browser/renderer_host/input/synthetic_touchscreen_pinch_gesture.h" #include "content/browser/renderer_host/navigation_controller_impl.h" #include "content/browser/renderer_host/navigation_entry_impl.h" #include "content/browser/renderer_host/navigation_request.h" #include "content/browser/renderer_host/navigator.h" #include "content/browser/renderer_host/render_frame_host_impl.h" #include "content/browser/renderer_host/render_frame_proxy_host.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/content_navigation_policy.h" #include "content/common/frame.mojom-test-utils.h" #include "content/common/frame_messages.h" #include "content/common/input/actions_parser.h" #include "content/common/input/synthetic_pinch_gesture_params.h" #include "content/common/input_messages.h" #include "content/common/page_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_task_traits.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/context_menu_params.h" #include "content/public/browser/javascript_dialog_manager.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/render_widget_host_observer.h" #include "content/public/common/content_client.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/common/use_zoom_for_dsf_policy.h" #include "content/public/test/back_forward_cache_util.h" #include "content/public/test/browser_test.h" #include "content/public/test/browser_test_utils.h" #include "content/public/test/content_browser_test_utils.h" #include "content/public/test/hit_test_region_observer.h" #include "content/public/test/navigation_handle_observer.h" #include "content/public/test/render_frame_host_test_support.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_navigation_interceptor.h" #include "content/test/render_document_feature.h" #include "content/test/test_content_browser_client.h" #include "ipc/constants.mojom.h" #include "ipc/ipc_security_test_util.h" #include "media/base/media_switches.h" #include "mojo/public/cpp/bindings/pending_remote.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 "services/network/public/cpp/features.h" #include "services/network/public/cpp/web_sandbox_flags.h" #include "services/network/public/mojom/web_sandbox_flags.mojom-shared.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h" #include "third_party/blink/public/common/feature_policy/feature_policy.h" #include "third_party/blink/public/common/feature_policy/policy_value.h" #include "third_party/blink/public/common/features.h" #include "third_party/blink/public/common/input/web_input_event.h" #include "third_party/blink/public/mojom/frame/frame.mojom-test-utils.h" #include "third_party/blink/public/mojom/frame/frame.mojom.h" #include "third_party/blink/public/mojom/security_context/insecure_request_policy.mojom.h" #include "ui/display/display_switches.h" #include "ui/display/screen.h" #include "ui/events/base_event_utils.h" #include "ui/events/blink/blink_features.h" #include "ui/events/event.h" #include "ui/events/event_utils.h" #include "ui/events/gesture_detection/gesture_configuration.h" #include "ui/events/keycodes/dom/dom_code.h" #include "ui/events/keycodes/dom/dom_key.h" #include "ui/events/keycodes/dom/keycode_converter.h" #include "ui/events/keycodes/keyboard_codes.h" #include "ui/gfx/geometry/point.h" #include "ui/gfx/geometry/rect.h" #include "ui/gfx/transform.h" #include "ui/latency/latency_info.h" #include "ui/native_theme/native_theme_features.h" #if defined(USE_AURA) #include "content/browser/renderer_host/render_widget_host_view_aura.h" #include "ui/aura/window.h" #include "ui/aura/window_tree_host.h" #endif #if defined(OS_MAC) #include "content/browser/renderer_host/input/synthetic_touchpad_pinch_gesture.h" #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 "content/browser/android/gesture_listener_manager.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/browser/web_contents/web_contents_view_android.h" #include "content/public/browser/android/child_process_importance.h" #include "content/public/common/content_client.h" #include "content/test/mock_overscroll_refresh_handler_android.h" #include "content/test/test_content_browser_client.h" #include "ui/android/view_android.h" #include "ui/android/window_android.h" #include "ui/events/android/event_handler_android.h" #include "ui/events/android/motion_event_android.h" #include "ui/gfx/geometry/point_f.h" #endif #if defined(OS_CHROMEOS) #include "ui/aura/test/test_screen.h" #endif using ::testing::SizeIs; using ::testing::WhenSorted; using ::testing::ElementsAre; 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; EXPECT_EQ(true, EvalJs(sender_ftn, "(" + post_message_script + ");")); 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) { return EvalJs(ftn, "window.receivedMessages;").ExtractInt(); } // 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) { EXPECT_EQ(true, EvalJs(caller_frame, JsReplace("!!window.open($1, $2)", url, name))); } // 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::Type::kMouseDown, blink::WebInputEvent::kNoModifiers, blink::WebInputEvent::GetStaticTimeStampForTests()); mouse_event.button = blink::WebPointerProperties::Button::kLeft; mouse_event.SetPositionInWidget(x, y); rwh->ForwardMouseEvent(mouse_event); } // Retrieve self.origin for the frame |ftn|. EvalJsResult GetOriginFromRenderer(FrameTreeNode* ftn) { return EvalJs(ftn, "self.origin;"); } double GetFrameDeviceScaleFactor(const ToRenderFrameHost& adapter) { return EvalJs(adapter, "window.devicePixelRatio;").ExtractDouble(); } 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_; base::RunLoop run_loop_; 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; run_loop_.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; run_loop_.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) {} ~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 RunLoop used to spin the message loop. base::RunLoop run_loop_; DISALLOW_COPY_AND_ASSIGN(RenderFrameHostCreatedObserver); }; RenderFrameHostCreatedObserver::~RenderFrameHostCreatedObserver() { } void RenderFrameHostCreatedObserver::Wait() { run_loop_.Run(); } void RenderFrameHostCreatedObserver::RenderFrameCreated( RenderFrameHost* render_frame_host) { frames_created_++; if (frames_created_ == expected_frame_count_) { run_loop_.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& event) override { user_interaction_received_ = true; } bool user_interaction_received_; DISALLOW_COPY_AND_ASSIGN(UserInteractionObserver); }; // Supports waiting until a WebContents notifies its observers that the visible // security state changed, and a test-specific condition is true at that time. class VisibleSecurityStateObserver : public WebContentsObserver { public: // Invoked at Wait() start and when the visible security state changes. // If the callback returns true, stops waiting. using ConditionCallback = base::RepeatingCallback; // Creates a VisibleSecurityStateObserver which will wait until // a visible security state change is announced by |web_contents| and // |condition_callback| returns true (unless |condition_callback| returns true // in Wait() already, when it will not wait at all). VisibleSecurityStateObserver(WebContents* web_contents, ConditionCallback condition_callback) : WebContentsObserver(web_contents), condition_callback_(condition_callback) {} ~VisibleSecurityStateObserver() override = default; VisibleSecurityStateObserver(const VisibleSecurityStateObserver& other) = delete; VisibleSecurityStateObserver& operator=( const VisibleSecurityStateObserver& other) = delete; // If the |condition_callback| passed to the constructor returns true, this // returns immediately. Otherwise, blocks until the |web_contents| passed to // the constructor notifies about a visible security state change and the // |condition_callback| evaluates to true. void Wait() { if (condition_callback_.Run(web_contents())) return; run_loop_.Run(); } void DidChangeVisibleSecurityState() override { if (condition_callback_.Run(web_contents())) run_loop_.Quit(); } private: ConditionCallback condition_callback_; base::RunLoop run_loop_; }; // 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(); } class RenderWidgetHostVisibilityObserver : public RenderWidgetHostObserver { public: explicit RenderWidgetHostVisibilityObserver(RenderWidgetHostImpl* rwhi, bool expected_visibility_state) : expected_visibility_state_(expected_visibility_state), observer_(this), was_observed_(false), did_fail_(false), render_widget_(rwhi) { observer_.Add(render_widget_); message_loop_runner_ = new MessageLoopRunner; } bool WaitUntilSatisfied() { if (!was_observed_) message_loop_runner_->Run(); if (observer_.IsObserving(render_widget_)) observer_.Remove(render_widget_); return !did_fail_; } private: void RenderWidgetHostVisibilityChanged(RenderWidgetHost* widget_host, bool became_visible) override { was_observed_ = true; did_fail_ = expected_visibility_state_ != became_visible; if (message_loop_runner_->loop_running()) message_loop_runner_->Quit(); } void RenderWidgetHostDestroyed(RenderWidgetHost* widget_host) override { observer_.Remove(widget_host); } bool expected_visibility_state_; scoped_refptr message_loop_runner_; ScopedObserver observer_; bool was_observed_; bool did_fail_; RenderWidgetHost* render_widget_; DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostVisibilityObserver); }; bool ConvertJSONToPoint(const std::string& str, gfx::PointF* point) { base::Optional value = base::JSONReader::Read(str); if (!value.has_value()) return false; if (!value->is_dict()) return false; base::Optional x = value->FindDoubleKey("x"); base::Optional y = value->FindDoubleKey("y"); if (!x.has_value()) return false; if (!y.has_value()) return false; point->set_x(x.value()); point->set_y(y.value()); return true; } void OpenURLBlockUntilNavigationComplete(Shell* shell, const GURL& url) { EXPECT_TRUE(WaitForLoadStop(shell->web_contents())); TestNavigationObserver same_tab_observer(shell->web_contents(), 1); OpenURLParams params( url, content::Referrer(shell->web_contents()->GetLastCommittedURL(), network::mojom::ReferrerPolicy::kAlways), WindowOpenDisposition::CURRENT_TAB, ui::PAGE_TRANSITION_LINK, true /* is_renderer_initiated */); params.initiator_origin = url::Origin::Create(url); shell->OpenURLFromTab(shell->web_contents(), params); same_tab_observer.Wait(); } // Helper function to generate a feature policy for a single feature and a list // of origins. // (Equivalent to the declared policy "feature origin1 origin2 ...".) // If the origins list is empty, it's treated as matches all origins // (Equivalent to the declared policy "feature *") blink::ParsedFeaturePolicyDeclaration CreateParsedFeaturePolicyDeclaration( blink::mojom::FeaturePolicyFeature feature, const std::vector& origins, bool match_all_origins = false) { blink::ParsedFeaturePolicyDeclaration declaration; declaration.feature = feature; declaration.matches_all_origins = match_all_origins; declaration.matches_opaque_src = match_all_origins; for (const auto& origin : origins) declaration.allowed_origins.push_back(url::Origin::Create(origin)); std::sort(declaration.allowed_origins.begin(), declaration.allowed_origins.end()); return declaration; } blink::ParsedFeaturePolicy CreateParsedFeaturePolicy( const std::vector& features, const std::vector& origins, bool match_all_origins = false) { blink::ParsedFeaturePolicy result; result.reserve(features.size()); for (const auto& feature : features) result.push_back(CreateParsedFeaturePolicyDeclaration(feature, origins, match_all_origins)); return result; } blink::ParsedFeaturePolicy CreateParsedFeaturePolicyMatchesAll( const std::vector& features) { return CreateParsedFeaturePolicy(features, {}, true); } blink::ParsedFeaturePolicy CreateParsedFeaturePolicyMatchesNone( const std::vector& features) { return CreateParsedFeaturePolicy(features, {}); } // Check frame depth on node, widget, and process all match expected depth. void CheckFrameDepth(unsigned int expected_depth, FrameTreeNode* node) { EXPECT_EQ(expected_depth, node->depth()); RenderProcessHost::Priority priority = node->current_frame_host()->GetRenderWidgetHost()->GetPriority(); EXPECT_EQ(expected_depth, priority.frame_depth); EXPECT_EQ(expected_depth, node->current_frame_host()->GetProcess()->GetFrameDepth()); } // Check |intersects_viewport| on widget and process. bool CheckIntersectsViewport(bool expected, FrameTreeNode* node) { RenderProcessHost::Priority priority = node->current_frame_host()->GetRenderWidgetHost()->GetPriority(); return priority.intersects_viewport == expected && node->current_frame_host()->GetProcess()->GetIntersectsViewport() == expected; } // Layout child frames in cross_site_iframe_factory.html so that they are the // same width as the viewport, and 75% of the height of the window. This is for // testing viewport intersection. Note this does not recurse into child frames // and re-layout in the same way since children might be in a different origin. void LayoutNonRecursiveForTestingViewportIntersection( WebContents* web_contents) { static const char kRafScript[] = R"( let width = window.innerWidth; let height = window.innerHeight * 0.75; for (let i = 0; i < window.frames.length; i++) { let child = document.getElementById("child-" + i); child.width = width; child.height = height; } )"; ASSERT_TRUE( EvalJsAfterLifecycleUpdate(web_contents, kRafScript, "").error.empty()); } void GenerateTapDownGesture(RenderWidgetHost* rwh) { blink::WebGestureEvent gesture_tap_down( blink::WebGestureEvent::Type::kGestureTapDown, blink::WebInputEvent::kNoModifiers, blink::WebInputEvent::GetStaticTimeStampForTests(), blink::WebGestureDevice::kTouchscreen); gesture_tap_down.is_source_touch_event_set_non_blocking = true; rwh->ForwardGestureEvent(gesture_tap_down); } // Class to monitor incoming FrameHostMsg_UpdateViewportIntersection messages. class UpdateViewportIntersectionMessageFilter : public content::BrowserMessageFilter { public: // If no routing_id is specified, filter will match all routing id's. explicit UpdateViewportIntersectionMessageFilter( int routing_id = MSG_ROUTING_NONE) : content::BrowserMessageFilter(FrameMsgStart), routing_id_(routing_id), msg_received_(false) {} bool OnMessageReceived(const IPC::Message& message) override { if (routing_id_ == MSG_ROUTING_NONE || message.routing_id() == routing_id_) { IPC_BEGIN_MESSAGE_MAP(UpdateViewportIntersectionMessageFilter, message) IPC_MESSAGE_HANDLER(FrameHostMsg_UpdateViewportIntersection, OnUpdateViewportIntersection) IPC_END_MESSAGE_MAP() } return false; } const blink::ViewportIntersectionState& GetIntersectionState() const { return intersection_state_; } bool MessageReceived() const { return msg_received_; } void Clear() { msg_received_ = false; intersection_state_ = blink::ViewportIntersectionState(); } void Wait() { DCHECK(!run_loop_); if (msg_received_) { msg_received_ = false; return; } std::unique_ptr run_loop(new base::RunLoop); run_loop_ = run_loop.get(); run_loop_->Run(); run_loop_ = nullptr; msg_received_ = false; } void set_run_loop(base::RunLoop* run_loop) { run_loop_ = run_loop; } private: ~UpdateViewportIntersectionMessageFilter() override {} void OnUpdateViewportIntersection( const blink::ViewportIntersectionState& intersection_state) { intersection_state_ = intersection_state; msg_received_ = true; if (run_loop_) run_loop_->Quit(); } const int routing_id_; base::RunLoop* run_loop_ = nullptr; bool msg_received_; blink::ViewportIntersectionState intersection_state_; DISALLOW_COPY_AND_ASSIGN(UpdateViewportIntersectionMessageFilter); }; } // namespace // // SitePerProcessBrowserTestBase // SitePerProcessBrowserTestBase::SitePerProcessBrowserTestBase() { #if !defined(OS_ANDROID) // TODO(bokan): Needed for scrollability check in // FrameOwnerPropertiesPropagationScrolling. crbug.com/662196. feature_list_.InitAndDisableFeature(features::kOverlayScrollbar); #endif } std::string SitePerProcessBrowserTestBase::DepictFrameTree( FrameTreeNode* node) { return visualizer_.DepictFrameTree(node); } void SitePerProcessBrowserTestBase::SetUpCommandLine( base::CommandLine* command_line) { ContentBrowserTest::SetUpCommandLine(command_line); IsolateAllSitesForTesting(command_line); command_line->AppendSwitch(switches::kValidateInputEventStream); } void SitePerProcessBrowserTestBase::SetUpOnMainThread() { host_resolver()->AddRule("*", "127.0.0.1"); SetupCrossSiteRedirector(embedded_test_server()); ASSERT_TRUE(embedded_test_server()->Start()); } // // SitePerProcessBrowserTest // SitePerProcessBrowserTest::SitePerProcessBrowserTest() { InitAndEnableRenderDocumentFeature(&feature_list_, GetParam()); } // // SitePerProcessHighDPIBrowserTest // class SitePerProcessHighDPIBrowserTest : public SitePerProcessBrowserTest { public: const double kDeviceScaleFactor = 2.0; SitePerProcessHighDPIBrowserTest() {} protected: void SetUpCommandLine(base::CommandLine* command_line) override { SitePerProcessBrowserTestBase::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 { SitePerProcessBrowserTestBase::SetUpCommandLine(command_line); command_line->AppendSwitch(switches::kIgnoreCertificateErrors); } }; // SitePerProcessFeaturePolicyBrowserTest class SitePerProcessFeaturePolicyBrowserTest : public SitePerProcessBrowserTestBase { public: SitePerProcessFeaturePolicyBrowserTest() = default; // Enable tests for parameterized features. void SetUpCommandLine(base::CommandLine* command_line) override { SitePerProcessBrowserTestBase::SetUpCommandLine(command_line); command_line->AppendSwitchASCII(switches::kEnableBlinkFeatures, "ExperimentalProductivityFeatures"); } }; // SitePerProcessAutoplayBrowserTest class SitePerProcessAutoplayBrowserTest : public SitePerProcessBrowserTest { public: SitePerProcessAutoplayBrowserTest() = default; void SetUpCommandLine(base::CommandLine* command_line) override { SitePerProcessBrowserTestBase::SetUpCommandLine(command_line); command_line->AppendSwitchASCII( switches::kAutoplayPolicy, switches::autoplay::kDocumentUserActivationRequiredPolicy); command_line->AppendSwitchASCII(switches::kEnableBlinkFeatures, "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()); } }; class SitePerProcessScrollAnchorTest : public SitePerProcessBrowserTest { public: SitePerProcessScrollAnchorTest() = default; void SetUpCommandLine(base::CommandLine* command_line) override { SitePerProcessBrowserTestBase::SetUpCommandLine(command_line); command_line->AppendSwitchASCII(switches::kEnableBlinkFeatures, "ScrollAnchorSerialization"); } }; // SitePerProcessEmbedderCSPEnforcementBrowserTest class SitePerProcessEmbedderCSPEnforcementBrowserTest : public SitePerProcessBrowserTest { public: SitePerProcessEmbedderCSPEnforcementBrowserTest() {} protected: void SetUpCommandLine(base::CommandLine* command_line) override { SitePerProcessBrowserTestBase::SetUpCommandLine(command_line); // TODO(amalika): Remove this switch when the EmbedderCSPEnforcement becomes // stable command_line->AppendSwitchASCII(switches::kEnableBlinkFeatures, "EmbedderCSPEnforcement"); } }; // SitePerProcessProgrammaticScrollTest. class SitePerProcessProgrammaticScrollTest : public SitePerProcessBrowserTest { public: SitePerProcessProgrammaticScrollTest() : kInfinity(1000000U), kPositiveXYPlane(0, 0, kInfinity, kInfinity) {} protected: const size_t kInfinity; const std::string kIframeOutOfViewHTML = "/iframe_out_of_view.html"; const std::string kIframeClippedHTML = "/iframe_clipped.html"; const std::string kInputBoxHTML = "/input_box.html"; const std::string kIframeSelector = "iframe"; const std::string kInputSelector = "input"; const gfx::Rect kPositiveXYPlane; // Waits until the |load| handle is called inside the frame. void WaitForOnLoad(FrameTreeNode* node) { RunCommandAndWaitForResponse(node, "notifyWhenLoaded();", "LOADED"); } void WaitForElementVisible(FrameTreeNode* node, const std::string& sel) { RunCommandAndWaitForResponse( node, base::StringPrintf("notifyWhenVisible(document.querySelector('%s'));", sel.c_str()), "VISIBLE"); } void WaitForViewportToStabilize(FrameTreeNode* node) { RunCommandAndWaitForResponse(node, "notifyWhenViewportStable(0);", "VIEWPORT_STABLE"); } void AddFocusedInputField(FrameTreeNode* node) { ASSERT_TRUE(ExecuteScript(node, "addFocusedInputField();")); } void SetWindowScroll(FrameTreeNode* node, int x, int y) { ASSERT_TRUE(ExecuteScript( node, base::StringPrintf("window.scrollTo(%d, %d);", x, y))); } // Helper function to retrieve the bounding client rect of the element // identified by |sel| inside |rfh|. gfx::Rect GetBoundingClientRect(FrameTreeNode* node, const std::string& sel) { std::string result; EXPECT_TRUE(ExecuteScriptAndExtractString( node, base::StringPrintf( "window.domAutomationController.send(rectAsString(" " document.querySelector('%s').getBoundingClientRect()));", sel.c_str()), &result)); return GetRectFromString(result); } // Returns a rect representing the current |visualViewport| in the main frame // of |contents|. gfx::Rect GetVisualViewport(FrameTreeNode* node) { std::string result; EXPECT_TRUE(ExecuteScriptAndExtractString( node, "window.domAutomationController.send(" " rectAsString(visualViewportAsRect()));", &result)); return GetRectFromString(result); } float GetVisualViewportScale(FrameTreeNode* node) { double scale; EXPECT_TRUE(ExecuteScriptAndExtractDouble( node, "window.domAutomationController.send(visualViewport.scale);", &scale)); return static_cast(scale); } private: void RunCommandAndWaitForResponse(FrameTreeNode* node, const std::string& command, const std::string& response) { std::string msg_from_renderer; ASSERT_TRUE( ExecuteScriptAndExtractString(node, command, &msg_from_renderer)); ASSERT_EQ(response, msg_from_renderer); } gfx::Rect GetRectFromString(const std::string& str) { std::vector tokens = base::SplitString( str, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); EXPECT_EQ(4U, tokens.size()); double x = 0.0, y = 0.0, width = 0.0, height = 0.0; EXPECT_TRUE(base::StringToDouble(tokens[0], &x)); EXPECT_TRUE(base::StringToDouble(tokens[1], &y)); EXPECT_TRUE(base::StringToDouble(tokens[2], &width)); EXPECT_TRUE(base::StringToDouble(tokens[3], &height)); return {static_cast(x), static_cast(y), static_cast(width), static_cast(height)}; } DISALLOW_COPY_AND_ASSIGN(SitePerProcessProgrammaticScrollTest); }; IN_PROC_BROWSER_TEST_P(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(); EXPECT_EQ(expected_dip_scale, GetFrameDeviceScaleFactor(root)); ASSERT_EQ(1U, root->child_count()); FrameTreeNode* child = root->child_at(0); EXPECT_EQ(expected_dip_scale, GetFrameDeviceScaleFactor(child)); } #if defined(OS_CHROMEOS) IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, SubframeUpdateToCorrectDeviceScaleFactor) { GURL main_url(embedded_test_server()->GetURL( "a.com", "/cross_site_iframe_factory.html?a(b)")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); EXPECT_EQ(1.0, GetFrameDeviceScaleFactor(web_contents())); FrameTreeNode* root = web_contents()->GetFrameTree()->root(); ASSERT_EQ(1U, root->child_count()); FrameTreeNode* child = root->child_at(0); EXPECT_EQ(1.0, GetFrameDeviceScaleFactor(child)); double expected_dip_scale = 2.0; // TODO(oshima): allow DeviceScaleFactor change on other platforms // (win, linux, mac, android and mus). aura::TestScreen* test_screen = static_cast(display::Screen::GetScreen()); test_screen->CreateHostForPrimaryDisplay(); test_screen->SetDeviceScaleFactor(expected_dip_scale); // This forces |expected_dip_scale| to be applied to the aura::WindowTreeHost // and aura::Window. aura::WindowTreeHost* window_tree_host = shell()->window()->GetHost(); window_tree_host->SetBoundsInPixels(window_tree_host->GetBoundsInPixels()); double device_scale_factor = 0; // Wait until dppx becomes 2 if the frame's dpr hasn't beeen updated // to 2 yet. const char kScript[] = "function sendDpr() " "{window.domAutomationController.send(window.devicePixelRatio);}; " "if (window.devicePixelRatio == 2) sendDpr();" "window.matchMedia('screen and " "(min-resolution: 2dppx)').addListener(function(e) { if (e.matches) { " "sendDpr();}})"; // Make sure that both main frame and iframe are updated to 2x. EXPECT_TRUE( ExecuteScriptAndExtractDouble(child, kScript, &device_scale_factor)); EXPECT_EQ(expected_dip_scale, device_scale_factor); device_scale_factor = 0; EXPECT_TRUE(ExecuteScriptAndExtractDouble(web_contents(), kScript, &device_scale_factor)); EXPECT_EQ(expected_dip_scale, device_scale_factor); } #endif class TextAutosizerPageInfoInterceptor : public blink::mojom::LocalMainFrameHostInterceptorForTesting { public: explicit TextAutosizerPageInfoInterceptor( RenderFrameHostImpl* render_frame_host) : render_frame_host_(render_frame_host) { render_frame_host_->local_main_frame_host_receiver_for_testing() .SwapImplForTesting(this); } ~TextAutosizerPageInfoInterceptor() override = default; LocalMainFrameHost* GetForwardingInterface() override { return render_frame_host_; } void WaitForPageInfo(base::Optional target_main_frame_width, base::Optional target_device_scale_adjustment) { if (remote_page_info_seen_) return; target_main_frame_width_ = target_main_frame_width; target_device_scale_adjustment_ = target_device_scale_adjustment; run_loop_ = std::make_unique(); run_loop_->Run(); run_loop_.reset(); } const blink::mojom::TextAutosizerPageInfo& GetTextAutosizerPageInfo() { return *remote_page_info_; } void TextAutosizerPageInfoChanged( blink::mojom::TextAutosizerPageInfoPtr remote_page_info) override { if ((!target_main_frame_width_ || remote_page_info->main_frame_width != target_main_frame_width_) && (!target_device_scale_adjustment_ || remote_page_info->device_scale_adjustment != target_device_scale_adjustment_)) { return; } remote_page_info_ = remote_page_info.Clone(); remote_page_info_seen_ = true; if (run_loop_) run_loop_->Quit(); GetForwardingInterface()->TextAutosizerPageInfoChanged( std::move(remote_page_info)); } private: RenderFrameHostImpl* render_frame_host_; bool remote_page_info_seen_ = false; blink::mojom::TextAutosizerPageInfoPtr remote_page_info_ = blink::mojom::TextAutosizerPageInfo::New(/*main_frame_width=*/0, /*main_frame_layout_width=*/0, /*device_scale_adjustment=*/1.f); std::unique_ptr run_loop_; base::Optional target_main_frame_width_; base::Optional target_device_scale_adjustment_; }; // TODO(tonikitoo): Move to fake_remote_frame.h|cc in case it is useful // for other tests. class FakeRemoteMainFrame : public blink::mojom::RemoteMainFrame { public: FakeRemoteMainFrame() = default; ~FakeRemoteMainFrame() override = default; void Init(blink::AssociatedInterfaceProvider* provider) { provider->OverrideBinderForTesting( blink::mojom::RemoteMainFrame::Name_, base::BindRepeating(&FakeRemoteMainFrame::BindFrameHostReceiver, base::Unretained(this))); } // blink::mojom::RemoteMainFrame overrides: void UpdateTextAutosizerPageInfo( blink::mojom::TextAutosizerPageInfoPtr page_info) override {} private: void BindFrameHostReceiver(mojo::ScopedInterfaceEndpointHandle handle) { receiver_.Bind( mojo::PendingAssociatedReceiver( std::move(handle))); } mojo::AssociatedReceiver receiver_{this}; }; // This class intercepts RenderFrameProxyHost creations, and overrides their // respective blink::mojom::RemoteMainFrame instances, so that it can watch for // text autosizer page info updates. class UpdateTextAutosizerInfoProxyObserver { public: UpdateTextAutosizerInfoProxyObserver() { RenderFrameProxyHost::SetCreatedCallbackForTesting( base::BindRepeating(&UpdateTextAutosizerInfoProxyObserver:: RenderFrameProxyHostCreatedCallback, base::Unretained(this))); } ~UpdateTextAutosizerInfoProxyObserver() { RenderFrameProxyHost::SetCreatedCallbackForTesting( RenderFrameProxyHost::CreatedCallback()); } const blink::mojom::TextAutosizerPageInfo& TextAutosizerPageInfo( RenderFrameProxyHost* proxy) { return remote_frames_[proxy]->page_info(); } private: class Remote : public FakeRemoteMainFrame { public: explicit Remote(RenderFrameProxyHost* proxy) { Init(proxy->GetRemoteAssociatedInterfacesTesting()); } void UpdateTextAutosizerPageInfo( blink::mojom::TextAutosizerPageInfoPtr page_info) override { page_info_ = *page_info; } const blink::mojom::TextAutosizerPageInfo& page_info() { return page_info_; } private: blink::mojom::TextAutosizerPageInfo page_info_; }; void RenderFrameProxyHostCreatedCallback(RenderFrameProxyHost* proxy_host) { remote_frames_[proxy_host] = std::make_unique(proxy_host); } std::map> remote_frames_; }; // Make sure that when a relevant feature of the main frame changes, e.g. the // frame width, that the browser is notified. IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, TextAutosizerPageInfo) { UpdateTextAutosizerInfoProxyObserver update_text_autosizer_info_observer; blink::web_pref::WebPreferences prefs = web_contents()->GetOrCreateWebPreferences(); prefs.text_autosizing_enabled = true; GURL main_url(embedded_test_server()->GetURL( "a.com", "/cross_site_iframe_factory.html?a(b)")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); web_contents()->SetWebPreferences(prefs); FrameTreeNode* root = web_contents()->GetFrameTree()->root(); ASSERT_EQ(1U, root->child_count()); FrameTreeNode* b_child = root->child_at(0); blink::mojom::TextAutosizerPageInfo received_page_info; auto interceptor = std::make_unique( web_contents()->GetMainFrame()); #if defined(OS_ANDROID) prefs.device_scale_adjustment += 0.05f; // Change the device scale adjustment to trigger a RemotePageInfo update. web_contents()->SetWebPreferences(prefs); // Make sure we receive a ViewHostMsg from the main frame's renderer. interceptor->WaitForPageInfo(base::Optional(), prefs.device_scale_adjustment); // Make sure the correct page message is sent to the child. base::RunLoop().RunUntilIdle(); received_page_info = interceptor->GetTextAutosizerPageInfo(); EXPECT_EQ(prefs.device_scale_adjustment, received_page_info.device_scale_adjustment); #else // Resize the main frame, then wait to observe that the RemotePageInfo message // arrives. auto* view = web_contents()->GetRenderWidgetHostView(); gfx::Rect old_bounds = view->GetViewBounds(); gfx::Rect new_bounds( old_bounds.origin(), gfx::Size(old_bounds.width() - 20, old_bounds.height() - 20)); view->SetBounds(new_bounds); // Make sure we receive a ViewHostMsg from the main frame's renderer. interceptor->WaitForPageInfo(new_bounds.width(), base::Optional()); // Make sure the correct page message is sent to the child. base::RunLoop().RunUntilIdle(); received_page_info = interceptor->GetTextAutosizerPageInfo(); EXPECT_EQ(new_bounds.width(), received_page_info.main_frame_width); #endif // defined(OS_ANDROID) // Dynamically create a new, cross-process frame to test sending the cached // TextAutosizerPageInfo. GURL c_url = embedded_test_server()->GetURL("c.com", "/title1.html"); // The following is a hack so we can get an IPC watcher connected to the // RenderProcessHost for C before the RenderView is created for it, and the // TextAutosizerPageInfo IPC is sent to it. scoped_refptr c_site = web_contents()->GetSiteInstance()->GetRelatedSiteInstance(c_url); // Force creation of a render process for c's SiteInstance, this will get // used when we dynamically create the new frame. auto* c_rph = static_cast(c_site->GetProcess()); ASSERT_TRUE(c_rph); ASSERT_NE(c_rph, root->current_frame_host()->GetProcess()); ASSERT_NE(c_rph, b_child->current_frame_host()->GetProcess()); // Create the subframe now. std::string create_frame_script = base::StringPrintf( "var new_iframe = document.createElement('iframe');" "new_iframe.src = '%s';" "document.body.appendChild(new_iframe);", c_url.spec().c_str()); EXPECT_TRUE(ExecuteScript(root, create_frame_script)); ASSERT_EQ(2U, root->child_count()); // Ensure IPC is sent. base::RunLoop().RunUntilIdle(); blink::mojom::TextAutosizerPageInfo page_info_sent_to_remote_main_frames = update_text_autosizer_info_observer.TextAutosizerPageInfo( web_contents() ->GetRenderManager() ->GetAllProxyHostsForTesting() .begin() ->second.get()); EXPECT_EQ(received_page_info.main_frame_width, page_info_sent_to_remote_main_frames.main_frame_width); EXPECT_EQ(received_page_info.main_frame_layout_width, page_info_sent_to_remote_main_frames.main_frame_layout_width); EXPECT_EQ(received_page_info.device_scale_adjustment, page_info_sent_to_remote_main_frames.device_scale_adjustment); } // Ensure that navigating subframes in --site-per-process mode works and the // correct documents are committed. IN_PROC_BROWSER_TEST_P(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 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 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 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_P(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(), JsReplace("window.location.href = $1", frame_url))); 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_P(SitePerProcessBrowserTest, CompositorViewportPixelSizeTest) { 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(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( nested_iframe_node->current_frame_host() ->GetRenderWidgetHost() ->GetView()); RenderFrameSubmissionObserver frame_observer(nested_iframe_node); frame_observer.WaitForMetadataChange(); // Verify that applying a CSS scale transform does not impact the size of the // content of the nested iframe. // The screen_space_rect_in_dip may be off by 1 due to rounding. There is no // good way to avoid this due to various device-scale-factor. (e.g. when // dsf=3.375, ceil(round(50 * 3.375) / 3.375) = 51. Thus, we allow the screen // size in dip to be off by 1 here. EXPECT_NEAR(50, connector->screen_space_rect_in_dip().size().width(), 1); EXPECT_NEAR(50, connector->screen_space_rect_in_dip().size().height(), 1); 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->GetCompositorViewportPixelSize()); } // Verify an OOPIF resize handler doesn't fire immediately after load without // the frame having been resized. See https://crbug.com/826457. IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, NoResizeAfterIframeLoad) { GURL main_url(embedded_test_server()->GetURL( "a.com", "/cross_site_iframe_factory.html?a(a)")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); FrameTreeNode* root = static_cast(shell()->web_contents()) ->GetFrameTree() ->root(); FrameTreeNode* iframe = root->child_at(0); GURL site_url = embedded_test_server()->GetURL("b.com", "/page_with_resize_handler.html"); NavigateFrameToURL(iframe, site_url); base::RunLoop().RunUntilIdle(); int resizes = -1; EXPECT_TRUE(ExecuteScriptAndExtractInt( iframe->current_frame_host(), "window.domAutomationController.send(resize_count);", &resizes)); // Should be zero because the iframe only has its initial size from parent. EXPECT_EQ(resizes, 0); } // 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_P(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(shell()->web_contents()) ->GetFrameTree() ->root(); RenderWidgetHostViewBase* rwhv_root = static_cast( 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); RenderFrameSubmissionObserver frame_observer(shell()->web_contents()); 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( nested_iframe_node->current_frame_host() ->GetRenderWidgetHost() ->GetView()); WaitForHitTestData(nested_iframe_node->current_frame_host()); float scale_factor = frame_observer.LastRenderFrameMetadata().page_scale_factor; // 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(); auto filter = base::MakeRefCounted(); 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::Type::kMouseWheel, blink::WebInputEvent::kNoModifiers, blink::WebInputEvent::GetStaticTimeStampForTests()); scroll_event.SetPositionInWidget( std::floor((bounds.x() - rwhv_root->GetViewBounds().x() - 5) * scale_factor), std::floor((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_P(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(shell()->web_contents()) ->GetFrameTree() ->root(); ASSERT_EQ(1U, root->child_count()); FrameTreeNode* child_iframe_node = root->child_at(0); RenderWidgetHost* child_rwh = child_iframe_node->current_frame_host()->GetRenderWidgetHost(); // The fling start won't bubble since its corresponding GSB hasn't bubbled. InputEventAckWaiter gesture_fling_start_ack_observer( child_rwh, blink::WebInputEvent::Type::kGestureFlingStart); WaitForHitTestData(child_iframe_node->current_frame_host()); gesture_fling_start_ack_observer.Reset(); GenerateTapDownGesture(child_rwh); // Send a GSB, GSU, GFS sequence and verify that the GFS bubbles. blink::WebGestureEvent gesture_scroll_begin( blink::WebGestureEvent::Type::kGestureScrollBegin, blink::WebInputEvent::kNoModifiers, blink::WebInputEvent::GetStaticTimeStampForTests(), blink::WebGestureDevice::kTouchscreen); gesture_scroll_begin.data.scroll_begin.delta_hint_units = ui::ScrollGranularity::kScrollByPrecisePixel; 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::Type::kGestureScrollUpdate, blink::WebInputEvent::kNoModifiers, blink::WebInputEvent::GetStaticTimeStampForTests(), blink::WebGestureDevice::kTouchscreen); gesture_scroll_update.data.scroll_update.delta_units = ui::ScrollGranularity::kScrollByPrecisePixel; 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::Type::kGestureFlingStart, blink::WebInputEvent::kNoModifiers, blink::WebInputEvent::GetStaticTimeStampForTests(), blink::WebGestureDevice::kTouchscreen); 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(); } // Test that scrolling a nested out-of-process iframe bubbles unused scroll // delta to a parent frame. IN_PROC_BROWSER_TEST_P(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(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. // SynchronizeVisualPropertiesMessageFilter catches updates to the position in // order to avoid busy waiting. It gets created early to catch the initial // rects from the navigation. auto filter = base::MakeRefCounted(); parent_iframe_node->current_frame_host()->GetProcess()->AddFilter( filter.get()); GURL site_url(embedded_test_server()->GetURL( "b.com", "/frame_tree/page_with_positioned_frame.html")); NavigateFrameToURL(parent_iframe_node, site_url); InputEventAckWaiter ack_observer( parent_iframe_node->current_frame_host()->GetRenderWidgetHost(), blink::WebInputEvent::Type::kGestureScrollEnd); // 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( parent_iframe_node->current_frame_host() ->GetRenderWidgetHost() ->GetView()); RenderWidgetHostViewBase* rwhv_nested = static_cast( nested_iframe_node->current_frame_host() ->GetRenderWidgetHost() ->GetView()); WaitForHitTestData(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::Type::kMouseWheel, blink::WebInputEvent::kNoModifiers, blink::WebInputEvent::GetStaticTimeStampForTests()); scroll_event.SetPositionInWidget(1, 1); // Use precise pixels 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.delta_units = ui::ScrollGranularity::kScrollByPrecisePixel; scroll_event.delta_x = 0.0f; scroll_event.delta_y = -5.0f; scroll_event.phase = blink::WebMouseWheelEvent::kPhaseBegan; rwhv_parent->ProcessMouseWheelEvent(scroll_event, ui::LatencyInfo()); // 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(); } // 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()); // 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::Type::kGestureScrollBegin, blink::WebInputEvent::kNoModifiers, blink::WebInputEvent::GetStaticTimeStampForTests(), blink::WebGestureDevice::kTouchpad); gesture_event.SetPositionInWidget(gfx::PointF(1, 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::Type::kGestureScrollUpdate, blink::WebInputEvent::kNoModifiers, blink::WebInputEvent::GetStaticTimeStampForTests(), blink::WebGestureDevice::kTouchpad); gesture_event.SetPositionInWidget(gfx::PointF(1, 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::Type::kGestureScrollEnd, blink::WebInputEvent::kNoModifiers, blink::WebInputEvent::GetStaticTimeStampForTests(), blink::WebGestureDevice::kTouchpad); gesture_event.SetPositionInWidget(gfx::PointF(1, 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 scrolling with the keyboard will bubble unused scroll to the // OOPIF's parent. IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, KeyboardScrollBubblingFromOOPIF) { GURL main_url(embedded_test_server()->GetURL( "a.com", "/frame_tree/page_with_iframe_in_scrollable_div.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(shell()->web_contents()) ->GetFrameTree() ->root(); ASSERT_EQ(1U, root->child_count()); FrameTreeNode* iframe_node = root->child_at(0); 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)); RenderWidgetHostViewBase* rwhv_child = static_cast( iframe_node->current_frame_host()->GetRenderWidgetHost()->GetView()); // This test does not involve hit testing, but input events could be dropped // by the renderer before the first compositor commit, so we wait here anyway // to avoid that. WaitForHitTestData(iframe_node->current_frame_host()); double initial_y = 0.0; ASSERT_TRUE(content::ExecuteScriptAndExtractDouble( root, "var wrapperDiv = document.getElementById('wrapper-div');" "var initial_y = wrapperDiv.scrollTop;" "var waitForScrollDownPromise = new Promise(function(resolve) {" " wrapperDiv.addEventListener('scroll', () => {" " if (wrapperDiv.scrollTop > initial_y)" " resolve(wrapperDiv.scrollTop);" " });" "});" "window.domAutomationController.send(initial_y);", &initial_y)); EXPECT_DOUBLE_EQ(0.0, initial_y); NativeWebKeyboardEvent key_event( blink::WebKeyboardEvent::Type::kRawKeyDown, blink::WebInputEvent::kNoModifiers, blink::WebInputEvent::GetStaticTimeStampForTests()); key_event.windows_key_code = ui::VKEY_DOWN; key_event.native_key_code = ui::KeycodeConverter::DomCodeToNativeKeycode(ui::DomCode::ARROW_DOWN); key_event.dom_code = static_cast(ui::DomCode::ARROW_DOWN); key_event.dom_key = ui::DomKey::ARROW_DOWN; rwhv_child->GetRenderWidgetHost()->ForwardKeyboardEvent(key_event); key_event.SetType(blink::WebKeyboardEvent::Type::kKeyUp); rwhv_child->GetRenderWidgetHost()->ForwardKeyboardEvent(key_event); double scrolled_y = 0.0; ASSERT_TRUE(content::ExecuteScriptAndExtractDouble( root, "waitForScrollDownPromise.then((scrolled_y) => {" " window.domAutomationController.send(scrolled_y);" "});", &scrolled_y)); EXPECT_GT(scrolled_y, 0.0); } // Test that fling on an out-of-process iframe progresses properly. IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, TouchscreenGestureFlingStart) { 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(shell()->web_contents()) ->GetFrameTree() ->root(); ASSERT_EQ(1U, root->child_count()); FrameTreeNode* child_iframe_node = root->child_at(0); RenderWidgetHost* child_rwh = child_iframe_node->current_frame_host()->GetRenderWidgetHost(); WaitForHitTestData(child_iframe_node->current_frame_host()); GenerateTapDownGesture(child_rwh); // Send a GSB to start scrolling sequence. blink::WebGestureEvent gesture_scroll_begin( blink::WebGestureEvent::Type::kGestureScrollBegin, blink::WebInputEvent::kNoModifiers, blink::WebInputEvent::GetStaticTimeStampForTests()); gesture_scroll_begin.SetSourceDevice(blink::WebGestureDevice::kTouchscreen); gesture_scroll_begin.data.scroll_begin.delta_hint_units = ui::ScrollGranularity::kScrollByPrecisePixel; 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); // Send a GFS and wait for the ack of the first GSU generated from progressing // the fling on the browser. InputEventAckWaiter gesture_scroll_update_ack_observer( child_rwh, blink::WebInputEvent::Type::kGestureScrollUpdate); gesture_scroll_update_ack_observer.Reset(); blink::WebGestureEvent gesture_fling_start( blink::WebGestureEvent::Type::kGestureFlingStart, blink::WebInputEvent::kNoModifiers, blink::WebInputEvent::GetStaticTimeStampForTests()); gesture_fling_start.SetSourceDevice(blink::WebGestureDevice::kTouchscreen); gesture_fling_start.data.fling_start.velocity_x = 0.f; gesture_fling_start.data.fling_start.velocity_y = 50.f; child_rwh->ForwardGestureEvent(gesture_fling_start); gesture_scroll_update_ack_observer.Wait(); } // Test that fling on an out-of-process iframe progresses properly. IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, TouchpadGestureFlingStart) { 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(shell()->web_contents()) ->GetFrameTree() ->root(); ASSERT_EQ(1U, root->child_count()); FrameTreeNode* child_iframe_node = root->child_at(0); RenderWidgetHost* child_rwh = child_iframe_node->current_frame_host()->GetRenderWidgetHost(); // Send a wheel event with phaseBegan to start scrolling sequence. InputEventAckWaiter gesture_scroll_begin_ack_observer( child_rwh, blink::WebInputEvent::Type::kGestureScrollBegin); blink::WebMouseWheelEvent scroll_event( blink::WebInputEvent::Type::kMouseWheel, blink::WebInputEvent::kNoModifiers, blink::WebInputEvent::GetStaticTimeStampForTests()); scroll_event.delta_units = ui::ScrollGranularity::kScrollByPrecisePixel; scroll_event.delta_x = 0.0f; scroll_event.delta_y = 5.0f; scroll_event.phase = blink::WebMouseWheelEvent::kPhaseBegan; child_rwh->ForwardWheelEvent(scroll_event); gesture_scroll_begin_ack_observer.Wait(); // Send a GFS and wait for the ack of the first GSU generated from progressing // the fling on the browser. InputEventAckWaiter gesture_scroll_update_ack_observer( child_rwh, blink::WebInputEvent::Type::kGestureScrollUpdate); gesture_scroll_update_ack_observer.Reset(); blink::WebGestureEvent gesture_fling_start( blink::WebGestureEvent::Type::kGestureFlingStart, blink::WebInputEvent::kNoModifiers, blink::WebInputEvent::GetStaticTimeStampForTests()); gesture_fling_start.SetSourceDevice(blink::WebGestureDevice::kTouchpad); gesture_fling_start.data.fling_start.velocity_x = 0.f; gesture_fling_start.data.fling_start.velocity_y = 50.f; child_rwh->ForwardGestureEvent(gesture_fling_start); // The test will pass when the GSU ack arrives, since it shows that the fling // controller has properly generated a GSU event from progressing the fling. gesture_scroll_update_ack_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::Type::kGestureScrollUpdate) { blink::WebGestureEvent received_update = *static_cast(&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::Type::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 message_loop_runner_; double remaining_delta_x_; double remaining_delta_y_; bool scroll_end_received_; DISALLOW_COPY_AND_ASSIGN(ScrollObserver); }; // Android: crbug.com/825629 // NDEBUG: crbug.com/1063045 #if defined(OS_ANDROID) || defined(NDEBUG) #define MAYBE_ScrollBubblingFromNestedOOPIFTest \ DISABLED_ScrollBubblingFromNestedOOPIFTest #else #define MAYBE_ScrollBubblingFromNestedOOPIFTest \ ScrollBubblingFromNestedOOPIFTest #endif IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, MAYBE_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)); RenderFrameSubmissionObserver frame_observer(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* 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( root->current_frame_host()->GetRenderWidgetHost()->GetView()); RenderWidgetHostViewBase* rwhv_nested = static_cast( nested_iframe_node->current_frame_host() ->GetRenderWidgetHost() ->GetView()); WaitForHitTestData(nested_iframe_node->current_frame_host()); InputEventAckWaiter ack_observer( root->current_frame_host()->GetRenderWidgetHost(), blink::WebInputEvent::Type::kGestureScrollBegin); std::unique_ptr scroll_observer; // 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(0, 15); 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::Type::kMouseWheel, blink::WebInputEvent::kNoModifiers, blink::WebInputEvent::GetStaticTimeStampForTests()); gfx::Rect bounds = rwhv_nested->GetViewBounds(); float scale_factor = frame_observer.LastRenderFrameMetadata().page_scale_factor; scroll_event.SetPositionInWidget( std::ceil((bounds.x() - root_view->GetViewBounds().x() + 10) * scale_factor), std::ceil((bounds.y() - root_view->GetViewBounds().y() + 10) * scale_factor)); scroll_event.delta_units = ui::ScrollGranularity::kScrollByPrecisePixel; scroll_event.delta_x = 0.0f; scroll_event.delta_y = 5.0f; scroll_event.phase = blink::WebMouseWheelEvent::kPhaseBegan; rwhv_nested->ProcessMouseWheelEvent(scroll_event, ui::LatencyInfo()); ack_observer.Wait(); // Send 10 wheel events with delta_y = 1 to the nested oopif. scroll_event.delta_y = 1.0f; scroll_event.phase = blink::WebMouseWheelEvent::kPhaseChanged; for (int i = 0; i < 10; i++) rwhv_nested->ProcessMouseWheelEvent(scroll_event, ui::LatencyInfo()); // Send a wheel end event to complete the scrolling sequence. scroll_event.delta_y = 0.0f; scroll_event.phase = blink::WebMouseWheelEvent::kPhaseEnded; rwhv_nested->ProcessMouseWheelEvent(scroll_event, ui::LatencyInfo()); scroll_observer->Wait(); } // Tests that scrolling bubbles from an oopif if its source body has // "overflow:hidden" style. IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, ScrollBubblingFromOOPIFWithBodyOverflowHidden) { GURL url_domain_a(embedded_test_server()->GetURL( "a.com", "/scrollable_page_with_iframe.html")); EXPECT_TRUE(NavigateToURL(shell(), url_domain_a)); RenderFrameSubmissionObserver frame_observer(shell()->web_contents()); FrameTreeNode* root = web_contents()->GetFrameTree()->root(); FrameTreeNode* iframe_node = root->child_at(0); GURL url_domain_b( embedded_test_server()->GetURL("b.com", "/body_overflow_hidden.html")); NavigateFrameToURL(iframe_node, url_domain_b); WaitForHitTestData(iframe_node->current_frame_host()); RenderWidgetHostViewBase* root_view = static_cast( root->current_frame_host()->GetRenderWidgetHost()->GetView()); RenderWidgetHostViewBase* child_view = static_cast( iframe_node->current_frame_host()->GetRenderWidgetHost()->GetView()); ScrollObserver scroll_observer(0, -5); root->current_frame_host()->GetRenderWidgetHost()->AddInputEventObserver( &scroll_observer); // Now scroll the nested frame downward, this must bubble to the root since // the iframe source body is not scrollable. blink::WebMouseWheelEvent scroll_event( blink::WebInputEvent::Type::kMouseWheel, blink::WebInputEvent::kNoModifiers, blink::WebInputEvent::GetStaticTimeStampForTests()); gfx::Rect bounds = child_view->GetViewBounds(); float scale_factor = frame_observer.LastRenderFrameMetadata().page_scale_factor; scroll_event.SetPositionInWidget( std::ceil((bounds.x() - root_view->GetViewBounds().x() + 10) * scale_factor), std::ceil((bounds.y() - root_view->GetViewBounds().y() + 10) * scale_factor)); scroll_event.delta_units = ui::ScrollGranularity::kScrollByPrecisePixel; scroll_event.delta_x = 0.0f; scroll_event.delta_y = -5.0f; scroll_event.phase = blink::WebMouseWheelEvent::kPhaseBegan; child_view->ProcessMouseWheelEvent(scroll_event, ui::LatencyInfo()); // Send a wheel end event to complete the scrolling sequence. scroll_event.delta_y = 0.0f; scroll_event.phase = blink::WebMouseWheelEvent::kPhaseEnded; child_view->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_P(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( nested_iframe_node->current_frame_host() ->GetRenderWidgetHost() ->GetView()); WaitForHitTestData(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([](blink::mojom::InputEventResultSource, blink::mojom::InputEventResultState state, const blink::WebInputEvent& event) { return event.GetType() == blink::WebGestureEvent::Type::kGestureScrollBegin && state == blink::mojom::InputEventResultState::kConsumed; })); // 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::Type::kMouseWheel, blink::WebInputEvent::kNoModifiers, blink::WebInputEvent::GetStaticTimeStampForTests()); scroll_event.SetPositionInWidget(90, 110); scroll_event.delta_units = ui::ScrollGranularity::kScrollByPrecisePixel; scroll_event.delta_x = 0.0f; scroll_event.delta_y = -50.0f; scroll_event.phase = blink::WebMouseWheelEvent::kPhaseBegan; rwhv_child->ProcessMouseWheelEvent(scroll_event, ui::LatencyInfo()); ack_observer.Wait(); } // This test verifies that scrolling an element to view works across OOPIFs. The // testing methodology is based on measuring bounding client rect position of // nested