| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <memory> |
| #include <vector> |
| |
| #include "base/containers/contains.h" |
| #include "base/functional/bind.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/run_loop.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/test/bind.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/test_timeouts.h" |
| #include "base/time/time.h" |
| #include "build/build_config.h" |
| #include "components/input/input_router_impl.h" |
| #include "components/input/render_widget_host_input_event_router.h" |
| #include "components/input/touch_action_filter.h" |
| #include "content/browser/permissions/permission_controller_impl.h" |
| #include "content/browser/renderer_host/input/touch_emulator_impl.h" |
| #include "content/browser/renderer_host/render_widget_host_impl.h" |
| #include "content/browser/renderer_host/render_widget_host_view_base.h" |
| #include "content/browser/web_contents/web_contents_impl.h" |
| #include "content/common/content_navigation_policy.h" |
| #include "content/common/input/synthetic_smooth_drag_gesture.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/render_widget_host_observer.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/browser/web_contents_observer.h" |
| #include "content/public/common/content_switches.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.h" |
| #include "content/public/test/content_browser_test_content_browser_client.h" |
| #include "content/public/test/content_browser_test_utils.h" |
| #include "content/public/test/test_utils.h" |
| #include "content/shell/browser/shell.h" |
| #include "content/test/content_browser_test_utils_internal.h" |
| #include "ipc/constants.mojom.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/common/input/synthetic_web_input_event_builders.h" |
| #include "third_party/blink/public/common/input/web_input_event.h" |
| #include "third_party/blink/public/common/input/web_mouse_event.h" |
| #include "third_party/blink/public/common/switches.h" |
| #include "ui/events/base_event_utils.h" |
| #include "ui/gfx/geometry/size.h" |
| #include "ui/gfx/switches.h" |
| #include "ui/latency/latency_info.h" |
| |
| #if BUILDFLAG(IS_MAC) |
| #include "third_party/blink/public/mojom/choosers/popup_menu.mojom.h" |
| #include "third_party/blink/public/mojom/frame/frame.mojom-test-utils.h" |
| #endif |
| |
| namespace content { |
| |
| namespace { |
| |
| // Test observer which waits for a visual properties update from a |
| // `RenderWidgetHost`. |
| class TestRenderWidgetHostObserver : public RenderWidgetHostObserver { |
| public: |
| explicit TestRenderWidgetHostObserver(RenderWidgetHost* widget_host) |
| : widget_host_(widget_host) { |
| widget_host_->AddObserver(this); |
| } |
| |
| ~TestRenderWidgetHostObserver() override { |
| widget_host_->RemoveObserver(this); |
| } |
| |
| // RenderWidgetHostObserver: |
| void RenderWidgetHostDidUpdateVisualProperties( |
| RenderWidgetHost* widget_host) override { |
| run_loop_.Quit(); |
| } |
| |
| void Wait() { run_loop_.Run(); } |
| |
| private: |
| raw_ptr<RenderWidgetHost> widget_host_ = nullptr; |
| base::RunLoop run_loop_; |
| }; |
| |
| } // namespace |
| |
| // For tests that just need a browser opened/navigated to a simple web page. |
| class RenderWidgetHostBrowserTest : public ContentBrowserTest { |
| public: |
| void SetUpOnMainThread() override { |
| ContentBrowserTest::SetUpOnMainThread(); |
| EXPECT_TRUE(NavigateToURL( |
| shell(), GURL("data:text/html,<!doctype html>" |
| "<body style='background-color: magenta;'></body>"))); |
| SimulateEndOfPaintHoldingOnPrimaryMainFrame(shell()->web_contents()); |
| } |
| |
| WebContents* web_contents() const { return shell()->web_contents(); } |
| RenderWidgetHostViewBase* view() const { |
| return static_cast<RenderWidgetHostViewBase*>( |
| web_contents()->GetRenderWidgetHostView()); |
| } |
| RenderWidgetHostImpl* host() const { |
| return static_cast<RenderWidgetHostImpl*>(view()->GetRenderWidgetHost()); |
| } |
| |
| void WaitForVisualPropertiesAck() { |
| while (host()->visual_properties_ack_pending_for_testing()) { |
| TestRenderWidgetHostObserver(host()).Wait(); |
| } |
| } |
| }; |
| |
| // This test enables --site-per-process flag. |
| class RenderWidgetHostSitePerProcessTest : public ContentBrowserTest { |
| public: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| IsolateAllSitesForTesting(command_line); |
| // Slow bots are flaky due to slower loading interacting with |
| // deferred commits. |
| command_line->AppendSwitch(blink::switches::kAllowPreCommitInput); |
| } |
| |
| void SetUpOnMainThread() override { |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| SetupCrossSiteRedirector(embedded_test_server()); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| } |
| |
| protected: |
| WebContentsImpl* web_contents() const { |
| return static_cast<WebContentsImpl*>(shell()->web_contents()); |
| } |
| |
| input::TouchActionFilter* GetTouchActionFilterForWidget( |
| RenderWidgetHostImpl* rwhi) { |
| return &static_cast<input::InputRouterImpl*>(rwhi->input_router()) |
| ->touch_action_filter_; |
| } |
| }; |
| |
| class TestInputEventObserver : public RenderWidgetHost::InputEventObserver { |
| public: |
| using EventTypeVector = std::vector<blink::WebInputEvent::Type>; |
| |
| ~TestInputEventObserver() override {} |
| |
| void OnInputEvent(const RenderWidgetHost& widget, |
| const blink::WebInputEvent& event) override { |
| dispatched_events_.push_back(event.GetType()); |
| } |
| |
| void OnInputEventAck(const RenderWidgetHost& widget, |
| blink::mojom::InputEventResultSource source, |
| blink::mojom::InputEventResultState state, |
| const blink::WebInputEvent& event) override { |
| if (blink::WebInputEvent::IsTouchEventType(event.GetType())) { |
| acked_touch_event_type_ = event.GetType(); |
| } |
| } |
| |
| EventTypeVector GetAndResetDispatchedEventTypes() { |
| EventTypeVector new_event_types; |
| std::swap(new_event_types, dispatched_events_); |
| return new_event_types; |
| } |
| |
| blink::WebInputEvent::Type acked_touch_event_type() const { |
| return acked_touch_event_type_; |
| } |
| |
| private: |
| EventTypeVector dispatched_events_; |
| blink::WebInputEvent::Type acked_touch_event_type_ = |
| blink::WebInputEvent::Type::kUndefined; |
| }; |
| |
| class RenderWidgetHostTouchEmulatorBrowserTest : public ContentBrowserTest { |
| public: |
| RenderWidgetHostTouchEmulatorBrowserTest() |
| : last_simulated_event_time_(ui::EventTimeForNow()), |
| simulated_event_time_delta_(base::Milliseconds(100)) {} |
| |
| void SetUpOnMainThread() override { |
| ContentBrowserTest::SetUpOnMainThread(); |
| |
| EXPECT_TRUE(NavigateToURL( |
| shell(), GURL("data:text/html,<!doctype html>" |
| "<body style='background-color: red;'></body>"))); |
| SimulateEndOfPaintHoldingOnPrimaryMainFrame(shell()->web_contents()); |
| } |
| |
| base::TimeTicks GetNextSimulatedEventTime() { |
| last_simulated_event_time_ += simulated_event_time_delta_; |
| return last_simulated_event_time_; |
| } |
| |
| void SimulateRoutedMouseEvent(blink::WebInputEvent::Type type, |
| int x, |
| int y, |
| int modifiers, |
| bool pressed) { |
| blink::WebMouseEvent event = |
| blink::SyntheticWebMouseEventBuilder::Build(type, x, y, modifiers); |
| if (pressed) { |
| event.button = blink::WebMouseEvent::Button::kLeft; |
| } |
| event.SetTimeStamp(GetNextSimulatedEventTime()); |
| input::RenderWidgetHostInputEventRouter* router = |
| static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetInputEventRouter(); |
| ASSERT_TRUE(router); |
| router->RouteMouseEvent(view(), &event, ui::LatencyInfo()); |
| } |
| |
| void WaitForAckWith(blink::WebInputEvent::Type type) { |
| InputMsgWatcher watcher(host(), type); |
| watcher.GetAckStateWaitIfNecessary(); |
| } |
| |
| RenderWidgetHostImpl* host() { |
| return static_cast<RenderWidgetHostImpl*>(view()->GetRenderWidgetHost()); |
| } |
| |
| RenderWidgetHostViewBase* view() { |
| return static_cast<RenderWidgetHostViewBase*>( |
| shell()->web_contents()->GetRenderWidgetHostView()); |
| } |
| |
| private: |
| base::TimeTicks last_simulated_event_time_; |
| const base::TimeDelta simulated_event_time_delta_; |
| }; |
| |
| // Synthetic mouse events not allowed on Android. |
| #if !BUILDFLAG(IS_ANDROID) |
| // This test makes sure that TouchEmulator doesn't emit a GestureScrollEnd |
| // without a valid unique_touch_event_id when it sees a GestureFlingStart |
| // terminating the underlying mouse scroll sequence. If the GestureScrollEnd is |
| // given a unique_touch_event_id of 0, then a crash will occur. |
| // TODO(crbug.com/404887525): Test randomly times out due to not receiving the |
| // InputEventAckWaiter kGestureScrollEnd event. |
| IN_PROC_BROWSER_TEST_F(RenderWidgetHostTouchEmulatorBrowserTest, |
| DISABLED_TouchEmulatorPinchWithGestureFling) { |
| auto* touch_emulator = host()->GetTouchEmulator(/*create_if_necessary=*/true); |
| touch_emulator->Enable(input::TouchEmulator::Mode::kEmulatingTouchFromMouse, |
| ui::GestureProviderConfigType::GENERIC_MOBILE); |
| touch_emulator->SetPinchGestureModeForTesting(true); |
| |
| TestInputEventObserver observer; |
| host()->AddInputEventObserver(&observer); |
| |
| SyntheticSmoothDragGestureParams params; |
| params.start_point = gfx::PointF(10.f, 110.f); |
| params.gesture_source_type = content::mojom::GestureSourceType::kMouseInput; |
| params.distances.push_back(gfx::Vector2d(0, -10)); |
| params.distances.push_back(gfx::Vector2d(0, -10)); |
| params.distances.push_back(gfx::Vector2d(0, -10)); |
| params.distances.push_back(gfx::Vector2d(0, -10)); |
| params.speed_in_pixels_s = 1200; |
| |
| // On slow bots (e.g. ChromeOS DBG) the synthetic gesture sequence events may |
| // be delivered slowly/erratically-timed so that the velocity_tracker in the |
| // TouchEmulator's GestureDetector may either (i) drop some scroll updates |
| // from the velocity estimate, or (ii) create an unexpectedly low velocity |
| // estimate. In either case, the minimum fling start velocity may not be |
| // achieved, meaning the condition we're trying to test never occurs. To |
| // avoid that, we'll keep trying until it happens. The failure mode for the |
| // test is that it times out. |
| do { |
| std::unique_ptr<SyntheticSmoothDragGesture> gesture( |
| new SyntheticSmoothDragGesture(params)); |
| |
| InputEventAckWaiter scroll_end_ack_waiter( |
| host(), blink::WebInputEvent::Type::kGestureScrollEnd); |
| base::RunLoop run_loop; |
| host()->QueueSyntheticGesture( |
| std::move(gesture), |
| base::BindOnce( |
| base::BindLambdaForTesting([&](SyntheticGesture::Result result) { |
| EXPECT_EQ(SyntheticGesture::GESTURE_FINISHED, result); |
| run_loop.Quit(); |
| }))); |
| run_loop.Run(); |
| scroll_end_ack_waiter.Wait(); |
| |
| // Verify that a GestureFlingStart was suppressed by the TouchEmulator, and |
| // that we generated a GestureScrollEnd and routed it without crashing. |
| TestInputEventObserver::EventTypeVector dispatched_events = |
| observer.GetAndResetDispatchedEventTypes(); |
| EXPECT_TRUE(base::Contains(dispatched_events, |
| blink::WebInputEvent::Type::kGestureScrollEnd)); |
| } while (!touch_emulator->suppress_next_fling_cancel_for_testing()); |
| } |
| #endif // !BUILDFLAG(IS_ANDROID) |
| |
| // Todo(crbug.com/994353): The test is flaky(crash/timeout) on MSAN, TSAN, and |
| // DEBUG builds. |
| #if (!defined(NDEBUG) || defined(THREAD_SANITIZER) || defined(MEMORY_SANITIZER)) |
| #define MAYBE_TouchEmulator DISABLED_TouchEmulator |
| #else |
| #define MAYBE_TouchEmulator TouchEmulator |
| #endif |
| IN_PROC_BROWSER_TEST_F(RenderWidgetHostTouchEmulatorBrowserTest, |
| MAYBE_TouchEmulator) { |
| host() |
| ->GetTouchEmulator(/*create_if_necessary=*/true) |
| ->Enable(input::TouchEmulator::Mode::kEmulatingTouchFromMouse, |
| ui::GestureProviderConfigType::GENERIC_MOBILE); |
| |
| TestInputEventObserver observer; |
| host()->AddInputEventObserver(&observer); |
| |
| // Simulate a mouse move without any pressed buttons. This should not |
| // generate any touch events. |
| SimulateRoutedMouseEvent(blink::WebInputEvent::Type::kMouseMove, 10, 120, 0, |
| false); |
| TestInputEventObserver::EventTypeVector dispatched_events = |
| observer.GetAndResetDispatchedEventTypes(); |
| EXPECT_EQ(0u, dispatched_events.size()); |
| |
| // Mouse press becomes touch start which in turn becomes tap. |
| SimulateRoutedMouseEvent(blink::WebInputEvent::Type::kMouseDown, 10, 120, 0, |
| true); |
| WaitForAckWith(blink::WebInputEvent::Type::kTouchStart); |
| EXPECT_EQ(blink::WebInputEvent::Type::kTouchStart, |
| observer.acked_touch_event_type()); |
| dispatched_events = observer.GetAndResetDispatchedEventTypes(); |
| ASSERT_EQ(2u, dispatched_events.size()); |
| EXPECT_EQ(blink::WebInputEvent::Type::kTouchStart, dispatched_events[0]); |
| EXPECT_EQ(blink::WebInputEvent::Type::kGestureTapDown, dispatched_events[1]); |
| |
| // Mouse drag generates touch move, cancels tap and starts scroll. |
| SimulateRoutedMouseEvent(blink::WebInputEvent::Type::kMouseMove, 10, 100, 0, |
| true); |
| dispatched_events = observer.GetAndResetDispatchedEventTypes(); |
| ASSERT_EQ(5u, dispatched_events.size()); |
| EXPECT_EQ(blink::WebInputEvent::Type::kTouchMove, dispatched_events[0]); |
| EXPECT_EQ(blink::WebInputEvent::Type::kGestureTapCancel, |
| dispatched_events[1]); |
| EXPECT_EQ(blink::WebInputEvent::Type::kGestureScrollBegin, |
| dispatched_events[2]); |
| EXPECT_EQ(blink::WebInputEvent::Type::kTouchScrollStarted, |
| dispatched_events[3]); |
| EXPECT_EQ(blink::WebInputEvent::Type::kGestureScrollUpdate, |
| dispatched_events[4]); |
| EXPECT_EQ(blink::WebInputEvent::Type::kTouchMove, |
| observer.acked_touch_event_type()); |
| EXPECT_EQ(0u, observer.GetAndResetDispatchedEventTypes().size()); |
| |
| // Mouse drag with shift becomes pinch. |
| SimulateRoutedMouseEvent(blink::WebInputEvent::Type::kMouseMove, 10, 95, |
| blink::WebInputEvent::kShiftKey, true); |
| EXPECT_EQ(blink::WebInputEvent::Type::kTouchMove, |
| observer.acked_touch_event_type()); |
| |
| dispatched_events = observer.GetAndResetDispatchedEventTypes(); |
| ASSERT_EQ(2u, dispatched_events.size()); |
| EXPECT_EQ(blink::WebInputEvent::Type::kTouchMove, dispatched_events[0]); |
| EXPECT_EQ(blink::WebInputEvent::Type::kGesturePinchBegin, |
| dispatched_events[1]); |
| |
| SimulateRoutedMouseEvent(blink::WebInputEvent::Type::kMouseMove, 10, 80, |
| blink::WebInputEvent::kShiftKey, true); |
| EXPECT_EQ(blink::WebInputEvent::Type::kTouchMove, |
| observer.acked_touch_event_type()); |
| |
| dispatched_events = observer.GetAndResetDispatchedEventTypes(); |
| ASSERT_EQ(2u, dispatched_events.size()); |
| EXPECT_EQ(blink::WebInputEvent::Type::kTouchMove, dispatched_events[0]); |
| EXPECT_EQ(blink::WebInputEvent::Type::kGesturePinchUpdate, |
| dispatched_events[1]); |
| |
| // Mouse drag without shift becomes scroll again. |
| SimulateRoutedMouseEvent(blink::WebInputEvent::Type::kMouseMove, 10, 70, 0, |
| true); |
| EXPECT_EQ(blink::WebInputEvent::Type::kTouchMove, |
| observer.acked_touch_event_type()); |
| |
| dispatched_events = observer.GetAndResetDispatchedEventTypes(); |
| ASSERT_EQ(3u, dispatched_events.size()); |
| EXPECT_EQ(blink::WebInputEvent::Type::kTouchMove, dispatched_events[0]); |
| EXPECT_EQ(blink::WebInputEvent::Type::kGesturePinchEnd, dispatched_events[1]); |
| EXPECT_EQ(blink::WebInputEvent::Type::kGestureScrollUpdate, |
| dispatched_events[2]); |
| |
| SimulateRoutedMouseEvent(blink::WebInputEvent::Type::kMouseMove, 10, 60, 0, |
| true); |
| EXPECT_EQ(blink::WebInputEvent::Type::kTouchMove, |
| observer.acked_touch_event_type()); |
| dispatched_events = observer.GetAndResetDispatchedEventTypes(); |
| ASSERT_EQ(2u, dispatched_events.size()); |
| EXPECT_EQ(blink::WebInputEvent::Type::kTouchMove, dispatched_events[0]); |
| EXPECT_EQ(blink::WebInputEvent::Type::kGestureScrollUpdate, |
| dispatched_events[1]); |
| |
| SimulateRoutedMouseEvent(blink::WebInputEvent::Type::kMouseUp, 10, 60, 0, |
| true); |
| WaitForAckWith(blink::WebInputEvent::Type::kTouchEnd); |
| EXPECT_EQ(blink::WebInputEvent::Type::kTouchEnd, |
| observer.acked_touch_event_type()); |
| dispatched_events = observer.GetAndResetDispatchedEventTypes(); |
| ASSERT_EQ(2u, dispatched_events.size()); |
| EXPECT_EQ(blink::WebInputEvent::Type::kTouchEnd, dispatched_events[0]); |
| EXPECT_EQ(blink::WebInputEvent::Type::kGestureScrollEnd, |
| dispatched_events[1]); |
| |
| // Mouse move does nothing. |
| SimulateRoutedMouseEvent(blink::WebInputEvent::Type::kMouseMove, 10, 50, 0, |
| false); |
| dispatched_events = observer.GetAndResetDispatchedEventTypes(); |
| EXPECT_EQ(0u, dispatched_events.size()); |
| |
| // Another mouse down continues scroll. |
| SimulateRoutedMouseEvent(blink::WebInputEvent::Type::kMouseDown, 10, 50, 0, |
| true); |
| WaitForAckWith(blink::WebInputEvent::Type::kTouchStart); |
| EXPECT_EQ(blink::WebInputEvent::Type::kTouchStart, |
| observer.acked_touch_event_type()); |
| dispatched_events = observer.GetAndResetDispatchedEventTypes(); |
| ASSERT_EQ(2u, dispatched_events.size()); |
| EXPECT_EQ(blink::WebInputEvent::Type::kTouchStart, dispatched_events[0]); |
| EXPECT_EQ(blink::WebInputEvent::Type::kGestureTapDown, dispatched_events[1]); |
| SimulateRoutedMouseEvent(blink::WebInputEvent::Type::kMouseMove, 10, 30, 0, |
| true); |
| EXPECT_EQ(blink::WebInputEvent::Type::kTouchMove, |
| observer.acked_touch_event_type()); |
| dispatched_events = observer.GetAndResetDispatchedEventTypes(); |
| ASSERT_EQ(5u, dispatched_events.size()); |
| EXPECT_EQ(blink::WebInputEvent::Type::kTouchMove, dispatched_events[0]); |
| EXPECT_EQ(blink::WebInputEvent::Type::kGestureTapCancel, |
| dispatched_events[1]); |
| EXPECT_EQ(blink::WebInputEvent::Type::kGestureScrollBegin, |
| dispatched_events[2]); |
| EXPECT_EQ(blink::WebInputEvent::Type::kTouchScrollStarted, |
| dispatched_events[3]); |
| EXPECT_EQ(blink::WebInputEvent::Type::kGestureScrollUpdate, |
| dispatched_events[4]); |
| EXPECT_EQ(0u, observer.GetAndResetDispatchedEventTypes().size()); |
| |
| // Another pinch. |
| SimulateRoutedMouseEvent(blink::WebInputEvent::Type::kMouseMove, 10, 20, |
| blink::WebInputEvent::kShiftKey, true); |
| EXPECT_EQ(blink::WebInputEvent::Type::kTouchMove, |
| observer.acked_touch_event_type()); |
| dispatched_events = observer.GetAndResetDispatchedEventTypes(); |
| EXPECT_EQ(2u, dispatched_events.size()); |
| EXPECT_EQ(blink::WebInputEvent::Type::kTouchMove, dispatched_events[0]); |
| EXPECT_EQ(blink::WebInputEvent::Type::kGesturePinchBegin, |
| dispatched_events[1]); |
| SimulateRoutedMouseEvent(blink::WebInputEvent::Type::kMouseMove, 10, 10, |
| blink::WebInputEvent::kShiftKey, true); |
| EXPECT_EQ(blink::WebInputEvent::Type::kTouchMove, |
| observer.acked_touch_event_type()); |
| dispatched_events = observer.GetAndResetDispatchedEventTypes(); |
| EXPECT_EQ(2u, dispatched_events.size()); |
| EXPECT_EQ(blink::WebInputEvent::Type::kTouchMove, dispatched_events[0]); |
| EXPECT_EQ(blink::WebInputEvent::Type::kGesturePinchUpdate, |
| dispatched_events[1]); |
| |
| // Turn off emulation during a pinch. |
| host()->GetTouchEmulator(/*create_if_necessary=*/true)->Disable(); |
| EXPECT_EQ(blink::WebInputEvent::Type::kTouchCancel, |
| observer.acked_touch_event_type()); |
| dispatched_events = observer.GetAndResetDispatchedEventTypes(); |
| ASSERT_EQ(3u, dispatched_events.size()); |
| EXPECT_EQ(blink::WebInputEvent::Type::kTouchCancel, dispatched_events[0]); |
| EXPECT_EQ(blink::WebInputEvent::Type::kGesturePinchEnd, dispatched_events[1]); |
| EXPECT_EQ(blink::WebInputEvent::Type::kGestureScrollEnd, |
| dispatched_events[2]); |
| |
| // Mouse event should pass untouched. |
| SimulateRoutedMouseEvent(blink::WebInputEvent::Type::kMouseMove, 10, 120, |
| blink::WebInputEvent::kShiftKey, true); |
| dispatched_events = observer.GetAndResetDispatchedEventTypes(); |
| ASSERT_EQ(1u, dispatched_events.size()); |
| EXPECT_EQ(blink::WebInputEvent::Type::kMouseMove, dispatched_events[0]); |
| |
| // Turn on emulation. |
| host() |
| ->GetTouchEmulator(/*create_if_necessary=*/true) |
| ->Enable(input::TouchEmulator::Mode::kEmulatingTouchFromMouse, |
| ui::GestureProviderConfigType::GENERIC_MOBILE); |
| |
| // Another touch. |
| SimulateRoutedMouseEvent(blink::WebInputEvent::Type::kMouseDown, 10, 120, 0, |
| true); |
| WaitForAckWith(blink::WebInputEvent::Type::kTouchStart); |
| EXPECT_EQ(blink::WebInputEvent::Type::kTouchStart, |
| observer.acked_touch_event_type()); |
| dispatched_events = observer.GetAndResetDispatchedEventTypes(); |
| ASSERT_EQ(2u, dispatched_events.size()); |
| EXPECT_EQ(blink::WebInputEvent::Type::kTouchStart, dispatched_events[0]); |
| EXPECT_EQ(blink::WebInputEvent::Type::kGestureTapDown, dispatched_events[1]); |
| |
| // Scroll. |
| SimulateRoutedMouseEvent(blink::WebInputEvent::Type::kMouseMove, 10, 100, 0, |
| true); |
| EXPECT_EQ(blink::WebInputEvent::Type::kTouchMove, |
| observer.acked_touch_event_type()); |
| dispatched_events = observer.GetAndResetDispatchedEventTypes(); |
| ASSERT_EQ(5u, dispatched_events.size()); |
| EXPECT_EQ(blink::WebInputEvent::Type::kTouchMove, dispatched_events[0]); |
| EXPECT_EQ(blink::WebInputEvent::Type::kGestureTapCancel, |
| dispatched_events[1]); |
| EXPECT_EQ(blink::WebInputEvent::Type::kGestureScrollBegin, |
| dispatched_events[2]); |
| EXPECT_EQ(blink::WebInputEvent::Type::kTouchScrollStarted, |
| dispatched_events[3]); |
| EXPECT_EQ(blink::WebInputEvent::Type::kGestureScrollUpdate, |
| dispatched_events[4]); |
| EXPECT_EQ(0u, observer.GetAndResetDispatchedEventTypes().size()); |
| |
| // Turn off emulation during a scroll. |
| host()->GetTouchEmulator(/*create_if_necessary=*/true)->Disable(); |
| EXPECT_EQ(blink::WebInputEvent::Type::kTouchCancel, |
| observer.acked_touch_event_type()); |
| |
| dispatched_events = observer.GetAndResetDispatchedEventTypes(); |
| ASSERT_EQ(2u, dispatched_events.size()); |
| EXPECT_EQ(blink::WebInputEvent::Type::kTouchCancel, dispatched_events[0]); |
| EXPECT_EQ(blink::WebInputEvent::Type::kGestureScrollEnd, |
| dispatched_events[1]); |
| |
| host()->RemoveInputEventObserver(&observer); |
| } |
| |
| // Observes the WebContents until a frame finishes loading the contents of a |
| // given GURL. |
| class DocumentLoadObserver : WebContentsObserver { |
| public: |
| DocumentLoadObserver(WebContents* contents, const GURL& url) |
| : WebContentsObserver(contents), document_origin_(url) {} |
| |
| DocumentLoadObserver(const DocumentLoadObserver&) = delete; |
| DocumentLoadObserver& operator=(const DocumentLoadObserver&) = delete; |
| |
| void Wait() { |
| if (loaded_) { |
| return; |
| } |
| run_loop_ = std::make_unique<base::RunLoop>(); |
| run_loop_->Run(); |
| } |
| |
| private: |
| void DidFinishLoad(RenderFrameHost* rfh, const GURL& url) override { |
| loaded_ |= (url == document_origin_); |
| if (loaded_ && run_loop_) { |
| run_loop_->Quit(); |
| } |
| } |
| |
| bool loaded_ = false; |
| const GURL document_origin_; |
| std::unique_ptr<base::RunLoop> run_loop_; |
| }; |
| |
| // This test verifies that when a cross-process child frame loads, the initial |
| // updates for touch event handlers are sent from the renderer. |
| IN_PROC_BROWSER_TEST_F(RenderWidgetHostSitePerProcessTest, |
| OnHasTouchEventHandlers) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(b)")); |
| GURL::Replacements replacement; |
| replacement.SetHostStr("b.com"); |
| replacement.SetQueryStr("b()"); |
| GURL target_child_url = main_url.ReplaceComponents(replacement); |
| DocumentLoadObserver child_frame_observer(shell()->web_contents(), |
| target_child_url); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| child_frame_observer.Wait(); |
| auto* filter = GetTouchActionFilterForWidget(web_contents() |
| ->GetPrimaryFrameTree() |
| .root() |
| ->child_at(0) |
| ->current_frame_host() |
| ->GetRenderWidgetHost()); |
| EXPECT_TRUE(filter->allowed_touch_action().has_value()); |
| } |
| |
| // The plumbing that this test is verifying is not utilized on Mac/Android, |
| // where popup menus don't create a popup RenderWidget, but rather they trigger |
| // a FrameHostMsg_ShowPopup to ask the browser to build and display the actual |
| // popup using native controls. |
| #if !BUILDFLAG(IS_MAC) && !BUILDFLAG(IS_ANDROID) |
| |
| namespace { |
| |
| // Helper to use inside a loop instead of using RunLoop::RunUntilIdle() to avoid |
| // the loop being a busy loop that prevents renderer from doing its job. Use |
| // only when there is no better way to synchronize. |
| void GiveItSomeTime(base::TimeDelta delta) { |
| base::RunLoop run_loop; |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, run_loop.QuitClosure(), delta); |
| run_loop.Run(); |
| } |
| |
| } // namespace |
| |
| IN_PROC_BROWSER_TEST_F(RenderWidgetHostSitePerProcessTest, |
| BrowserClosesSelectPopup) { |
| // Navigate to a page with a <select> element. |
| GURL main_url(embedded_test_server()->GetURL( |
| "a.com", "/site_isolation/page-with-select.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| SimulateEndOfPaintHoldingOnPrimaryMainFrame(shell()->web_contents()); |
| |
| auto* contents = static_cast<WebContentsImpl*>(shell()->web_contents()); |
| FrameTreeNode* root = contents->GetPrimaryFrameTree().root(); |
| RenderFrameHostImpl* root_frame_host = root->current_frame_host(); |
| RenderProcessHost* process = root_frame_host->GetProcess(); |
| |
| // Open the <select> menu by focusing it and sending a space key |
| // at the focused node. This creates a popup widget. |
| input::NativeWebKeyboardEvent event( |
| blink::WebKeyboardEvent::Type::kChar, blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests()); |
| event.text[0] = ' '; |
| |
| for (int i = 0; i < 2; ++i) { |
| bool browser_closes = i == 0; |
| |
| // This focuses and opens the select box, creating a popup RenderWidget. We |
| // wait for the RenderWidgetHost to be shown. |
| auto filter = |
| std::make_unique<ShowPopupWidgetWaiter>(contents, root_frame_host); |
| EXPECT_TRUE(ExecJs(root_frame_host, "focusSelectMenu();")); |
| root_frame_host->GetRenderWidgetHost()->ForwardKeyboardEvent(event); |
| filter->Wait(); |
| |
| // The popup RenderWidget will get its own routing id. |
| int popup_routing_id = filter->last_routing_id(); |
| EXPECT_TRUE(popup_routing_id); |
| // Grab a pointer to the popup RenderWidget. |
| RenderWidgetHost* popup_widget_host = |
| RenderWidgetHost::FromID(process->GetDeprecatedID(), popup_routing_id); |
| ASSERT_TRUE(popup_widget_host); |
| ASSERT_NE(popup_widget_host, root_frame_host->GetRenderWidgetHost()); |
| |
| auto* popup_widget_host_impl = |
| static_cast<RenderWidgetHostImpl*>(popup_widget_host); |
| if (browser_closes) { |
| // Close the popup RenderWidget from the browser side. |
| popup_widget_host_impl->ShutdownAndDestroyWidget(true); |
| } else { |
| base::WeakPtr<RenderWidgetHostImpl> popup_weak_ptr = |
| popup_widget_host_impl->GetWeakPtr(); |
| |
| // Close the popup RenderWidget from the renderer side by removing focus. |
| EXPECT_TRUE(ExecJs(root_frame_host, "document.activeElement.blur()")); |
| |
| // Ensure that the RenderWidgetHostImpl gets destroyed, which implies the |
| // close step has also been sent to the renderer process. |
| while (popup_weak_ptr) { |
| GiveItSomeTime(TestTimeouts::tiny_timeout()); |
| } |
| } |
| // Ensure the renderer didn't explode :). |
| { |
| const auto title_when_done = |
| std::to_array<std::u16string>({u"done 0", u"done 1"}); |
| TitleWatcher title_watcher(shell()->web_contents(), title_when_done[i]); |
| EXPECT_TRUE( |
| ExecJs(root_frame_host, JsReplace("document.title='done $1'", i))); |
| EXPECT_EQ(title_watcher.WaitAndGetTitle(), title_when_done[i]); |
| } |
| } |
| } |
| |
| #endif |
| |
| #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) |
| |
| namespace { |
| |
| // Intercept PopupWidgetHost::ShowPopup to override the initial bounds |
| class ShowPopupInterceptor |
| : public blink::mojom::PopupWidgetHostInterceptorForTesting { |
| public: |
| ShowPopupInterceptor(WebContentsImpl* web_contents, |
| RenderFrameHostImpl* frame_host, |
| const gfx::Rect& overriden_bounds) |
| : create_new_popup_widget_interceptor_( |
| frame_host, |
| base::BindOnce(&ShowPopupInterceptor::DidCreatePopupWidget, |
| base::Unretained(this))), |
| overriden_bounds_(overriden_bounds) {} |
| |
| ShowPopupInterceptor(const ShowPopupInterceptor&) = delete; |
| ShowPopupInterceptor& operator=(const ShowPopupInterceptor&) = delete; |
| |
| ~ShowPopupInterceptor() override { |
| if (auto* rwhi = RenderWidgetHostImpl::FromID(process_id_, routing_id_)) { |
| std::ignore = |
| rwhi->popup_widget_host_receiver_for_testing().SwapImplForTesting( |
| rwhi); |
| } |
| } |
| |
| void Wait() { run_loop_.Run(); } |
| |
| // blink::mojom::PopupWidgetHostInterceptorForTesting: |
| blink::mojom::PopupWidgetHost* GetForwardingInterface() override { |
| DCHECK_NE(IPC::mojom::kRoutingIdNone, routing_id_); |
| return RenderWidgetHostImpl::FromID(process_id_, routing_id_); |
| } |
| |
| void ShowPopup(const gfx::Rect& initial_rect, |
| const gfx::Rect& initial_anchor_rect, |
| ShowPopupCallback callback) override { |
| GetForwardingInterface()->ShowPopup(overriden_bounds_, initial_anchor_rect, |
| std::move(callback)); |
| run_loop_.Quit(); |
| } |
| |
| void DidCreatePopupWidget(RenderWidgetHost* render_widget_host) { |
| process_id_ = render_widget_host->GetProcess()->GetDeprecatedID(); |
| routing_id_ = render_widget_host->GetRoutingID(); |
| std::ignore = static_cast<RenderWidgetHostImpl*>(render_widget_host) |
| ->popup_widget_host_receiver_for_testing() |
| .SwapImplForTesting(this); |
| } |
| |
| int last_routing_id() const { return routing_id_; } |
| |
| private: |
| CreateNewPopupWidgetInterceptor create_new_popup_widget_interceptor_; |
| base::RunLoop run_loop_; |
| gfx::Rect overriden_bounds_; |
| int32_t routing_id_ = IPC::mojom::kRoutingIdNone; |
| int32_t process_id_ = 0; |
| }; |
| |
| #if BUILDFLAG(IS_MAC) |
| |
| // Intercepts calls to LocalFrameHost::ShowPopupMenu method(), to override |
| // initial bounds and hook the `PopupMenuClient` |
| class ShowPopupMenuInterceptor |
| : public blink::mojom::LocalFrameHostInterceptorForTesting, |
| public blink::mojom::PopupMenuClient { |
| public: |
| explicit ShowPopupMenuInterceptor(RenderFrameHostImpl* render_frame_host, |
| const gfx::Rect& overriden_bounds) |
| : overriden_bounds_(overriden_bounds), |
| swapped_impl_( |
| render_frame_host->local_frame_host_receiver_for_testing(), |
| this) {} |
| |
| ~ShowPopupMenuInterceptor() override = default; |
| |
| LocalFrameHost* GetForwardingInterface() override { |
| return swapped_impl_.old_impl(); |
| } |
| |
| void Wait() { run_loop_.Run(); } |
| |
| void ShowPopupMenu( |
| mojo::PendingRemote<blink::mojom::PopupMenuClient> popup_client, |
| const gfx::Rect& bounds, |
| double font_size, |
| int32_t selected_item, |
| std::vector<blink::mojom::MenuItemPtr> menu_items, |
| bool right_aligned, |
| bool allow_multiple_selection) override { |
| CHECK(GetForwardingInterface()); |
| GetForwardingInterface()->ShowPopupMenu( |
| receiver_.BindNewPipeAndPassRemote(), overriden_bounds_, font_size, |
| selected_item, std::move(menu_items), right_aligned, |
| allow_multiple_selection); |
| } |
| |
| void DidAcceptIndices(const std::vector<int32_t>& indices) override { |
| receiver_.reset(); |
| } |
| |
| void DidCancel() override { |
| is_cancelled_ = true; |
| receiver_.reset(); |
| run_loop_.Quit(); |
| } |
| |
| bool is_cancelled() const { return is_cancelled_; } |
| |
| private: |
| base::RunLoop run_loop_; |
| bool is_cancelled_{false}; |
| gfx::Rect overriden_bounds_; |
| mojo::test::ScopedSwapImplForTesting<blink::mojom::LocalFrameHost> |
| swapped_impl_; |
| mojo::Receiver<blink::mojom::PopupMenuClient> receiver_{this}; |
| }; |
| #endif // BUILDFLAG(IS_MAC) |
| |
| } // namespace |
| |
| IN_PROC_BROWSER_TEST_F(RenderWidgetHostSitePerProcessTest, |
| BrowserClosesPopupIntersectsPermissionPrompt) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "a.com", "/site_isolation/page-with-select.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| SimulateEndOfPaintHoldingOnPrimaryMainFrame(shell()->web_contents()); |
| |
| auto* contents = static_cast<WebContentsImpl*>(web_contents()); |
| FrameTreeNode* root = contents->GetPrimaryFrameTree().root(); |
| RenderFrameHostImpl* root_frame_host = root->current_frame_host(); |
| |
| // TODO(crbug.com/40750695): Crash when we attempt to use a mock prompt here. |
| // After the ticket is fixed, remove the shortcut of getting bounds and use |
| // the `MockPermissionPromptFactory` instead. |
| // Create a popup widget and wait for the RenderWidgetHost to be shown. |
| gfx::Rect permission_exclusion_area_bounds(100, 100, 100, 100); |
| static_cast<PermissionControllerImpl*>( |
| root_frame_host->GetBrowserContext()->GetPermissionController()) |
| ->set_exclusion_area_bounds_for_tests(permission_exclusion_area_bounds); |
| #if BUILDFLAG(IS_MAC) |
| ShowPopupMenuInterceptor show_popup_menu_interceptor( |
| root_frame_host, permission_exclusion_area_bounds - |
| contents->GetContainerBounds().OffsetFromOrigin()); |
| #else |
| ShowPopupInterceptor show_popup_interceptor(contents, root_frame_host, |
| permission_exclusion_area_bounds); |
| #endif // BUILDFLAG(IS_MAC) |
| |
| input::NativeWebKeyboardEvent event( |
| blink::WebKeyboardEvent::Type::kChar, blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests()); |
| event.text[0] = ' '; |
| EXPECT_TRUE(ExecJs(root_frame_host, "focusSelectMenu();")); |
| root_frame_host->GetRenderWidgetHost()->ForwardKeyboardEvent(event); |
| |
| #if BUILDFLAG(IS_MAC) |
| show_popup_menu_interceptor.Wait(); |
| ASSERT_TRUE(show_popup_menu_interceptor.is_cancelled()); |
| #else |
| show_popup_interceptor.Wait(); |
| ASSERT_FALSE( |
| RenderWidgetHost::FromID(root_frame_host->GetProcess()->GetDeprecatedID(), |
| show_popup_interceptor.last_routing_id())); |
| #endif // BUILDFLAG(IS_MAC) |
| } |
| |
| #endif // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) |
| |
| // Tests that `window.screen` dimensions match the display, not the viewport, |
| // while the frame is fullscreen. See crbug.com/1367416 |
| IN_PROC_BROWSER_TEST_F(RenderWidgetHostBrowserTest, FullscreenSize) { |
| // Check initial dimensions before entering fullscreen. |
| ASSERT_FALSE(shell()->IsFullscreenForTabOrPending(web_contents())); |
| ASSERT_FALSE(web_contents()->IsFullscreen()); |
| WaitForVisualPropertiesAck(); |
| EXPECT_EQ(host()->GetScreenInfo().rect.size().ToString(), |
| EvalJs(web_contents(), "`${screen.width}x${screen.height}`")); |
| |
| // Enter fullscreen; Content Shell does not resize the viewport to fill the |
| // screen in fullscreen on some platforms. |
| constexpr char kEnterFullscreenScript[] = R"JS( |
| document.documentElement.requestFullscreen().then(() => { |
| return !!document.fullscreenElement; |
| }); |
| )JS"; |
| ASSERT_TRUE(EvalJs(web_contents(), kEnterFullscreenScript).ExtractBool()); |
| |
| // `window.screen` dimensions match the display size. |
| EXPECT_EQ(host()->GetScreenInfo().rect.size().ToString(), |
| EvalJs(web_contents(), "`${screen.width}x${screen.height}`")); |
| |
| // Check dimensions again after exiting fullscreen. |
| constexpr char kExitFullscreenScript[] = R"JS( |
| document.exitFullscreen().then(() => { |
| return !document.fullscreenElement; |
| }); |
| )JS"; |
| ASSERT_TRUE(EvalJs(web_contents(), kExitFullscreenScript).ExtractBool()); |
| ASSERT_FALSE(web_contents()->IsFullscreen()); |
| EXPECT_EQ(host()->GetScreenInfo().rect.size().ToString(), |
| EvalJs(web_contents(), "`${screen.width}x${screen.height}`")); |
| } |
| |
| class RenderWidgetHostFoldableCSSTest : public RenderWidgetHostBrowserTest { |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| ContentBrowserTest::SetUpCommandLine(command_line); |
| command_line->AppendSwitch( |
| switches::kEnableExperimentalWebPlatformFeatures); |
| } |
| }; |
| |
| // Tests that when a video element goes fullscreen and uses the default |
| // fullscreen UA stylesheet (in blink/core/css/fullscreen.css) the viewport |
| // segments MQs and env variables are correctly working. |
| IN_PROC_BROWSER_TEST_F( |
| RenderWidgetHostFoldableCSSTest, |
| ViewportSegmentsWorksInUAFullscreenCSSAfterEnteringFullscreen) { |
| const char kTestPageURL[] = |
| R"HTML(data:text/html,<!DOCTYPE html> |
| <video id='video'></video>)HTML"; |
| EXPECT_TRUE(NavigateToURL(shell(), GURL(kTestPageURL))); |
| // Check initial state before entering fullscreen. |
| ASSERT_FALSE(shell()->IsFullscreenForTabOrPending(web_contents())); |
| ASSERT_FALSE(web_contents()->IsFullscreen()); |
| constexpr char kEnterFullscreenScript[] = R"JS( |
| document.querySelector('video').requestFullscreen().then(() => { |
| return !!document.fullscreenElement; |
| }); |
| )JS"; |
| // Initial state. This will ensure that no display feature/viewport segments |
| // are coming from the platform. |
| view()->OverrideDisplayFeatureForEmulation(nullptr); |
| ASSERT_TRUE(EvalJs(web_contents(), kEnterFullscreenScript).ExtractBool()); |
| |
| // Changing the display feature/viewport segments without leaving fullscreen |
| // should update the video element. |
| const gfx::Size root_view_size = view()->GetVisibleViewportSize(); |
| const int kDisplayFeatureLength = 10; |
| int offset = root_view_size.width() / 2 - kDisplayFeatureLength / 2; |
| DisplayFeature emulated_display_feature{ |
| DisplayFeature::Orientation::kVertical, offset, |
| /* mask_length */ kDisplayFeatureLength}; |
| view()->OverrideDisplayFeatureForEmulation(&emulated_display_feature); |
| WaitForVisualPropertiesAck(); |
| EXPECT_EQ(base::NumberToString(offset) + "px", |
| EvalJs(shell(), "getComputedStyle(video).width").ExtractString()); |
| // Rounding of GetVisibleViewportSize in the presence of a non-integer |
| // devicePixelRatio device can make this off by one vs the video height. |
| EXPECT_NEAR( |
| root_view_size.height(), |
| EvalJs(shell(), "parseInt(getComputedStyle(video).height)").ExtractInt(), |
| 1); |
| |
| emulated_display_feature.orientation = |
| DisplayFeature::Orientation::kHorizontal; |
| offset = root_view_size.height() / 2 - kDisplayFeatureLength / 2; |
| emulated_display_feature.offset = offset; |
| view()->OverrideDisplayFeatureForEmulation(&emulated_display_feature); |
| WaitForVisualPropertiesAck(); |
| EXPECT_EQ(base::NumberToString(offset) + "px", |
| EvalJs(shell(), "getComputedStyle(video).height").ExtractString()); |
| EXPECT_NEAR( |
| root_view_size.width(), |
| EvalJs(shell(), "parseInt(getComputedStyle(video).width)").ExtractInt(), |
| 1); |
| |
| // No display feature/viewport segments are set, the video should go |
| // fullscreen. |
| view()->OverrideDisplayFeatureForEmulation(nullptr); |
| WaitForVisualPropertiesAck(); |
| // Rounding of GetVisibleViewportSize in the presence of a non-integer |
| // devicePixelRatio device can make this off by one vs the video height. |
| EXPECT_NEAR( |
| root_view_size.height(), |
| EvalJs(shell(), "parseInt(getComputedStyle(video).height)").ExtractInt(), |
| 1); |
| EXPECT_NEAR( |
| root_view_size.width(), |
| EvalJs(shell(), "parseInt(getComputedStyle(video).width)").ExtractInt(), |
| 1); |
| |
| constexpr char kExitFullscreenScript[] = R"JS( |
| document.exitFullscreen().then(() => { |
| return !document.fullscreenElement; |
| }); |
| )JS"; |
| ASSERT_TRUE(EvalJs(web_contents(), kExitFullscreenScript).ExtractBool()); |
| ASSERT_FALSE(web_contents()->IsFullscreen()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| RenderWidgetHostFoldableCSSTest, |
| ViewportSegmentsWorksInUAFullscreenCSSBeforeEnteringFullscreen) { |
| const char kTestPageURL[] = |
| R"HTML(data:text/html,<!DOCTYPE html> |
| <video id='video'></video>)HTML"; |
| EXPECT_TRUE(NavigateToURL(shell(), GURL(kTestPageURL))); |
| // Check initial state before entering fullscreen. |
| ASSERT_FALSE(shell()->IsFullscreenForTabOrPending(web_contents())); |
| ASSERT_FALSE(web_contents()->IsFullscreen()); |
| constexpr char kEnterFullscreenScript[] = R"JS( |
| document.querySelector('video').requestFullscreen().then(() => { |
| return !!document.fullscreenElement; |
| }); |
| )JS"; |
| |
| const gfx::Size root_view_size = view()->GetVisibleViewportSize(); |
| const int kDisplayFeatureLength = 10; |
| int offset = root_view_size.height() / 2 - kDisplayFeatureLength / 2; |
| DisplayFeature emulated_display_feature{ |
| DisplayFeature::Orientation::kHorizontal, offset, |
| /* mask_length */ kDisplayFeatureLength}; |
| view()->OverrideDisplayFeatureForEmulation(&emulated_display_feature); |
| WaitForVisualPropertiesAck(); |
| ASSERT_TRUE(EvalJs(web_contents(), kEnterFullscreenScript).ExtractBool()); |
| EXPECT_EQ(base::NumberToString(offset) + "px", |
| EvalJs(shell(), "getComputedStyle(video).height").ExtractString()); |
| EXPECT_NEAR( |
| root_view_size.width(), |
| EvalJs(shell(), "parseInt(getComputedStyle(video).width)").ExtractInt(), |
| 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(RenderWidgetHostFoldableCSSTest, |
| FoldablesCSSWithReload) { |
| const char kTestPageURL[] = |
| R"HTML(data:text/html,<!DOCTYPE html> |
| <style> |
| @media (horizontal-viewport-segments: 2) and |
| (vertical-viewport-segments: 1) { |
| div { margin-left: env(viewport-segment-right 0 0, 10px); } |
| } |
| </style> |
| <div id='target'></div>)HTML"; |
| |
| LoadStopObserver load_stop_observer(shell()->web_contents()); |
| EXPECT_TRUE(NavigateToURL(shell(), GURL(kTestPageURL))); |
| load_stop_observer.Wait(); |
| |
| const gfx::Size root_view_size = view()->GetVisibleViewportSize(); |
| const int kDisplayFeatureLength = 10; |
| const int offset = root_view_size.width() / 2 - kDisplayFeatureLength / 2; |
| DisplayFeature emulated_display_feature{ |
| DisplayFeature::Orientation::kVertical, offset, |
| /* mask_length */ kDisplayFeatureLength}; |
| view()->OverrideDisplayFeatureForEmulation(&emulated_display_feature); |
| |
| EXPECT_EQ( |
| base::NumberToString(emulated_display_feature.offset) + "px", |
| EvalJs(shell(), "getComputedStyle(target).marginLeft").ExtractString()); |
| |
| // Ensure that the environment variables have the correct values in the new |
| // document that is created on reloading the page. |
| LoadStopObserver load_stop_observer2(shell()->web_contents()); |
| TestNavigationManager navigation_manager(shell()->web_contents(), |
| GURL(kTestPageURL)); |
| shell()->Reload(); |
| EXPECT_TRUE(navigation_manager.WaitForResponse()); |
| if (ShouldCreateNewHostForAllFrames()) { |
| // When RenderDocument is enabled, a new RenderWidgetHost will be created |
| // after the reload, so we need to call SynchronizeVisualProperties() again. |
| RenderWidgetHostImpl* target_rwh = static_cast<RenderWidgetHostImpl*>( |
| navigation_manager.GetNavigationHandle() |
| ->GetRenderFrameHost() |
| ->GetRenderWidgetHost()); |
| target_rwh->GetView()->OverrideDisplayFeatureForEmulation( |
| &emulated_display_feature); |
| } |
| EXPECT_TRUE(navigation_manager.WaitForNavigationFinished()); |
| load_stop_observer2.Wait(); |
| |
| EXPECT_EQ( |
| base::NumberToString(emulated_display_feature.offset) + "px", |
| EvalJs(shell(), "getComputedStyle(target).marginLeft").ExtractString()); |
| } |
| |
| class RenderWidgetHostDelegatedInkMetadataTest |
| : public RenderWidgetHostTouchEmulatorBrowserTest { |
| public: |
| RenderWidgetHostDelegatedInkMetadataTest() = default; |
| void SetUpOnMainThread() override { |
| ContentBrowserTest::SetUpOnMainThread(); |
| |
| LoadStopObserver load_stop_observer(shell()->web_contents()); |
| EXPECT_TRUE( |
| NavigateToURL(shell(), GURL(R"HTML(data:text/html,<!DOCTYPE html> |
| <body> <canvas id="board" width="400" height="400"></canvas> </body> |
| )HTML"))); |
| load_stop_observer.Wait(); |
| SimulateEndOfPaintHoldingOnPrimaryMainFrame(shell()->web_contents()); |
| } |
| |
| protected: |
| void WaitForDelegatedInkMetadata( |
| RenderFrameSubmissionObserver& frame_observer) { |
| // The mouse event is not necessarily routed in the first frame that is |
| // generated. Generate frames until the mouse event and canvas paint is |
| // routed to the compositor. |
| do { |
| frame_observer.WaitForMetadataChange(); |
| } while (!frame_observer.LastRenderFrameMetadata() |
| .delegated_ink_metadata.has_value() && |
| frame_observer.render_frame_count() <= kMaxFrames); |
| frame_observer.ResetCounter(); |
| } |
| |
| private: |
| static constexpr int kMaxFrames = 3; |
| }; |
| |
| // Confirm that using the |updateInkTrailStartPoint| JS API results in the |
| // |request_points_for_delegated_ink_| flag being set on the RWHVB. |
| IN_PROC_BROWSER_TEST_F(RenderWidgetHostDelegatedInkMetadataTest, |
| FlagGetsSetFromRenderFrameMetadata) { |
| ASSERT_TRUE(ExecJs(shell()->web_contents(), R"( |
| let ctx = board.getContext('2d'); |
| let presenter = null; |
| navigator.ink.requestPresenter().then(e => { presenter = e; }); |
| const pointSize = 15; |
| const style = { color: 'rgb(255,0,0)', diameter: pointSize }; |
| |
| board.addEventListener('pointermove', event => { |
| // Paint on the canvas to force damage and new frames generation. |
| ctx.fillstyle = 'rgb(0,255,0)'; |
| ctx.fillRect(event.clientX, event.clientY - board |
| .getBoundingClientRect().top, pointSize, pointSize); |
| presenter.updateInkTrailStartPoint(event, style); |
| }); |
| )")); |
| RenderFrameMetadataProviderImpl* metadata_provider = |
| host()->render_frame_metadata_provider(); |
| RenderFrameSubmissionObserver frame_observer(metadata_provider); |
| SimulateRoutedMouseEvent(blink::WebInputEvent::Type::kMouseMove, 10, 10, 0, |
| false); |
| WaitForDelegatedInkMetadata(frame_observer); |
| |
| EXPECT_TRUE(metadata_provider->LastRenderFrameMetadata() |
| .delegated_ink_metadata.value() |
| .delegated_ink_is_hovering); |
| |
| // Confirm that the state of hover changing on the next produced delegated ink |
| // metadata results in a new RenderFrameMetadata being sent, with |
| // |delegated_ink_hovering| false. |
| SimulateRoutedMouseEvent(blink::WebInputEvent::Type::kMouseMove, 20, 20, |
| blink::WebInputEvent::kLeftButtonDown, false); |
| WaitForDelegatedInkMetadata(frame_observer); |
| |
| EXPECT_FALSE(metadata_provider->LastRenderFrameMetadata() |
| .delegated_ink_metadata.value() |
| .delegated_ink_is_hovering); |
| |
| // Confirm that the flag is set back to false when the JS API isn't called. |
| RunUntilInputProcessed(host()); |
| const cc::RenderFrameMetadata& last_metadata = |
| metadata_provider->LastRenderFrameMetadata(); |
| EXPECT_FALSE(last_metadata.delegated_ink_metadata.has_value()); |
| |
| // Finally, confirm that a change in hovering state (pointerdown to pointerup |
| // here) without a call to updateInkTrailStartPoint doesn't cause a new |
| // RenderFrameMetadata to be sent. |
| SimulateRoutedMouseEvent(blink::WebInputEvent::Type::kMouseMove, 20, 20, 0, |
| false); |
| RunUntilInputProcessed(host()); |
| EXPECT_EQ(last_metadata, metadata_provider->LastRenderFrameMetadata()); |
| } |
| |
| // If the DelegatedInkTrailPresenter creates a metadata that has the same |
| // timestamp as the previous one, it does not set the metadata. |
| IN_PROC_BROWSER_TEST_F(RenderWidgetHostDelegatedInkMetadataTest, |
| DuplicateMetadata) { |
| ASSERT_TRUE(ExecJs(shell()->web_contents(), R"( |
| let ctx = board.getContext('2d'); |
| let presenter = null; |
| navigator.ink.requestPresenter().then(e => { presenter = e; }); |
| const pointSize = 15; |
| const style = { color: 'rgb(255,0,0)', diameter: pointSize }; |
| let first_move_event = null; |
| |
| board.addEventListener('pointermove', event => { |
| // Paint on the canvas to force damage and new frames generation. |
| ctx.fillstyle = 'rgb(0,255,0)'; |
| ctx.fillRect(event.clientX, event.clientY - board |
| .getBoundingClientRect().top, pointSize, pointSize); |
| if (first_move_event == null) { |
| first_move_event = event; |
| } |
| presenter.updateInkTrailStartPoint(first_move_event, style); |
| }); |
| )")); |
| |
| RenderFrameMetadataProviderImpl* metadata_provider = |
| host()->render_frame_metadata_provider(); |
| RenderFrameSubmissionObserver frame_observer(metadata_provider); |
| SimulateRoutedMouseEvent(blink::WebInputEvent::Type::kMouseMove, 10, 10, 0, |
| false); |
| |
| WaitForDelegatedInkMetadata(frame_observer); |
| EXPECT_TRUE(metadata_provider->LastRenderFrameMetadata() |
| .delegated_ink_metadata.value() |
| .delegated_ink_is_hovering); |
| |
| // Confirm metadata has no value when updateInkTrailStartPoint is called |
| // with the same event. |
| SimulateRoutedMouseEvent(blink::WebInputEvent::Type::kMouseMove, 20, 20, |
| blink::WebInputEvent::kLeftButtonDown, false); |
| RunUntilInputProcessed(host()); |
| EXPECT_FALSE(metadata_provider->LastRenderFrameMetadata() |
| .delegated_ink_metadata.has_value()); |
| } |
| |
| #if BUILDFLAG(IS_ANDROID) |
| namespace { |
| |
| class LocalSurfaceIdChangedObserver |
| : public RenderFrameMetadataProvider::Observer { |
| public: |
| explicit LocalSurfaceIdChangedObserver( |
| bool expect_newer_id, |
| const viz::LocalSurfaceId& local_surface_id, |
| RenderFrameMetadataProviderImpl* provider) |
| : expect_newer_id_(expect_newer_id), |
| current_id_(local_surface_id), |
| provider_(provider) { |
| provider_->AddObserver(this); |
| } |
| ~LocalSurfaceIdChangedObserver() override { provider_->RemoveObserver(this); } |
| |
| // `RenderFrameMetadataProvider::Observer`: |
| void OnRenderFrameMetadataChangedBeforeActivation( |
| const cc::RenderFrameMetadata& metadata) override { |
| if (!metadata.local_surface_id.has_value()) { |
| return; |
| } |
| if (expect_newer_id_ && |
| !metadata.local_surface_id->IsNewerThan(current_id_)) { |
| // Only record the first newer id. |
| return; |
| } |
| if (!expect_newer_id_) { |
| // Fail immediately instead of timing out. |
| ASSERT_FALSE(metadata.local_surface_id->IsNewerThan(current_id_)); |
| if (metadata.local_surface_id != current_id_) { |
| // Only record the first id that's the same. |
| return; |
| } |
| } |
| observed_id_ = metadata.local_surface_id.value(); |
| if (run_loop_) { |
| run_loop_->Quit(); |
| } |
| } |
| void OnRenderFrameMetadataChangedAfterActivation( |
| base::TimeTicks activation_time) override {} |
| void OnRenderFrameSubmission() override {} |
| void OnLocalSurfaceIdChanged( |
| const cc::RenderFrameMetadata& metadata) override {} |
| |
| [[nodiscard]] bool WaitForExpectedLocalSurfaceIdUpdate( |
| const viz::LocalSurfaceId& expected_id) { |
| // If `OnRenderFrameMetadataChangedBeforeActivation()` is called before |
| // `WaitForExpectedLocalSurfaceIdUpdate()`. |
| if (observed_id_.is_valid()) { |
| return observed_id_ == expected_id; |
| } |
| CHECK(!run_loop_); |
| run_loop_ = std::make_unique<base::RunLoop>(); |
| run_loop_->Run(); |
| return observed_id_ == expected_id; |
| } |
| |
| const viz::LocalSurfaceId& observed_id() const { return observed_id_; } |
| |
| private: |
| const bool expect_newer_id_; |
| const viz::LocalSurfaceId current_id_; |
| const raw_ptr<RenderFrameMetadataProviderImpl> provider_; |
| std::unique_ptr<base::RunLoop> run_loop_; |
| viz::LocalSurfaceId observed_id_ = viz::LocalSurfaceId{}; |
| }; |
| |
| } // namespace |
| |
| class RenderWidgetHostSameDocNavUpdatesLocalSurfaceIdTest |
| : public RenderWidgetHostBrowserTest, |
| public ::testing::WithParamInterface<bool> { |
| public: |
| RenderWidgetHostSameDocNavUpdatesLocalSurfaceIdTest() = default; |
| |
| void SetUpOnMainThread() override { |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| auto preferences = web_contents()->GetOrCreateWebPreferences(); |
| preferences.increment_local_surface_id_for_mainframe_same_doc_navigation = |
| GetParam(); |
| web_contents()->SetWebPreferences(preferences); |
| } |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| RenderWidgetHostBrowserTest::SetUpCommandLine(command_line); |
| command_line->AppendSwitch(switches::kForcePrefersNoReducedMotion); |
| } |
| }; |
| |
| // Assert that with `IncrementLocalSurfaceIdForMainframeSameDocNavigation` |
| // enabled, the `LocalSurfaceId` will be updated for same-doc navigations. |
| IN_PROC_BROWSER_TEST_P(RenderWidgetHostSameDocNavUpdatesLocalSurfaceIdTest, |
| SameDocNavigationUpdatesLocalSurfaceId) { |
| bool increment_local_surface_id = GetParam(); |
| ASSERT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL( |
| "/session_history/fragment.html"))); |
| // Changes the background color when navigate to "fragment.html#a". |
| ASSERT_TRUE(ExecJs(web_contents(), R"( |
| window.addEventListener("hashchange", (event) => { |
| document.body.style.background = 'red'; |
| }) |
| )")); |
| // Get the current LocalSurfaceId of the mainframe. |
| const viz::LocalSurfaceId& id_before_nav = view()->GetLocalSurfaceId(); |
| LocalSurfaceIdChangedObserver obs( |
| increment_local_surface_id, id_before_nav, |
| view()->host()->render_frame_metadata_provider()); |
| viz::LocalSurfaceId expected; |
| if (increment_local_surface_id) { |
| // Expect the child component of the LocalSurfaceId is incremented by one, |
| // as the result of the same-doc navigation to #a. |
| expected = viz::LocalSurfaceId(id_before_nav.parent_sequence_number(), |
| id_before_nav.child_sequence_number() + 1, |
| id_before_nav.embed_token()); |
| } else { |
| expected = id_before_nav; |
| } |
| ASSERT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL( |
| "/session_history/fragment.html#a"))); |
| // Forces a frame submission from the renderer. |
| WaitForCopyableViewInWebContents(shell()->web_contents()); |
| ASSERT_TRUE(obs.WaitForExpectedLocalSurfaceIdUpdate(expected)) |
| << "Expected " << expected << " but observed " << obs.observed_id(); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| RenderWidgetHostSameDocNavUpdatesLocalSurfaceIdTest, |
| ::testing::Bool()); |
| |
| #endif // BUILDFLAG(IS_ANDROID) |
| |
| namespace { |
| |
| enum class TestConfig { kSameDoc = 0, kBFCacheEnabled, kBFCacheDisabled }; |
| |
| static std::string DescribeTest( |
| const ::testing::TestParamInfo<TestConfig>& info) { |
| switch (info.param) { |
| case TestConfig::kSameDoc: |
| return "SameDoc"; |
| case TestConfig::kBFCacheEnabled: |
| return "CrossDoc_BFCacheEnabled"; |
| case TestConfig::kBFCacheDisabled: |
| return "CrossDoc_BFCacheDisabled"; |
| } |
| } |
| |
| class ItemSequenceNumberObserver : public RenderFrameMetadataProvider::Observer, |
| public WebContentsObserver { |
| public: |
| ItemSequenceNumberObserver(WebContents* web_contents, |
| int64_t expected_sequence_number) |
| : WebContentsObserver(web_contents), |
| expected_sequence_number_(expected_sequence_number) {} |
| ~ItemSequenceNumberObserver() override { |
| if (provider_) { |
| provider_->RemoveObserver(this); |
| } |
| } |
| |
| // `RenderFrameMetadataProvider::Observer`: |
| void OnRenderFrameMetadataChangedBeforeActivation( |
| const cc::RenderFrameMetadata& metadata) override {} |
| void OnRenderFrameMetadataChangedAfterActivation( |
| base::TimeTicks activation_time) override { |
| ASSERT_TRUE(provider_); |
| if (expected_sequence_number_ == |
| provider_->LastRenderFrameMetadata() |
| .primary_main_frame_item_sequence_number) { |
| observed_expected_number_ = true; |
| } else { |
| return; |
| } |
| if (run_loop_) { |
| run_loop_->Quit(); |
| } |
| } |
| void OnRenderFrameSubmission() override {} |
| void OnLocalSurfaceIdChanged( |
| const cc::RenderFrameMetadata& metadata) override {} |
| |
| // `WebContentsObserver`: |
| void ReadyToCommitNavigation(NavigationHandle* navigation_handle) override { |
| ASSERT_FALSE(navigation_handle->IsSameDocument()); |
| auto* request = static_cast<NavigationRequest*>(navigation_handle); |
| RenderFrameHostImpl* rfhi = request->GetRenderFrameHost(); |
| ASSERT_TRUE(rfhi); |
| provider_ = rfhi->GetView()->host()->render_frame_metadata_provider(); |
| provider_->AddObserver(this); |
| } |
| |
| // For same-doc navigations, we don't get `WCO::ReadyToCommitNavigation`. |
| // Since this is testing code, we just set the provider directly (instead of |
| // registering a new `CommitDeferringCondition`). |
| void SetProviderForSameDocNavigations( |
| RenderFrameMetadataProviderImpl* provider) { |
| ASSERT_FALSE(provider_); |
| provider_ = provider; |
| provider_->AddObserver(this); |
| } |
| |
| [[nodiscard]] bool WaitForExpectedItemSequenceNumber() { |
| if (!provider_) { |
| return false; |
| } |
| // If `OnRenderFrameMetadataChangedAfterActivation()` is called before |
| // `WaitForExpectedItemSequenceNumber()`. |
| if (observed_expected_number_) { |
| return true; |
| } |
| CHECK(!run_loop_); |
| run_loop_ = std::make_unique<base::RunLoop>(); |
| run_loop_->Run(); |
| return observed_expected_number_; |
| } |
| |
| private: |
| const int64_t expected_sequence_number_; |
| |
| raw_ptr<RenderFrameMetadataProviderImpl> provider_; |
| std::unique_ptr<base::RunLoop> run_loop_; |
| bool observed_expected_number_ = false; |
| }; |
| |
| class ItemSequenceNumberTracker : public RenderFrameMetadataProvider::Observer { |
| public: |
| explicit ItemSequenceNumberTracker(RenderFrameMetadataProviderImpl* provider) |
| : provider_(provider), |
| last_sequence_number_(provider_->LastRenderFrameMetadata() |
| .primary_main_frame_item_sequence_number) { |
| provider_->AddObserver(this); |
| } |
| ~ItemSequenceNumberTracker() override { provider_->RemoveObserver(this); } |
| |
| // `RenderFrameMetadataProvider::Observer`: |
| void OnRenderFrameMetadataChangedBeforeActivation( |
| const cc::RenderFrameMetadata& metadata) override {} |
| void OnRenderFrameMetadataChangedAfterActivation( |
| base::TimeTicks activation_time) override { |
| last_sequence_number_ = provider_->LastRenderFrameMetadata() |
| .primary_main_frame_item_sequence_number; |
| if (run_loop_ && last_sequence_number_ == expected_sequence_number_) { |
| run_loop_->Quit(); |
| } |
| } |
| void OnRenderFrameSubmission() override {} |
| void OnLocalSurfaceIdChanged( |
| const cc::RenderFrameMetadata& metadata) override {} |
| |
| void WaitForExpectedItemSequenceNumber(int64_t expected_sequence_number) { |
| expected_sequence_number_ = expected_sequence_number; |
| if (expected_sequence_number_ == last_sequence_number_) { |
| return; |
| } |
| run_loop_ = std::make_unique<base::RunLoop>(); |
| run_loop_->Run(); |
| } |
| |
| private: |
| raw_ptr<RenderFrameMetadataProviderImpl> provider_; |
| |
| int64_t expected_sequence_number_; |
| int64_t last_sequence_number_; |
| |
| std::unique_ptr<base::RunLoop> run_loop_; |
| }; |
| |
| class RenderWidgetHostItemSequenceNumberInRenderFrameMetadataTest |
| : public RenderWidgetHostBrowserTest, |
| public ::testing::WithParamInterface<TestConfig> { |
| public: |
| RenderWidgetHostItemSequenceNumberInRenderFrameMetadataTest() = default; |
| ~RenderWidgetHostItemSequenceNumberInRenderFrameMetadataTest() override = |
| default; |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| auto test_config = GetParam(); |
| switch (test_config) { |
| case TestConfig::kSameDoc: { |
| first_url_ = "/changing_color.html#red"; |
| second_url_ = "/changing_color.html#green"; |
| break; |
| } |
| case TestConfig::kBFCacheEnabled: { |
| first_url_ = "/empty.html"; |
| second_url_ = "/title1.html"; |
| scoped_feature_list_.InitWithFeaturesAndParameters( |
| GetDefaultEnabledBackForwardCacheFeaturesForTesting(), |
| GetDefaultDisabledBackForwardCacheFeaturesForTesting()); |
| break; |
| } |
| case TestConfig::kBFCacheDisabled: { |
| first_url_ = "/empty.html"; |
| second_url_ = "/title1.html"; |
| command_line->AppendSwitch(switches::kDisableBackForwardCache); |
| break; |
| } |
| } |
| RenderWidgetHostBrowserTest::SetUpCommandLine(command_line); |
| } |
| |
| void SetUpOnMainThread() override { |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| } |
| |
| GURL FirstURL() { return embedded_test_server()->GetURL(first_url_); } |
| |
| GURL SecondURL() { return embedded_test_server()->GetURL(second_url_); } |
| |
| private: |
| std::string first_url_; |
| std::string second_url_; |
| std::vector<std::string> hosts_; |
| |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| } // namespace |
| |
| IN_PROC_BROWSER_TEST_P( |
| RenderWidgetHostItemSequenceNumberInRenderFrameMetadataTest, |
| ItemSequenceNumberExpected) { |
| ASSERT_TRUE(NavigateToURL(shell(), FirstURL())); |
| ASSERT_TRUE(NavigateToURL(shell(), SecondURL())); |
| |
| auto* controller = static_cast<NavigationControllerImpl*>( |
| &(web_contents()->GetController())); |
| ASSERT_EQ(controller->GetEntryCount(), 2); |
| int64_t expected_sequence_number = |
| controller->GetEntryAtIndex(0) |
| ->GetFrameEntry(controller->frame_tree().root()) |
| ->item_sequence_number(); |
| |
| ItemSequenceNumberObserver obs(web_contents(), expected_sequence_number); |
| if (GetParam() == TestConfig::kSameDoc) { |
| obs.SetProviderForSameDocNavigations( |
| view()->host()->render_frame_metadata_provider()); |
| } |
| |
| TestNavigationObserver nav_observer(web_contents(), 1); |
| ASSERT_TRUE(web_contents()->GetController().CanGoBack()); |
| web_contents()->GetController().GoBack(); |
| |
| nav_observer.WaitForNavigationFinished(); |
| ASSERT_TRUE(obs.WaitForExpectedItemSequenceNumber()); |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| RenderWidgetHostItemSequenceNumberInRenderFrameMetadataTest, |
| ItemSequenceNumberExpectedNoContentChange) { |
| if (GetParam() != TestConfig::kSameDoc) { |
| return; |
| } |
| |
| ASSERT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL( |
| "/session_history/fragment.html"))); |
| |
| auto* controller = static_cast<NavigationControllerImpl*>( |
| &(web_contents()->GetController())); |
| ItemSequenceNumberTracker tracker( |
| view()->host()->render_frame_metadata_provider()); |
| |
| tracker.WaitForExpectedItemSequenceNumber( |
| controller->GetEntryAtIndex(0) |
| ->GetFrameEntry(controller->frame_tree().root()) |
| ->item_sequence_number()); |
| |
| ASSERT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL( |
| "/session_history/fragment.html#a"))); |
| tracker.WaitForExpectedItemSequenceNumber( |
| controller->GetEntryAtIndex(1) |
| ->GetFrameEntry(controller->frame_tree().root()) |
| ->item_sequence_number()); |
| |
| ASSERT_TRUE(web_contents()->GetController().CanGoBack()); |
| web_contents()->GetController().GoBack(); |
| tracker.WaitForExpectedItemSequenceNumber( |
| controller->GetEntryAtIndex(0) |
| ->GetFrameEntry(controller->frame_tree().root()) |
| ->item_sequence_number()); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| All, |
| RenderWidgetHostItemSequenceNumberInRenderFrameMetadataTest, |
| #if BUILDFLAG(IS_FUCHSIA) |
| // TODO(crbug/345304287): Temporary disable the same-doc variant while |
| // investigate the cause for flakiness. |
| ::testing::ValuesIn({TestConfig::kBFCacheEnabled, |
| TestConfig::kBFCacheDisabled}), |
| #else |
| ::testing::ValuesIn({TestConfig::kSameDoc, TestConfig::kBFCacheEnabled, |
| TestConfig::kBFCacheDisabled}), |
| #endif |
| &DescribeTest); |
| |
| } // namespace content |