| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/site_per_process_browsertest.h" |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <algorithm> |
| #include <list> |
| #include <map> |
| #include <memory> |
| #include <set> |
| #include <string> |
| #include <tuple> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/command_line.h" |
| #include "base/feature_list.h" |
| #include "base/json/json_reader.h" |
| #include "base/location.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/path_service.h" |
| #include "base/run_loop.h" |
| #include "base/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/task/post_task.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/frame_host/cross_process_frame_connector.h" |
| #include "content/browser/frame_host/frame_navigation_entry.h" |
| #include "content/browser/frame_host/frame_tree.h" |
| #include "content/browser/frame_host/interstitial_page_impl.h" |
| #include "content/browser/frame_host/navigation_controller_impl.h" |
| #include "content/browser/frame_host/navigation_entry_impl.h" |
| #include "content/browser/frame_host/navigator.h" |
| #include "content/browser/frame_host/render_frame_host_impl.h" |
| #include "content/browser/frame_host/render_frame_proxy_host.h" |
| #include "content/browser/gpu/compositor_util.h" |
| #include "content/browser/loader/resource_dispatcher_host_impl.h" |
| #include "content/browser/renderer_host/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/render_view_host_impl.h" |
| #include "content/browser/renderer_host/render_widget_host_input_event_router.h" |
| #include "content/browser/renderer_host/render_widget_host_view_child_frame.h" |
| #include "content/browser/storage_partition_impl.h" |
| #include "content/browser/url_loader_factory_getter.h" |
| #include "content/browser/web_contents/web_contents_impl.h" |
| #include "content/common/frame_messages.h" |
| #include "content/common/input/actions_parser.h" |
| #include "content/common/input/synthetic_pinch_gesture_params.h" |
| #include "content/common/input_messages.h" |
| #include "content/common/renderer.mojom.h" |
| #include "content/common/view_messages.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/interstitial_page_delegate.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_features.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/common/navigation_policy.h" |
| #include "content/public/common/url_constants.h" |
| #include "content/public/common/use_zoom_for_dsf_policy.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/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 "ipc/constants.mojom.h" |
| #include "ipc/ipc_security_test_util.h" |
| #include "media/base/media_switches.h" |
| #include "mojo/public/cpp/bindings/strong_binding.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "net/test/embedded_test_server/http_request.h" |
| #include "net/test/embedded_test_server/http_response.h" |
| #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" |
| #include "services/network/public/cpp/features.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.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/frame/sandbox_flags.h" |
| #include "third_party/blink/public/platform/web_input_event.h" |
| #include "third_party/blink/public/platform/web_insecure_request_policy.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/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_MACOSX) |
| #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/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/test/mock_overscroll_refresh_handler_android.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/env.h" |
| #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::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::Type type) override { |
| user_interaction_received_ = true; |
| } |
| |
| bool user_interaction_received_; |
| |
| DISALLOW_COPY_AND_ASSIGN(UserInteractionObserver); |
| }; |
| |
| // 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<MessageLoopRunner> message_loop_runner_; |
| ScopedObserver<RenderWidgetHost, RenderWidgetHostObserver> observer_; |
| bool was_observed_; |
| bool did_fail_; |
| RenderWidgetHost* render_widget_; |
| |
| DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostVisibilityObserver); |
| }; |
| |
| class TestInterstitialDelegate : public InterstitialPageDelegate { |
| private: |
| // InterstitialPageDelegate: |
| std::string GetHTMLContents() override { return "<p>Interstitial</p>"; } |
| }; |
| |
| bool ConvertJSONToPoint(const std::string& str, gfx::PointF* point) { |
| base::Optional<base::Value> value = base::JSONReader::Read(str); |
| if (!value.has_value()) |
| return false; |
| if (!value->is_dict()) |
| return false; |
| base::Optional<double> x = value->FindDoubleKey("x"); |
| base::Optional<double> 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) { |
| 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...".) |
| void SetParsedFeaturePolicyDeclaration( |
| blink::ParsedFeaturePolicyDeclaration* declaration, |
| blink::mojom::FeaturePolicyFeature feature, |
| const std::vector<GURL>& origins) { |
| declaration->feature = feature; |
| blink::mojom::PolicyValueType feature_type = |
| blink::FeaturePolicy::GetDefaultFeatureList().at(feature).second; |
| declaration->fallback_value = |
| blink::PolicyValue::CreateMinPolicyValue(feature_type); |
| declaration->opaque_value = declaration->fallback_value; |
| if (feature == blink::mojom::FeaturePolicyFeature::kOversizedImages) { |
| declaration->fallback_value.SetDoubleValue(2.0); |
| declaration->opaque_value.SetDoubleValue(2.0); |
| } |
| DCHECK(!origins.empty()); |
| for (const auto origin : origins) |
| declaration->values.insert(std::pair<url::Origin, blink::PolicyValue>( |
| url::Origin::Create(origin), |
| blink::PolicyValue::CreateMaxPolicyValue(feature_type))); |
| } |
| |
| blink::ParsedFeaturePolicy CreateFPHeader( |
| blink::mojom::FeaturePolicyFeature feature1, |
| blink::mojom::FeaturePolicyFeature feature2, |
| const std::vector<GURL>& origins) { |
| blink::ParsedFeaturePolicy result(2); |
| SetParsedFeaturePolicyDeclaration(&(result[0]), feature1, origins); |
| SetParsedFeaturePolicyDeclaration(&(result[1]), feature2, origins); |
| return result; |
| } |
| |
| // Helper function to generate a feature policy for a single feature which |
| // matches every origin. (Equivalent to the declared policy "feature1 *; |
| // feature2 *".) |
| blink::ParsedFeaturePolicy CreateFPHeaderMatchesAll( |
| blink::mojom::FeaturePolicyFeature feature1, |
| blink::mojom::FeaturePolicyFeature feature2) { |
| blink::ParsedFeaturePolicy result(2); |
| blink::mojom::PolicyValueType feature_type1 = |
| blink::FeaturePolicy::GetDefaultFeatureList().at(feature1).second; |
| blink::mojom::PolicyValueType feature_type2 = |
| blink::FeaturePolicy::GetDefaultFeatureList().at(feature2).second; |
| blink::PolicyValue max_value1 = |
| blink::PolicyValue::CreateMaxPolicyValue(feature_type1); |
| blink::PolicyValue max_value2 = |
| blink::PolicyValue::CreateMaxPolicyValue(feature_type2); |
| result[0].feature = feature1; |
| result[0].fallback_value = max_value1; |
| result[0].opaque_value = max_value1; |
| result[1].feature = feature2; |
| result[1].fallback_value = max_value2; |
| result[1].opaque_value = max_value2; |
| return result; |
| } |
| |
| // 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* script = R"( |
| function relayoutNonRecursiveForTestingViewportIntersection() { |
| var width = window.innerWidth; |
| var height = window.innerHeight * 0.75; |
| for (var i = 0; i < window.frames.length; i++) { |
| child = document.getElementById("child-" + i); |
| child.width = width; |
| child.height = height; |
| } |
| } |
| relayoutNonRecursiveForTestingViewportIntersection(); |
| )"; |
| EXPECT_TRUE(ExecuteScript(web_contents, script)); |
| } |
| |
| void GenerateTapDownGesture(RenderWidgetHost* rwh) { |
| blink::WebGestureEvent gesture_tap_down( |
| blink::WebGestureEvent::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: |
| UpdateViewportIntersectionMessageFilter() |
| : content::BrowserMessageFilter(FrameMsgStart), msg_received_(false) {} |
| |
| bool OnMessageReceived(const IPC::Message& message) override { |
| IPC_BEGIN_MESSAGE_MAP(UpdateViewportIntersectionMessageFilter, message) |
| IPC_MESSAGE_HANDLER(FrameHostMsg_UpdateViewportIntersection, |
| OnUpdateViewportIntersection) |
| IPC_END_MESSAGE_MAP() |
| return false; |
| } |
| |
| gfx::Rect GetCompositingRect() const { return compositing_rect_; } |
| gfx::Rect GetViewportIntersection() const { return viewport_intersection_; } |
| blink::FrameOcclusionState GetOcclusionState() const { |
| return occlusion_state_; |
| } |
| |
| void Wait() { |
| DCHECK(!run_loop_); |
| if (msg_received_) { |
| msg_received_ = false; |
| return; |
| } |
| std::unique_ptr<base::RunLoop> 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 gfx::Rect& viewport_intersection, |
| const gfx::Rect& compositing_rect, |
| blink::FrameOcclusionState occlusion_state) { |
| // The message is going to be posted to UI thread after |
| // OnUpdateViewportIntersection returns. This additional post on the IO |
| // thread guarantees that by the time OnUpdateViewportIntersectionOnUI runs, |
| // the message has been handled on the UI thread. |
| base::PostTaskWithTraits( |
| FROM_HERE, {content::BrowserThread::IO}, |
| base::BindOnce(&UpdateViewportIntersectionMessageFilter:: |
| OnUpdateViewportIntersectionPostOnIO, |
| this, viewport_intersection, compositing_rect, |
| occlusion_state)); |
| } |
| void OnUpdateViewportIntersectionPostOnIO( |
| const gfx::Rect& viewport_intersection, |
| const gfx::Rect& compositing_rect, |
| blink::FrameOcclusionState occlusion_state) { |
| base::PostTaskWithTraits( |
| FROM_HERE, {content::BrowserThread::UI}, |
| base::BindOnce(&UpdateViewportIntersectionMessageFilter:: |
| OnUpdateViewportIntersectionOnUI, |
| this, viewport_intersection, compositing_rect, |
| occlusion_state)); |
| } |
| void OnUpdateViewportIntersectionOnUI( |
| const gfx::Rect& viewport_intersection, |
| const gfx::Rect& compositing_rect, |
| blink::FrameOcclusionState occlusion_state) { |
| viewport_intersection_ = viewport_intersection; |
| compositing_rect_ = compositing_rect; |
| occlusion_state_ = occlusion_state; |
| msg_received_ = true; |
| if (run_loop_) |
| run_loop_->Quit(); |
| } |
| base::RunLoop* run_loop_ = nullptr; |
| bool msg_received_; |
| gfx::Rect compositing_rect_; |
| gfx::Rect viewport_intersection_; |
| blink::FrameOcclusionState occlusion_state_ = |
| blink::FrameOcclusionState::kUnknown; |
| DISALLOW_COPY_AND_ASSIGN(UpdateViewportIntersectionMessageFilter); |
| }; |
| |
| } // namespace |
| |
| // |
| // SitePerProcessBrowserTest |
| // |
| |
| SitePerProcessBrowserTest::SitePerProcessBrowserTest() {} |
| |
| std::string SitePerProcessBrowserTest::DepictFrameTree(FrameTreeNode* node) { |
| return visualizer_.DepictFrameTree(node); |
| } |
| |
| void SitePerProcessBrowserTest::SetUpCommandLine( |
| base::CommandLine* command_line) { |
| IsolateAllSitesForTesting(command_line); |
| |
| command_line->AppendSwitch(switches::kValidateInputEventStream); |
| |
| #if !defined(OS_ANDROID) |
| // TODO(bokan): Needed for scrollability check in |
| // FrameOwnerPropertiesPropagationScrolling. crbug.com/662196. |
| feature_list_.InitAndDisableFeature(features::kOverlayScrollbar); |
| #endif |
| } |
| |
| void SitePerProcessBrowserTest::SetUpOnMainThread() { |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| SetupCrossSiteRedirector(embedded_test_server()); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| } |
| |
| // |
| // SitePerProcessHighDPIBrowserTest |
| // |
| |
| class SitePerProcessHighDPIBrowserTest : public SitePerProcessBrowserTest { |
| public: |
| const double kDeviceScaleFactor = 2.0; |
| |
| SitePerProcessHighDPIBrowserTest() {} |
| |
| protected: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| SitePerProcessBrowserTest::SetUpCommandLine(command_line); |
| command_line->AppendSwitchASCII( |
| switches::kForceDeviceScaleFactor, |
| base::StringPrintf("%f", kDeviceScaleFactor)); |
| } |
| }; |
| |
| // SitePerProcessIgnoreCertErrorsBrowserTest |
| |
| class SitePerProcessIgnoreCertErrorsBrowserTest |
| : public SitePerProcessBrowserTest { |
| public: |
| SitePerProcessIgnoreCertErrorsBrowserTest() {} |
| |
| protected: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| SitePerProcessBrowserTest::SetUpCommandLine(command_line); |
| command_line->AppendSwitch(switches::kIgnoreCertificateErrors); |
| } |
| }; |
| |
| // SitePerProcessFeaturePolicyBrowserTest |
| |
| class SitePerProcessFeaturePolicyBrowserTest |
| : public SitePerProcessBrowserTest { |
| public: |
| SitePerProcessFeaturePolicyBrowserTest() = default; |
| |
| // Enable tests for parameterized features. |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| SitePerProcessBrowserTest::SetUpCommandLine(command_line); |
| command_line->AppendSwitchASCII("enable-blink-features", |
| "ExperimentalProductivityFeatures"); |
| } |
| }; |
| |
| // SitePerProcessFeaturePolicyJavaScriptBrowserTest |
| |
| class SitePerProcessFeaturePolicyJavaScriptBrowserTest |
| : public SitePerProcessBrowserTest { |
| public: |
| SitePerProcessFeaturePolicyJavaScriptBrowserTest() = default; |
| |
| // Enable the feature policy JavaScript interface |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| SitePerProcessBrowserTest::SetUpCommandLine(command_line); |
| command_line->AppendSwitchASCII( |
| "enable-blink-features", |
| "FeaturePolicyJavaScriptInterface,ExperimentalProductivityFeatures"); |
| } |
| }; |
| |
| // SitePerProcessAutoplayBrowserTest |
| |
| class SitePerProcessAutoplayBrowserTest : public SitePerProcessBrowserTest { |
| public: |
| SitePerProcessAutoplayBrowserTest() = default; |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| SitePerProcessBrowserTest::SetUpCommandLine(command_line); |
| command_line->AppendSwitchASCII( |
| switches::kAutoplayPolicy, |
| switches::autoplay::kDocumentUserActivationRequiredPolicy); |
| command_line->AppendSwitchASCII("enable-blink-features", |
| "FeaturePolicyAutoplayFeature"); |
| } |
| |
| bool AutoplayAllowed(const ToRenderFrameHost& adapter, |
| bool with_user_gesture) { |
| RenderFrameHost* rfh = adapter.render_frame_host(); |
| const char* test_script = "attemptPlay();"; |
| bool worked = false; |
| if (with_user_gesture) { |
| EXPECT_TRUE(ExecuteScriptAndExtractBool(rfh, test_script, &worked)); |
| } else { |
| EXPECT_TRUE(ExecuteScriptWithoutUserGestureAndExtractBool( |
| rfh, test_script, &worked)); |
| } |
| return worked; |
| } |
| |
| void NavigateFrameAndWait(FrameTreeNode* node, const GURL& url) { |
| NavigateFrameToURL(node, url); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| EXPECT_EQ(url, node->current_url()); |
| } |
| }; |
| |
| class SitePerProcesScrollAnchorTest : public SitePerProcessBrowserTest { |
| public: |
| SitePerProcesScrollAnchorTest() = default; |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| SitePerProcessBrowserTest::SetUpCommandLine(command_line); |
| command_line->AppendSwitchASCII("enable-blink-features", |
| "ScrollAnchorSerialization"); |
| } |
| }; |
| |
| // SitePerProcessEmbedderCSPEnforcementBrowserTest |
| |
| class SitePerProcessEmbedderCSPEnforcementBrowserTest |
| : public SitePerProcessBrowserTest { |
| public: |
| SitePerProcessEmbedderCSPEnforcementBrowserTest() {} |
| |
| protected: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| SitePerProcessBrowserTest::SetUpCommandLine(command_line); |
| // TODO(amalika): Remove this switch when the EmbedderCSPEnforcement becomes |
| // stable |
| command_line->AppendSwitchASCII(switches::kEnableBlinkFeatures, |
| "EmbedderCSPEnforcement"); |
| } |
| }; |
| |
| // 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<float>(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<std::string> 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<int>(x), static_cast<int>(y), static_cast<int>(width), |
| static_cast<int>(height)}; |
| } |
| |
| DISALLOW_COPY_AND_ASSIGN(SitePerProcessProgrammaticScrollTest); |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(SitePerProcessHighDPIBrowserTest, |
| SubframeLoadsWithCorrectDeviceScaleFactor) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(b)")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| // On Android forcing device scale factor does not work for tests, therefore |
| // we ensure that make frame and iframe have the same DIP scale there, but |
| // not necessarily kDeviceScaleFactor. |
| const double expected_dip_scale = |
| #if defined(OS_ANDROID) |
| GetFrameDeviceScaleFactor(web_contents()); |
| #else |
| SitePerProcessHighDPIBrowserTest::kDeviceScaleFactor; |
| #endif |
| |
| EXPECT_EQ(expected_dip_scale, GetFrameDeviceScaleFactor(web_contents())); |
| |
| FrameTreeNode* root = web_contents()->GetFrameTree()->root(); |
| ASSERT_EQ(1U, root->child_count()); |
| |
| FrameTreeNode* child = root->child_at(0); |
| EXPECT_EQ(expected_dip_scale, GetFrameDeviceScaleFactor(child)); |
| } |
| |
| #if defined(OS_CHROMEOS) |
| IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, |
| SubframeUpdateToCorrectDeviceScaleFactor) { |
| if (aura::Env::GetInstance()->mode() == aura::Env::Mode::MUS) |
| return; |
| |
| 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<aura::TestScreen*>(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 |
| |
| // Ensure that navigating subframes in --site-per-process mode works and the |
| // correct documents are committed. |
| IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, CrossSiteIframe) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(a,a(a,a(a)))")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| // It is safe to obtain the root frame tree node here, as it doesn't change. |
| FrameTreeNode* root = web_contents()->GetFrameTree()->root(); |
| |
| TestNavigationObserver observer(shell()->web_contents()); |
| |
| // Load same-site page into iframe. |
| FrameTreeNode* child = root->child_at(0); |
| GURL http_url(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| NavigateFrameToURL(child, http_url); |
| EXPECT_EQ(http_url, observer.last_navigation_url()); |
| EXPECT_TRUE(observer.last_navigation_succeeded()); |
| { |
| // There should be only one RenderWidgetHost when there are no |
| // cross-process iframes. |
| std::set<RenderWidgetHostView*> views_set = |
| web_contents()->GetRenderWidgetHostViewsInTree(); |
| EXPECT_EQ(1U, views_set.size()); |
| } |
| |
| EXPECT_EQ( |
| " Site A\n" |
| " |--Site A\n" |
| " +--Site A\n" |
| " |--Site A\n" |
| " +--Site A\n" |
| " +--Site A\n" |
| "Where A = http://a.com/", |
| DepictFrameTree(root)); |
| |
| // Load cross-site page into iframe. |
| GURL url = embedded_test_server()->GetURL("foo.com", "/title2.html"); |
| { |
| RenderFrameDeletedObserver deleted_observer(child->current_frame_host()); |
| NavigateFrameToURL(root->child_at(0), url); |
| deleted_observer.WaitUntilDeleted(); |
| } |
| // Verify that the navigation succeeded and the expected URL was loaded. |
| EXPECT_TRUE(observer.last_navigation_succeeded()); |
| EXPECT_EQ(url, observer.last_navigation_url()); |
| |
| // Ensure that we have created a new process for the subframe. |
| ASSERT_EQ(2U, root->child_count()); |
| SiteInstance* site_instance = child->current_frame_host()->GetSiteInstance(); |
| RenderViewHost* rvh = child->current_frame_host()->render_view_host(); |
| RenderProcessHost* rph = child->current_frame_host()->GetProcess(); |
| EXPECT_NE(shell()->web_contents()->GetRenderViewHost(), rvh); |
| EXPECT_NE(shell()->web_contents()->GetSiteInstance(), site_instance); |
| EXPECT_NE(shell()->web_contents()->GetMainFrame()->GetProcess(), rph); |
| { |
| // There should be now two RenderWidgetHosts, one for each process |
| // rendering a frame. |
| std::set<RenderWidgetHostView*> views_set = |
| web_contents()->GetRenderWidgetHostViewsInTree(); |
| EXPECT_EQ(2U, views_set.size()); |
| } |
| RenderFrameProxyHost* proxy_to_parent = |
| child->render_manager()->GetProxyToParent(); |
| EXPECT_TRUE(proxy_to_parent); |
| EXPECT_TRUE(proxy_to_parent->cross_process_frame_connector()); |
| // The out-of-process iframe should have its own RenderWidgetHost, |
| // independent of any RenderViewHost. |
| EXPECT_NE( |
| rvh->GetWidget()->GetView(), |
| proxy_to_parent->cross_process_frame_connector()->get_view_for_testing()); |
| EXPECT_TRUE(child->current_frame_host()->GetRenderWidgetHost()); |
| |
| EXPECT_EQ( |
| " Site A ------------ proxies for B\n" |
| " |--Site B ------- proxies for A\n" |
| " +--Site A ------- proxies for B\n" |
| " |--Site A -- proxies for B\n" |
| " +--Site A -- proxies for B\n" |
| " +--Site A -- proxies for B\n" |
| "Where A = http://a.com/\n" |
| " B = http://foo.com/", |
| DepictFrameTree(root)); |
| |
| // Load another cross-site page into the same iframe. |
| url = embedded_test_server()->GetURL("bar.com", "/title3.html"); |
| { |
| RenderFrameDeletedObserver deleted_observer(child->current_frame_host()); |
| NavigateFrameToURL(root->child_at(0), url); |
| deleted_observer.WaitUntilDeleted(); |
| } |
| EXPECT_TRUE(observer.last_navigation_succeeded()); |
| EXPECT_EQ(url, observer.last_navigation_url()); |
| |
| // Check again that a new process is created and is different from the |
| // top level one and the previous one. |
| ASSERT_EQ(2U, root->child_count()); |
| child = root->child_at(0); |
| EXPECT_NE(shell()->web_contents()->GetRenderViewHost(), |
| child->current_frame_host()->render_view_host()); |
| EXPECT_NE(rvh, child->current_frame_host()->render_view_host()); |
| EXPECT_NE(shell()->web_contents()->GetSiteInstance(), |
| child->current_frame_host()->GetSiteInstance()); |
| EXPECT_NE(site_instance, |
| child->current_frame_host()->GetSiteInstance()); |
| EXPECT_NE(shell()->web_contents()->GetMainFrame()->GetProcess(), |
| child->current_frame_host()->GetProcess()); |
| EXPECT_NE(rph, child->current_frame_host()->GetProcess()); |
| { |
| std::set<RenderWidgetHostView*> views_set = |
| web_contents()->GetRenderWidgetHostViewsInTree(); |
| EXPECT_EQ(2U, views_set.size()); |
| } |
| EXPECT_EQ(proxy_to_parent, child->render_manager()->GetProxyToParent()); |
| EXPECT_TRUE(proxy_to_parent->cross_process_frame_connector()); |
| EXPECT_NE( |
| child->current_frame_host()->render_view_host()->GetWidget()->GetView(), |
| proxy_to_parent->cross_process_frame_connector()->get_view_for_testing()); |
| EXPECT_TRUE(child->current_frame_host()->GetRenderWidgetHost()); |
| |
| EXPECT_EQ( |
| " Site A ------------ proxies for C\n" |
| " |--Site C ------- proxies for A\n" |
| " +--Site A ------- proxies for C\n" |
| " |--Site A -- proxies for C\n" |
| " +--Site A -- proxies for C\n" |
| " +--Site A -- proxies for C\n" |
| "Where A = http://a.com/\n" |
| " C = http://bar.com/", |
| DepictFrameTree(root)); |
| } |
| |
| // Ensure that title updates affect the correct NavigationEntry after a new |
| // subframe navigation with an out-of-process iframe. https://crbug.com/616609. |
| IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, TitleAfterCrossSiteIframe) { |
| // Start at an initial page. |
| GURL initial_url(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), initial_url)); |
| |
| // Navigate to a same-site page with a same-site iframe. |
| GURL main_url(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(a)")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| FrameTreeNode* root = web_contents()->GetFrameTree()->root(); |
| |
| // Make the main frame update its title after the subframe loads. |
| EXPECT_TRUE(ExecuteScript(shell()->web_contents(), |
| "document.querySelector('iframe').onload = " |
| " function() { document.title = 'loaded'; };")); |
| EXPECT_TRUE( |
| ExecuteScript(shell()->web_contents(), "document.title = 'not loaded';")); |
| base::string16 expected_title(base::UTF8ToUTF16("loaded")); |
| TitleWatcher title_watcher(shell()->web_contents(), expected_title); |
| |
| // Navigate the iframe cross-site. |
| TestNavigationObserver load_observer(shell()->web_contents()); |
| GURL frame_url = embedded_test_server()->GetURL("b.com", "/title2.html"); |
| EXPECT_TRUE(ExecuteScript(root->child_at(0)->current_frame_host(), |
| 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_F(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<WebContentsImpl*>(shell()->web_contents()) |
| ->GetFrameTree() |
| ->root(); |
| |
| ASSERT_EQ(1U, root->child_count()); |
| |
| FrameTreeNode* parent_iframe_node = root->child_at(0); |
| |
| EXPECT_EQ( |
| " Site A ------------ proxies for B\n" |
| " +--Site A ------- proxies for B\n" |
| " +--Site B -- proxies for A\n" |
| "Where A = http://a.com/\n" |
| " B = http://baz.com/", |
| DepictFrameTree(root)); |
| |
| FrameTreeNode* nested_iframe_node = parent_iframe_node->child_at(0); |
| RenderFrameProxyHost* proxy_to_parent = |
| nested_iframe_node->render_manager()->GetProxyToParent(); |
| CrossProcessFrameConnector* connector = |
| proxy_to_parent->cross_process_frame_connector(); |
| RenderWidgetHostViewBase* rwhv_nested = |
| static_cast<RenderWidgetHostViewBase*>( |
| nested_iframe_node->current_frame_host() |
| ->GetRenderWidgetHost() |
| ->GetView()); |
| |
| 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_F(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<WebContentsImpl*>(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_F(SitePerProcessBrowserTest, ViewBoundsInNestedFrameTest) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(a)")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| // It is safe to obtain the root frame tree node here, as it doesn't change. |
| FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetFrameTree() |
| ->root(); |
| RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>( |
| root->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| ASSERT_EQ(1U, root->child_count()); |
| |
| FrameTreeNode* parent_iframe_node = root->child_at(0); |
| GURL site_url(embedded_test_server()->GetURL( |
| "a.com", "/frame_tree/page_with_positioned_frame.html")); |
| NavigateFrameToURL(parent_iframe_node, site_url); |
| 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<RenderWidgetHostViewBase*>( |
| nested_iframe_node->current_frame_host() |
| ->GetRenderWidgetHost() |
| ->GetView()); |
| WaitForHitTestDataOrChildSurfaceReady( |
| 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<SynchronizeVisualPropertiesMessageFilter>(); |
| root->current_frame_host()->GetProcess()->AddFilter(filter.get()); |
| |
| // Scroll the parent frame downward to verify that the child rect gets updated |
| // correctly. |
| blink::WebMouseWheelEvent scroll_event( |
| blink::WebInputEvent::kMouseWheel, blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests()); |
| |
| scroll_event.SetPositionInWidget( |
| gfx::ToFlooredInt((bounds.x() - rwhv_root->GetViewBounds().x() - 5) * |
| scale_factor), |
| gfx::ToFlooredInt((bounds.y() - rwhv_root->GetViewBounds().y() - 5) * |
| scale_factor)); |
| scroll_event.delta_x = 0.0f; |
| scroll_event.delta_y = -30.0f; |
| scroll_event.phase = blink::WebMouseWheelEvent::kPhaseBegan; |
| rwhv_root->ProcessMouseWheelEvent(scroll_event, ui::LatencyInfo()); |
| filter->WaitForRect(); |
| |
| // The precise amount of scroll for the first view position update is not |
| // deterministic, so this simply verifies that the OOPIF moved from its |
| // earlier position. |
| gfx::Rect update_rect = filter->last_rect(); |
| EXPECT_LT(update_rect.y(), bounds.y() - rwhv_root->GetViewBounds().y()); |
| } |
| |
| // This test verifies that scroll bubbling from an OOPIF properly forwards |
| // GestureFlingStart events from the child frame to the parent frame. This |
| // test times out on failure. |
| IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, |
| GestureFlingStartEventsBubble) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(b)")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetFrameTree() |
| ->root(); |
| ASSERT_EQ(1U, root->child_count()); |
| |
| 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::kGestureFlingStart); |
| |
| WaitForHitTestDataOrChildSurfaceReady( |
| 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::kGestureScrollBegin, |
| blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests(), |
| blink::WebGestureDevice::kTouchscreen); |
| gesture_scroll_begin.data.scroll_begin.delta_hint_units = |
| blink::WebGestureEvent::ScrollUnits::kPrecisePixels; |
| gesture_scroll_begin.data.scroll_begin.delta_x_hint = 0.f; |
| gesture_scroll_begin.data.scroll_begin.delta_y_hint = 5.f; |
| |
| child_rwh->ForwardGestureEvent(gesture_scroll_begin); |
| |
| blink::WebGestureEvent gesture_scroll_update( |
| blink::WebGestureEvent::kGestureScrollUpdate, |
| blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests(), |
| blink::WebGestureDevice::kTouchscreen); |
| gesture_scroll_update.data.scroll_update.delta_units = |
| blink::WebGestureEvent::ScrollUnits::kPrecisePixels; |
| gesture_scroll_update.data.scroll_update.delta_x = 0.f; |
| gesture_scroll_update.data.scroll_update.delta_y = 5.f; |
| gesture_scroll_update.data.scroll_update.velocity_y = 5.f; |
| |
| child_rwh->ForwardGestureEvent(gesture_scroll_update); |
| |
| blink::WebGestureEvent gesture_fling_start( |
| blink::WebGestureEvent::kGestureFlingStart, |
| blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::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_F(SitePerProcessBrowserTest, ScrollBubblingFromOOPIFTest) { |
| ui::GestureConfiguration::GetInstance()->set_scroll_debounce_interval_in_ms( |
| 0); |
| GURL main_url(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(b)")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| // It is safe to obtain the root frame tree node here, as it doesn't change. |
| FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetFrameTree() |
| ->root(); |
| ASSERT_EQ(1U, root->child_count()); |
| |
| FrameTreeNode* parent_iframe_node = root->child_at(0); |
| |
| // This test uses the position of the nested iframe within the parent iframe |
| // to infer the scroll position of the parent. |
| // 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<SynchronizeVisualPropertiesMessageFilter>(); |
| parent_iframe_node->current_frame_host()->GetProcess()->AddFilter( |
| filter.get()); |
| |
| InputEventAckWaiter ack_observer( |
| parent_iframe_node->current_frame_host()->GetRenderWidgetHost(), |
| blink::WebInputEvent::kGestureScrollEnd); |
| |
| GURL site_url(embedded_test_server()->GetURL( |
| "b.com", "/frame_tree/page_with_positioned_frame.html")); |
| NavigateFrameToURL(parent_iframe_node, site_url); |
| |
| // Navigate the nested frame to a page large enough to have scrollbars. |
| FrameTreeNode* nested_iframe_node = parent_iframe_node->child_at(0); |
| GURL nested_site_url(embedded_test_server()->GetURL( |
| "baz.com", "/tall_page.html")); |
| NavigateFrameToURL(nested_iframe_node, nested_site_url); |
| |
| EXPECT_EQ( |
| " Site A ------------ proxies for B C\n" |
| " +--Site B ------- proxies for A C\n" |
| " +--Site C -- proxies for A B\n" |
| "Where A = http://a.com/\n" |
| " B = http://b.com/\n" |
| " C = http://baz.com/", |
| DepictFrameTree(root)); |
| |
| RenderWidgetHostViewBase* rwhv_parent = |
| static_cast<RenderWidgetHostViewBase*>( |
| parent_iframe_node->current_frame_host() |
| ->GetRenderWidgetHost() |
| ->GetView()); |
| |
| RenderWidgetHostViewBase* rwhv_nested = |
| static_cast<RenderWidgetHostViewBase*>( |
| nested_iframe_node->current_frame_host() |
| ->GetRenderWidgetHost() |
| ->GetView()); |
| |
| WaitForHitTestDataOrChildSurfaceReady( |
| nested_iframe_node->current_frame_host()); |
| |
| // Save the original offset as a point of reference. |
| filter->WaitForRect(); |
| gfx::Rect update_rect = filter->last_rect(); |
| int initial_y = update_rect.y(); |
| filter->ResetRectRunLoop(); |
| |
| // Scroll the parent frame downward. |
| blink::WebMouseWheelEvent scroll_event( |
| blink::WebInputEvent::kMouseWheel, blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests()); |
| scroll_event.SetPositionInWidget(1, 1); |
| scroll_event.delta_x = 0.0f; |
| scroll_event.delta_y = -5.0f; |
| scroll_event.phase = blink::WebMouseWheelEvent::kPhaseBegan; |
| // Set has_precise_scroll_deltas to keep these events off the animated scroll |
| // pathways, which currently break this test. |
| // https://bugs.chromium.org/p/chromium/issues/detail?id=710513 |
| scroll_event.has_precise_scrolling_deltas = true; |
| rwhv_parent->ProcessMouseWheelEvent(scroll_event, ui::LatencyInfo()); |
| |
| // 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::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::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::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_F(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<WebContentsImpl*>(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<RenderWidgetHostViewBase*>( |
| iframe_node->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| |
| 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::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<int>(ui::DomCode::ARROW_DOWN); |
| key_event.dom_key = ui::DomKey::ARROW_DOWN; |
| |
| rwhv_child->GetRenderWidgetHost()->ForwardKeyboardEvent(key_event); |
| |
| key_event.SetType(blink::WebKeyboardEvent::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_F(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<WebContentsImpl*>(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(); |
| WaitForHitTestDataOrChildSurfaceReady( |
| child_iframe_node->current_frame_host()); |
| |
| GenerateTapDownGesture(child_rwh); |
| // Send a GSB to start scrolling sequence. |
| blink::WebGestureEvent gesture_scroll_begin( |
| blink::WebGestureEvent::kGestureScrollBegin, |
| blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests()); |
| gesture_scroll_begin.SetSourceDevice(blink::WebGestureDevice::kTouchscreen); |
| gesture_scroll_begin.data.scroll_begin.delta_hint_units = |
| blink::WebGestureEvent::ScrollUnits::kPrecisePixels; |
| gesture_scroll_begin.data.scroll_begin.delta_x_hint = 0.f; |
| gesture_scroll_begin.data.scroll_begin.delta_y_hint = 5.f; |
| child_rwh->ForwardGestureEvent(gesture_scroll_begin); |
| |
| // 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::kGestureScrollUpdate); |
| gesture_scroll_update_ack_observer.Reset(); |
| blink::WebGestureEvent gesture_fling_start( |
| blink::WebGestureEvent::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_F(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<WebContentsImpl*>(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::kGestureScrollBegin); |
| blink::WebMouseWheelEvent scroll_event( |
| blink::WebInputEvent::kMouseWheel, blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests()); |
| scroll_event.delta_x = 0.0f; |
| scroll_event.delta_y = 5.0f; |
| scroll_event.phase = blink::WebMouseWheelEvent::kPhaseBegan; |
| scroll_event.has_precise_scrolling_deltas = true; |
| 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::kGestureScrollUpdate); |
| gesture_scroll_update_ack_observer.Reset(); |
| blink::WebGestureEvent gesture_fling_start( |
| blink::WebGestureEvent::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::kGestureScrollUpdate) { |
| blink::WebGestureEvent received_update = |
| *static_cast<const blink::WebGestureEvent*>(&event); |
| remaining_delta_x_ -= received_update.data.scroll_update.delta_x; |
| remaining_delta_y_ -= received_update.data.scroll_update.delta_y; |
| } else if (event.GetType() == blink::WebInputEvent::kGestureScrollEnd) { |
| if (message_loop_runner_->loop_running()) |
| message_loop_runner_->Quit(); |
| DCHECK_EQ(0, remaining_delta_x_); |
| DCHECK_EQ(0, remaining_delta_y_); |
| scroll_end_received_ = true; |
| } |
| } |
| |
| void Wait() { |
| if (!scroll_end_received_) { |
| message_loop_runner_->Run(); |
| } |
| } |
| |
| void Reset(double delta_x, double delta_y) { |
| message_loop_runner_ = new content::MessageLoopRunner; |
| remaining_delta_x_ = delta_x; |
| remaining_delta_y_ = delta_y; |
| scroll_end_received_ = false; |
| } |
| |
| private: |
| scoped_refptr<content::MessageLoopRunner> message_loop_runner_; |
| double remaining_delta_x_; |
| double remaining_delta_y_; |
| bool scroll_end_received_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ScrollObserver); |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, |
| ScrollBubblingFromNestedOOPIFTest) { |
| ui::GestureConfiguration::GetInstance()->set_scroll_debounce_interval_in_ms( |
| 0); |
| GURL main_url(embedded_test_server()->GetURL( |
| "/frame_tree/page_with_positioned_nested_frames.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| 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<RenderWidgetHostViewBase*>( |
| root->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| |
| RenderWidgetHostViewBase* rwhv_nested = |
| static_cast<RenderWidgetHostViewBase*>( |
| nested_iframe_node->current_frame_host() |
| ->GetRenderWidgetHost() |
| ->GetView()); |
| |
| WaitForHitTestDataOrChildSurfaceReady( |
| nested_iframe_node->current_frame_host()); |
| |
| InputEventAckWaiter ack_observer( |
| root->current_frame_host()->GetRenderWidgetHost(), |
| blink::WebInputEvent::kGestureScrollBegin); |
| |
| std::unique_ptr<ScrollObserver> 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<ScrollObserver>(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::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( |
| gfx::ToCeiledInt((bounds.x() - root_view->GetViewBounds().x() + 10) * |
| scale_factor), |
| gfx::ToCeiledInt((bounds.y() - root_view->GetViewBounds().y() + 10) * |
| scale_factor)); |
| scroll_event.delta_x = 0.0f; |
| scroll_event.delta_y = 5.0f; |
| scroll_event.phase = blink::WebMouseWheelEvent::kPhaseBegan; |
| scroll_event.has_precise_scrolling_deltas = true; |
| rwhv_nested->ProcessMouseWheelEvent(scroll_event, ui::LatencyInfo()); |
| ack_observer.Wait(); |
| |
| // 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_F(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); |
| WaitForHitTestDataOrChildSurfaceReady(iframe_node->current_frame_host()); |
| |
| RenderWidgetHostViewBase* root_view = static_cast<RenderWidgetHostViewBase*>( |
| root->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| |
| RenderWidgetHostViewBase* child_view = static_cast<RenderWidgetHostViewBase*>( |
| 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::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( |
| gfx::ToCeiledInt((bounds.x() - root_view->GetViewBounds().x() + 10) * |
| scale_factor), |
| gfx::ToCeiledInt((bounds.y() - root_view->GetViewBounds().y() + 10) * |
| scale_factor)); |
| scroll_event.delta_x = 0.0f; |
| scroll_event.delta_y = -5.0f; |
| scroll_event.phase = blink::WebMouseWheelEvent::kPhaseBegan; |
| scroll_event.has_precise_scrolling_deltas = true; |
| 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_F(SitePerProcessBrowserTest, ScrollLocalSubframeInOOPIF) { |
| ui::GestureConfiguration::GetInstance()->set_scroll_debounce_interval_in_ms( |
| 0); |
| |
| // This must be tall enough such that the outer iframe is not scrollable. |
| GURL main_url(embedded_test_server()->GetURL( |
| "a.com", "/frame_tree/page_with_tall_positioned_frame.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| // It is safe to obtain the root frame tree node here, as it doesn't change. |
| FrameTreeNode* root = web_contents()->GetFrameTree()->root(); |
| ASSERT_EQ(1U, root->child_count()); |
| |
| FrameTreeNode* parent_iframe_node = root->child_at(0); |
| GURL outer_frame_url(embedded_test_server()->GetURL( |
| "baz.com", "/frame_tree/page_with_positioned_frame.html")); |
| NavigateFrameToURL(parent_iframe_node, outer_frame_url); |
| |
| // This must be tall enough such that the inner iframe is scrollable. |
| FrameTreeNode* nested_iframe_node = parent_iframe_node->child_at(0); |
| GURL inner_frame_url( |
| embedded_test_server()->GetURL("baz.com", "/tall_page.html")); |
| NavigateFrameToURL(nested_iframe_node, inner_frame_url); |
| |
| ASSERT_EQ( |
| " Site A ------------ proxies for B\n" |
| " +--Site B ------- proxies for A\n" |
| " +--Site B -- proxies for A\n" |
| "Where A = http://a.com/\n" |
| " B = http://baz.com/", |
| DepictFrameTree(root)); |
| |
| RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>( |
| nested_iframe_node->current_frame_host() |
| ->GetRenderWidgetHost() |
| ->GetView()); |
| |
| WaitForHitTestDataOrChildSurfaceReady( |
| parent_iframe_node->current_frame_host()); |
| |
| // When we scroll the inner frame, we should have the GSB be consumed. |
| // The outer iframe not being scrollable should not cause the GSB to go |
| // unconsumed. |
| InputEventAckWaiter ack_observer( |
| parent_iframe_node->current_frame_host()->GetRenderWidgetHost(), |
| base::BindRepeating([](content::InputEventAckSource, |
| content::InputEventAckState state, |
| const blink::WebInputEvent& event) { |
| return event.GetType() == blink::WebGestureEvent::kGestureScrollBegin && |
| state == content::INPUT_EVENT_ACK_STATE_CONSUMED; |
| })); |
| |
| // Wait until renderer's compositor thread is synced. Otherwise the non fast |
| // scrollable regions won't be set when the event arrives. |
| MainThreadFrameObserver observer(rwhv_child->GetRenderWidgetHost()); |
| observer.Wait(); |
| |
| // Now scroll the inner frame downward. |
| blink::WebMouseWheelEvent scroll_event( |
| blink::WebInputEvent::kMouseWheel, blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests()); |
| scroll_event.SetPositionInWidget(90, 110); |
| scroll_event.delta_x = 0.0f; |
| scroll_event.delta_y = -50.0f; |
| scroll_event.phase = blink::WebMouseWheelEvent::kPhaseBegan; |
| scroll_event.has_precise_scrolling_deltas = true; |
| rwhv_child->ProcessMouseWheelEvent(scroll_event, ui::LatencyInfo()); |
| ack_observer.Wait(); |
| } |
| |
| // This test verifies that scrolling an element to view works across OOPIFs. The |
| // testing methodology is based on measuring bounding client rect position of |
| // nested <iframe>'s after the inner-most frame scrolls into view. The |
| // measurements are for two identical pages where one page does not have any |
| // OOPIFs while the other has some nested OOPIFs. |
| // TODO(crbug.com/827431): This test is flaking on all platforms. |
| IN_PROC_BROWSER_TEST_F(SitePerProcessProgrammaticScrollTest, |
| DISABLED_ScrollElementIntoView) { |
| const GURL url_a( |
| embedded_test_server()->GetURL("a.com", kIframeOutOfViewHTML)); |
| const GURL url_b( |
| embedded_test_server()->GetURL("b.com", kIframeOutOfViewHTML)); |
| const GURL url_c( |
| embedded_test_server()->GetURL("c.com", kIframeOutOfViewHTML)); |
| |
| // Number of <iframe>'s which will not be empty. The actual frame tree has two |
| // more nodes one for root and one for the inner-most empty <iframe>. |
| const size_t kNonEmptyIframesCount = 5; |
| const std::string kScrollIntoViewScript = |
| "document.body.scrollIntoView({'behavior' : 'instant'});"; |
| const int kRectDimensionErrorTolerance = 0; |
| |
| // First, recursively set the |scrollTop| and |scrollLeft| of |document.body| |
| // to its maximum and then navigate the <iframe> to |url_a|. The page will be |
| // structured as a(a(a(a(a(a(a)))))) where the inner-most <iframe> is empty. |
| ASSERT_TRUE(NavigateToURL(shell(), url_a)); |
| FrameTreeNode* node = web_contents()->GetFrameTree()->root(); |
| WaitForOnLoad(node); |
| std::vector<gfx::Rect> reference_page_bounds_before_scroll = { |
| GetBoundingClientRect(node, kIframeSelector)}; |
| node = node->child_at(0); |
| for (size_t index = 0; index < kNonEmptyIframesCount; ++index) { |
| NavigateFrameToURL(node, url_a); |
| WaitForOnLoad(node); |
| // Store |document.querySelector('iframe').getBoundingClientRect()|. |
| reference_page_bounds_before_scroll.push_back( |
| GetBoundingClientRect(node, kIframeSelector)); |
| node = node->child_at(0); |
| } |
| // Sanity-check: If the page is setup properly then all the <iframe>s should |
| // be out of view and their bounding rect should not intersect with the |
| // positive XY plane. |
| for (const auto& rect : reference_page_bounds_before_scroll) |
| ASSERT_FALSE(rect.Intersects(kPositiveXYPlane)); |
| // Now scroll the inner-most frame into view. |
| ASSERT_TRUE(ExecuteScript(node, kScrollIntoViewScript)); |
| // Store current client bounds origins to later compare against those from the |
| // page which contains OOPIFs. |
| node = web_contents()->GetFrameTree()->root(); |
| std::vector<gfx::Rect> reference_page_bounds_after_scroll = { |
| GetBoundingClientRect(node, kIframeSelector)}; |
| node = node->child_at(0); |
| for (size_t index = 0; index < kNonEmptyIframesCount; ++index) { |
| reference_page_bounds_after_scroll.push_back( |
| GetBoundingClientRect(node, kIframeSelector)); |
| node = node->child_at(0); |
| } |
| |
| // Repeat the same process for the page containing OOPIFs. The page is |
| // structured as b(b(a(c(a(a(a)))))) where the inner-most <iframe> is empty. |
| ASSERT_TRUE(NavigateToURL(shell(), url_b)); |
| node = web_contents()->GetFrameTree()->root(); |
| WaitForOnLoad(node); |
| std::vector<gfx::Rect> test_page_bounds_before_scroll = { |
| GetBoundingClientRect(node, kIframeSelector)}; |
| const GURL iframe_urls[] = {url_b, url_a, url_c, url_a, url_a}; |
| node = node->child_at(0); |
| for (size_t index = 0; index < kNonEmptyIframesCount; ++index) { |
| NavigateFrameToURL(node, iframe_urls[index]); |
| WaitForOnLoad(node); |
| test_page_bounds_before_scroll.push_back( |
| GetBoundingClientRect(node, kIframeSelector)); |
| node = node->child_at(0); |
| } |
| // Sanity-check: The bounds should match those from non-OOPIF page. |
| for (size_t index = 0; index < kNonEmptyIframesCount; ++index) { |
| ASSERT_TRUE(test_page_bounds_before_scroll[index].ApproximatelyEqual( |
| reference_page_bounds_before_scroll[index], |
| kRectDimensionErrorTolerance)); |
| } |
| // Scroll the inner most OOPIF. |
| ASSERT_TRUE(ExecuteScript(node, kScrollIntoViewScript)); |
| // Now traverse the chain bottom to top and verify the bounds match for each |
| // <iframe>. |
| int index = kNonEmptyIframesCount; |
| RenderFrameHostImpl* current_rfh = node->current_frame_host()->GetParent(); |
| while (current_rfh) { |
| gfx::Rect current_bounds = |
| GetBoundingClientRect(current_rfh->frame_tree_node(), kIframeSelector); |
| gfx::Rect reference_bounds = reference_page_bounds_after_scroll[index]; |
| if (current_bounds.ApproximatelyEqual(reference_bounds, |
| kRectDimensionErrorTolerance)) { |
| current_rfh = current_rfh->GetParent(); |
| --index; |
| } else { |
| base::RunLoop run_loop; |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout()); |
| run_loop.Run(); |
| } |
| } |
| } |
| |
| // This test verifies that ScrollFocusedEditableElementIntoView works correctly |
| // for OOPIFs. Essentially, the test verifies that in a similar setup, the |
| // resultant page scale factor is the same for OOPIF and non-OOPIF cases. This |
| // also verifies that in response to the scroll command, the root-layer scrolls |
| // correctly and the <input> is visible in visual viewport. |
| #if defined(OS_ANDROID) |
| // crbug.com/793616 |
| #define MAYBE_ScrollFocusedEditableElementIntoView \ |
| DISABLED_ScrollFocusedEditableElementIntoView |
| #else |
| #define MAYBE_ScrollFocusedEditableElementIntoView \ |
| ScrollFocusedEditableElementIntoView |
| #endif |
| IN_PROC_BROWSER_TEST_F(SitePerProcessProgrammaticScrollTest, |
| MAYBE_ScrollFocusedEditableElementIntoView) { |
| GURL url_a(embedded_test_server()->GetURL("a.com", kIframeOutOfViewHTML)); |
| GURL url_b(embedded_test_server()->GetURL("b.com", kIframeOutOfViewHTML)); |
| |
| #if defined(OS_ANDROID) |
| // The reason for Android specific code is that |
| // AutoZoomFocusedNodeToLegibleScale is in blink's WebSettings and difficult |
| // to access from here. It so happens that the setting is on for Android. |
| |
| // A lower bound on the ratio of page scale factor after scroll. The actual |
| // value depends on minReadableCaretHeight / caret_bounds.Height(). The page |
| // is setup so caret height is quite small so the expected scale should be |
| // larger than 2.0. |
| float kLowerBoundOnScaleAfterScroll = 2.0; |
| float kEpsilon = 0.1; |
| #endif |
| |
| ASSERT_TRUE(NavigateToURL(shell(), url_a)); |
| FrameTreeNode* root = web_contents()->GetFrameTree()->root(); |
| WaitForOnLoad(root); |
| NavigateFrameToURL(root->child_at(0), url_a); |
| WaitForOnLoad(root->child_at(0)); |
| #if defined(OS_ANDROID) |
| float scale_before_scroll_nonoopif = GetVisualViewportScale(root); |
| #endif |
| AddFocusedInputField(root->child_at(0)); |
| // Focusing <input> causes scrollIntoView(). The following line makes sure |
| // that the <iframe> is out of view again. |
| SetWindowScroll(root, 0, 0); |
| ASSERT_FALSE(GetVisualViewport(root).Intersects( |
| GetBoundingClientRect(root, kIframeSelector))); |
| root->child_at(0) |
| ->current_frame_host() |
| ->GetFrameInputHandler() |
| ->ScrollFocusedEditableNodeIntoRect(gfx::Rect()); |
| WaitForElementVisible(root, kIframeSelector); |
| #if defined(OS_ANDROID) |
| float scale_after_scroll_nonoopif = GetVisualViewportScale(root); |
| // Increased scale means zoom triggered correctly. |
| EXPECT_GT(scale_after_scroll_nonoopif - scale_before_scroll_nonoopif, |
| kEpsilon); |
| EXPECT_GT(scale_after_scroll_nonoopif, kLowerBoundOnScaleAfterScroll); |
| #endif |
| |
| // Retry the test on an OOPIF page. |
| Shell* new_shell = CreateBrowser(); |
| ASSERT_TRUE(NavigateToURL(new_shell, url_b)); |
| root = static_cast<WebContentsImpl*>(new_shell->web_contents()) |
| ->GetFrameTree() |
| ->root(); |
| WaitForOnLoad(root); |
| #if defined(OS_ANDROID) |
| float scale_before_scroll_oopif = GetVisualViewportScale(root); |
| // Sanity-check: |
| ASSERT_NEAR(scale_before_scroll_oopif, scale_before_scroll_nonoopif, |
| kEpsilon); |
| #endif |
| NavigateFrameToURL(root->child_at(0), url_a); |
| WaitForOnLoad(root->child_at(0)); |
| AddFocusedInputField(root->child_at(0)); |
| SetWindowScroll(root, 0, 0); |
| ASSERT_FALSE(GetVisualViewport(root).Intersects( |
| GetBoundingClientRect(root, kIframeSelector))); |
| root->child_at(0) |
| ->current_frame_host() |
| ->GetFrameInputHandler() |
| ->ScrollFocusedEditableNodeIntoRect(gfx::Rect()); |
| WaitForElementVisible(root, kIframeSelector); |
| #if defined(OS_ANDROID) |
| float scale_after_scroll_oopif = GetVisualViewportScale(root); |
| EXPECT_GT(scale_after_scroll_oopif - scale_before_scroll_oopif, kEpsilon); |
| EXPECT_GT(scale_after_scroll_oopif, kLowerBoundOnScaleAfterScroll); |
| // The scale is based on the caret height and it should be the same in both |
| // OOPIF and non-OOPIF pages. |
| EXPECT_NEAR(scale_after_scroll_oopif, scale_after_scroll_nonoopif, kEpsilon); |
| #endif |
| // Make sure the <input> is at least partly visible in the |visualViewport|. |
| gfx::Rect final_visual_viewport_oopif = GetVisualViewport(root); |
| gfx::Rect iframe_bounds_after_scroll_oopif = |
| GetBoundingClientRect(root, kIframeSelector); |
| gfx::Rect input_bounds_after_scroll_oopif = |
| GetBoundingClientRect(root->child_at(0), kInputSelector); |
| input_bounds_after_scroll_oopif += |
| iframe_bounds_after_scroll_oopif.OffsetFromOrigin(); |
| ASSERT_TRUE( |
| final_visual_viewport_oopif.Intersects(input_bounds_after_scroll_oopif)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SitePerProcessProgrammaticScrollTest, |
| ScrollClippedFocusedEditableElementIntoView) { |
| GURL url_a(embedded_test_server()->GetURL("a.com", kIframeClippedHTML)); |
| GURL child_url_b(embedded_test_server()->GetURL("b.com", kInputBoxHTML)); |
| |
| ASSERT_TRUE(NavigateToURL(shell(), url_a)); |
| FrameTreeNode* root = web_contents()->GetFrameTree()->root(); |
| WaitForOnLoad(root); |
| NavigateFrameToURL(root->child_at(0), child_url_b); |
| WaitForOnLoad(root->child_at(0)); |
| |
| SetWindowScroll(root, 0, 0); |
| SetWindowScroll(root->child_at(0), 1000, 2000); |
| |
| float scale_before = GetVisualViewportScale(root); |
| |
| // The input_box page focuses the input box on load. This call should |
| // simulate the scroll into view we do when an input box is tapped. |
| root->child_at(0) |
| ->current_frame_host() |
| ->GetFrameInputHandler() |
| ->ScrollFocusedEditableNodeIntoRect(gfx::Rect()); |
| |
| // The scroll into view is animated on the compositor. Make sure we wait |
| // until that's completed before testing the rects. |
| WaitForElementVisible(root, kIframeSelector); |
| WaitForViewportToStabilize(root); |
| |
| // These rects are in the coordinate space of the root frame. |
| gfx::Rect visual_viewport_rect = GetVisualViewport(root); |
| gfx::Rect window_rect = GetBoundingClientRect(root, ":root"); |
| gfx::Rect iframe_rect = GetBoundingClientRect(root, "iframe"); |
| gfx::Rect clip_rect = GetBoundingClientRect(root, "#clip"); |
| |
| // This is in the coordinate space of the iframe, we'll add the iframe offset |
| // after to put it into the root frame's coordinate space. |
| gfx::Rect input_rect = GetBoundingClientRect(root->child_at(0), "input"); |
| |
| // Make sure the input rect is visible in the iframe. |
| EXPECT_TRUE(gfx::Rect(iframe_rect.size()).Intersects(input_rect)) |
| << "Input box [" << input_rect.ToString() << "] isn't visible in iframe [" |
| << gfx::Rect(iframe_rect.size()).ToString() << "]"; |
| |
| input_rect += iframe_rect.OffsetFromOrigin(); |
| |
| // Make sure the input rect is visible through the clipping layer. |
| EXPECT_TRUE(clip_rect.Intersects(input_rect)) |
| << "Input box [" << input_rect.ToString() << "] isn't scrolled into view " |
| << "of the clipping layer [" << clip_rect.ToString() << "]"; |
| |
| // And finally, it should be visible in the layout and visual viewports. |
| EXPECT_TRUE(window_rect.Intersects(input_rect)) |
| << "Input box [" << input_rect.ToString() << "] isn't visible in the " |
| << "layout viewport [" << window_rect.ToString() << "]"; |
| EXPECT_TRUE(visual_viewport_rect.Intersects(input_rect)) |
| << "Input box [" << input_rect.ToString() << "] isn't visible in the " |
| << "visual viewport [" << visual_viewport_rect.ToString() << "]"; |
| |
| float scale_after = GetVisualViewportScale(root); |
| |
| // Make sure we still zoom in on the input box on platforms that zoom into the |
| // focused editable. |
| #if defined(OS_ANDROID) |
| EXPECT_GT(scale_after, scale_before); |
| #else |
| EXPECT_FLOAT_EQ(scale_after, scale_before); |
| #endif |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SitePerProcessProgrammaticScrollTest, |
| ScrolledOutOfView) { |
| GURL main_frame( |
| embedded_test_server()->GetURL("a.com", kIframeOutOfViewHTML)); |
| GURL child_url_b( |
| embedded_test_server()->GetURL("b.com", kIframeOutOfViewHTML)); |
| |
| // This will set up the page frame tree as A(B()). |
| ASSERT_TRUE(NavigateToURL(shell(), main_frame)); |
| FrameTreeNode* root = web_contents()->GetFrameTree()->root(); |
| WaitForOnLoad(root); |
| NavigateFrameToURL(root->child_at(0), child_url_b); |
| WaitForOnLoad(root->child_at(0)); |
| |
| FrameTreeNode* nested_iframe_node = root->child_at(0); |
| RenderFrameProxyHost* proxy_to_parent = |
| nested_iframe_node->render_manager()->GetProxyToParent(); |
| CrossProcessFrameConnector* connector = |
| proxy_to_parent->cross_process_frame_connector(); |
| |
| while (blink::mojom::FrameVisibility::kRenderedOutOfViewport != |
| connector->visibility()) { |
| base::RunLoop run_loop; |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout()); |
| run_loop.Run(); |
| } |
| } |
| |
| // This test verifies that smooth scrolling works correctly inside nested OOPIFs |
| // which are same origin with the parent. Note that since the frame tree has |
| // a A(B(A1())) structure, if and A1 and A2 shared the same |
| // SmoothScrollSequencer, then this test would time out or at best be flaky with |
| // random time outs. See https://crbug.com/865446 for more context. |
| IN_PROC_BROWSER_TEST_F(SitePerProcessProgrammaticScrollTest, |
| SmoothScrollInNestedSameProcessOOPIF) { |
| GURL main_frame( |
| embedded_test_server()->GetURL("a.com", kIframeOutOfViewHTML)); |
| GURL child_url_b( |
| embedded_test_server()->GetURL("b.com", kIframeOutOfViewHTML)); |
| GURL same_origin( |
| embedded_test_server()->GetURL("a.com", kIframeOutOfViewHTML)); |
| |
| // This will set up the page frame tree as A(B(A1(A2()))) where A1 is later |
| // asked to scroll the <iframe> element of A2 into view. The important bit |
| // here is that the inner frame A1 is recursively scrolling (smoothly) an |
| // element inside its document into view (A2's origin is irrelevant here). |
| ASSERT_TRUE(NavigateToURL(shell(), main_frame)); |
| FrameTreeNode* root = web_contents()->GetFrameTree()->root(); |
| WaitForOnLoad(root); |
| NavigateFrameToURL(root->child_at(0), child_url_b); |
| WaitForOnLoad(root->child_at(0)); |
| auto* nested_ftn = root->child_at(0)->child_at(0); |
| NavigateFrameToURL(nested_ftn, same_origin); |
| WaitForOnLoad(nested_ftn); |
| |
| // *Smoothly* scroll the inner most frame into view. |
| ASSERT_TRUE(ExecuteScript( |
| nested_ftn, |
| "document.querySelector('iframe').scrollIntoView({behavior: 'smooth'})")); |
| WaitForElementVisible(root, kIframeSelector); |
| WaitForElementVisible(root->child_at(0), kIframeSelector); |
| WaitForElementVisible(nested_ftn, kIframeSelector); |
| } |
| |
| // Tests OOPIF rendering by checking that the RWH of the iframe generates |
| // OnSwapCompositorFrame message. |
| IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, CompositorFrameSwapped) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(baz)")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| // It is safe to obtain the root frame tree node here, as it doesn't change. |
| FrameTreeNode* root = web_contents()->GetFrameTree()->root(); |
| ASSERT_EQ(1U, root->child_count()); |
| |
| FrameTreeNode* child_node = root->child_at(0); |
| GURL site_url(embedded_test_server()->GetURL( |
| "baz.com", "/cross_site_iframe_factory.html?baz()")); |
| EXPECT_EQ(site_url, child_node->current_url()); |
| EXPECT_NE(shell()->web_contents()->GetSiteInstance(), |
| child_node->current_frame_host()->GetSiteInstance()); |
| // Wait for CompositorFrame submission. |
| RenderFrameSubmissionObserver observer( |
| child_node->current_frame_host() |
| ->GetRenderWidgetHost() |
| ->render_frame_metadata_provider()); |
| observer.WaitForAnyFrameSubmission(); |
| } |
| |
| // Ensure that OOPIFs are deleted after navigating to a new main frame. |
| IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, CleanupCrossSiteIframe) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(a,a(a,a(a)))")); |
| 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 a cross-site page into both iframes. |
| GURL foo_url = embedded_test_server()->GetURL("foo.com", "/title2.html"); |
| NavigateFrameToURL(root->child_at(0), foo_url); |
| EXPECT_TRUE(observer.last_navigation_succeeded()); |
| EXPECT_EQ(foo_url, observer.last_navigation_url()); |
| NavigateFrameToURL(root->child_at(1), foo_url); |
| EXPECT_TRUE(observer.last_navigation_succeeded()); |
| EXPECT_EQ(foo_url, observer.last_navigation_url()); |
| |
| // Ensure that we have created a new process for the subframes. |
| EXPECT_EQ( |
| " Site A ------------ proxies for B\n" |
| " |--Site B ------- proxies for A\n" |
| " +--Site B ------- proxies for A\n" |
| "Where A = http://a.com/\n" |
| " B = http://foo.com/", |
| DepictFrameTree(root)); |
| |
| int subframe_process_id = root->child_at(0) |
| ->current_frame_host() |
| ->GetSiteInstance() |
| ->GetProcess() |
| ->GetID(); |
| int subframe_rvh_id = root->child_at(0) |
| ->current_frame_host() |
| ->render_view_host() |
| ->GetRoutingID(); |
| EXPECT_TRUE(RenderViewHost::FromID(subframe_process_id, subframe_rvh_id)); |
| |
| // Use Javascript in the parent to remove one of the frames and ensure that |
| // the subframe goes away. |
| EXPECT_TRUE(ExecuteScript(shell(), |
| "document.body.removeChild(" |
| "document.querySelectorAll('iframe')[0])")); |
| ASSERT_EQ(1U, root->child_count()); |
| |
| // Load a new same-site page in the top-level frame and ensure the other |
| // subframe goes away. |
| GURL new_url(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), new_url)); |
| ASSERT_EQ(0U, root->child_count()); |
| |
| // Ensure the RVH for the subframe gets cleaned up when the frame goes away. |
| EXPECT_FALSE(RenderViewHost::FromID(subframe_process_id, subframe_rvh_id)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, NavigateRemoteFrame) { |
| 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()); |
| |
| // 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(); |
| } |
| EXPECT_TRUE(observer.last_navigation_succeeded()); |
| EXPECT_EQ(url, observer.last_navigation_url()); |
| |
| // Ensure that we have created a new process for the subframe. |
| EXPECT_EQ( |
| " Site A ------------ proxies for B\n" |
| " |--Site B ------- proxies for A\n" |
| " +--Site A ------- proxies for B\n" |
| " |--Site A -- proxies for B\n" |
| " +--Site A -- proxies for B\n" |
| " +--Site A -- proxies for B\n" |
| "Where A = http://a.com/\n" |
| " B = http://foo.com/", |
| DepictFrameTree(root)); |
| SiteInstance* site_instance = child->current_frame_host()->GetSiteInstance(); |
| EXPECT_NE(shell()->web_contents()->GetSiteInstance(), site_instance); |
| |
| // Emulate the main frame changing the src of the iframe such that it |
| // navigates cross-site. |
| url = embedded_test_server()->GetURL("bar.com", "/title3.html"); |
| { |
| RenderFrameDeletedObserver deleted_observer(child->current_frame_host()); |
| NavigateIframeToURL(shell()->web_contents(), "child-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. |
| EXPECT_EQ( |
| " Site A ------------ proxies for C\n" |
| " |--Site C ------- proxies for A\n" |
| " +--Site A ------- proxies for C\n" |
| " |--Site A -- proxies for C\n" |
| " +--Site A -- proxies for C\n" |
| " +--Site A -- proxies for C\n" |
| "Where A = http://a.com/\n" |
| " C = http://bar.com/", |
| DepictFrameTree(root)); |
| |
| // Navigate back to the parent's origin and ensure we return to the |
| // parent's process. |
| { |
| RenderFrameDeletedObserver deleted_observer(child->current_frame_host()); |
| NavigateFrameToURL(child, http_url); |
| deleted_observer.WaitUntilDeleted(); |
| } |
| EXPECT_EQ(http_url, observer.last_navigation_url()); |
| EXPECT_TRUE(observer.last_navigation_succeeded()); |
| EXPECT_EQ(shell()->web_contents()->GetSiteInstance(), |
| child->current_frame_host()->GetSiteInstance()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, |
| NavigateRemoteFrameToBlankAndDataURLs) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(a,a(a))")); |
| 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()); |
| EXPECT_EQ( |
| " Site A\n" |
| " |--Site A\n" |
| " +--Site A\n" |
| " +--Site A\n" |
| "Where A = http://a.com/", |
| DepictFrameTree(root)); |
| |
| // Load cross-site page into iframe. |
| GURL url = embedded_test_server()->GetURL("foo.com", "/title2.html"); |
| NavigateFrameToURL(child, url); |
| EXPECT_TRUE(observer.last_navigation_succeeded()); |
| EXPECT_EQ(url, observer.last_navigation_url()); |
| EXPECT_EQ( |
| " Site A ------------ proxies for B\n" |
| " |--Site B ------- proxies for A\n" |
| " +--Site A ------- proxies for B\n" |
| " +--Site A -- proxies for B\n" |
| "Where A = http://a.com/\n" |
| " B = http://foo.com/", |
| DepictFrameTree(root)); |
| |
| // Navigate iframe to a data URL. The navigation happens from a script in the |
| // parent frame, so the data URL should be committed in the same SiteInstance |
| // as the parent frame. |
| RenderFrameDeletedObserver deleted_observer1( |
| root->child_at(0)->current_frame_host()); |
| GURL data_url("data:text/html,dataurl"); |
| NavigateIframeToURL(shell()->web_contents(), "child-0", data_url); |
| EXPECT_TRUE(observer.last_navigation_succeeded()); |
| EXPECT_EQ(data_url, observer.last_navigation_url()); |
| |
| // Wait for the old process to exit, to verify that the proxies go away. |
| deleted_observer1.WaitUntilDeleted(); |
| |
| // Ensure that we have navigated using the top level process. |
| EXPECT_EQ( |
| " Site A\n" |
| " |--Site A\n" |
| " +--Site A\n" |
| " +--Site A\n" |
| "Where A = http://a.com/", |
| DepictFrameTree(root)); |
| |
| // Load cross-site page into iframe. |
| url = embedded_test_server()->GetURL("bar.com", "/title2.html"); |
| NavigateFrameToURL(child, url); |
| EXPECT_TRUE(observer.last_navigation_succeeded()); |
| EXPECT_EQ(url, observer.last_navigation_url()); |
| EXPECT_EQ( |
| " Site A ------------ proxies for C\n" |
| " |--Site C ------- proxies for A\n" |
| " +--Site A ------- proxies for C\n" |
| " +--Site A -- proxies for C\n" |
| "Where A = http://a.com/\n" |
| " C = http://bar.com/", |
| DepictFrameTree(root)); |
| |
| // Navigate iframe to about:blank. The navigation happens from a script in the |
| // parent frame, so it should be committed in the same SiteInstance as the |
| // parent frame. |
| RenderFrameDeletedObserver deleted_observer2( |
| root->child_at(0)->current_frame_host()); |
| GURL about_blank_url("about:blank#foo"); |
| NavigateIframeToURL(shell()->web_contents(), "child-0", about_blank_url); |
| EXPECT_TRUE(observer.last_navigation_succeeded()); |
| EXPECT_EQ(about_blank_url, observer.last_navigation_url()); |
| |
| // Wait for the old process to exit, to verify that the proxies go away. |
| deleted_observer2.WaitUntilDeleted(); |
| |
| // Ensure that we have navigated using the top level process. |
| EXPECT_EQ( |
| " Site A\n" |
| " |--Site A\n" |
| " +--Site A\n" |
| " +--Site A\n" |
| "Where A = http://a.com/", |
| DepictFrameTree(root)); |
| |
| // Load cross-site page into iframe again. |
| url = embedded_test_server()->GetURL("f00.com", "/title3.html"); |
| NavigateFrameToURL(child, url); |
| EXPECT_TRUE(observer.last_navigation_succeeded()); |
| EXPECT_EQ(url, observer.last_navigation_url()); |
| EXPECT_EQ( |
| " Site A ------------ proxies for D\n" |
| " |--Site D ------- proxies for A\n" |
| " +--Site A ------- proxies for D\n" |
| " +--Site A -- proxies for D\n" |
| "Where A = http://a.com/\n" |
| " D = http://f00.com/", |
| DepictFrameTree(root)); |
| |
| // Navigate the iframe itself to about:blank using a script executing in its |
| // own context. It should stay in the same SiteInstance as before, not the |
| // parent one. |
| TestFrameNavigationObserver frame_observer(child); |
| ExecuteScriptAsync(child, "window.location.href = 'about:blank#foo';"); |
| frame_observer.Wait(); |
| EXPECT_EQ(about_blank_url, child->current_url()); |
| |
| // Ensure that we have navigated using the top level process. |
| EXPECT_EQ( |
| " Site A ------------ proxies for D\n" |
| " |--Site D ------- proxies for A\n" |
| " +--Site A ------- proxies for D\n" |
| " +--Site A -- proxies for D\n" |
| "Where A = http://a.com/\n" |
| " D = http://f00.com/", |
| DepictFrameTree(root)); |
| } |
| |
| // This test checks that killing a renderer process of a remote frame |
| // and then navigating some other frame to the same SiteInstance of the killed |
| // process works properly. |
| // This can be illustrated as follows, |
| // where 1/2/3 are FrameTreeNode-s and A/B are processes and B* is the killed |
| // B process: |
| // |
| // 1 A A A |
| // / \ -> / \ -> Kill B -> / \ -> Navigate 3 to B -> / \ . |
| // 2 3 B A B* A B* B |
| // |
| // Initially, node1.proxy_hosts_ = {B} |
| // After we kill B, we make sure B stays in node1.proxy_hosts_, then we navigate |
| // 3 to B and we expect that to complete normally. |
| // See http://crbug.com/432107. |
| // |
| // Note that due to http://crbug.com/450681, node2 cannot be re-navigated to |
| // site B and stays in not rendered state. |
| IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, |
| NavigateRemoteFrameToKilledProcess) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "foo.com", "/cross_site_iframe_factory.html?foo.com(bar.com, foo.com)")); |
| 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(); |
| |
|