| // Copyright 2012 The Chromium Authors | 
 | // 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 <cmath> | 
 | #include <list> | 
 | #include <map> | 
 | #include <memory> | 
 | #include <set> | 
 | #include <string> | 
 | #include <tuple> | 
 | #include <utility> | 
 | #include <vector> | 
 |  | 
 | #include "base/command_line.h" | 
 | #include "base/containers/contains.h" | 
 | #include "base/feature_list.h" | 
 | #include "base/functional/bind.h" | 
 | #include "base/functional/callback.h" | 
 | #include "base/functional/callback_helpers.h" | 
 | #include "base/json/json_reader.h" | 
 | #include "base/location.h" | 
 | #include "base/memory/ptr_util.h" | 
 | #include "base/memory/raw_ptr.h" | 
 | #include "base/memory/raw_ref.h" | 
 | #include "base/memory/scoped_refptr.h" | 
 | #include "base/path_service.h" | 
 | #include "base/run_loop.h" | 
 | #include "base/scoped_observation.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/to_string.h" | 
 | #include "base/strings/utf_string_conversions.h" | 
 | #include "base/task/sequenced_task_runner.h" | 
 | #include "base/task/single_thread_task_runner.h" | 
 | #include "base/test/bind.h" | 
 | #include "base/test/metrics/histogram_tester.h" | 
 | #include "base/test/run_until.h" | 
 | #include "base/test/test_future.h" | 
 | #include "base/test/test_timeouts.h" | 
 | #include "base/test/test_trace_processor.h" | 
 | #include "base/time/time.h" | 
 | #include "base/timer/timer.h" | 
 | #include "build/build_config.h" | 
 | #include "cc/base/math_util.h" | 
 | #include "cc/input/touch_action.h" | 
 | #include "components/input/features.h" | 
 | #include "components/input/input_constants.h" | 
 | #include "components/input/input_router.h" | 
 | #include "components/input/render_widget_host_input_event_router.h" | 
 | #include "components/input/switches.h" | 
 | #include "components/input/utils.h" | 
 | #include "components/viz/host/host_frame_sink_manager.h" | 
 | #include "content/browser/child_process_security_policy_impl.h" | 
 | #include "content/browser/compositor/surface_utils.h" | 
 | #include "content/browser/gpu/compositor_util.h" | 
 | #include "content/browser/gpu/gpu_data_manager_impl.h" | 
 | #include "content/browser/process_lock.h" | 
 | #include "content/browser/process_reuse_policy.h" | 
 | #include "content/browser/renderer_host/agent_scheduling_group_host.h" | 
 | #include "content/browser/renderer_host/cross_process_frame_connector.h" | 
 | #include "content/browser/renderer_host/frame_navigation_entry.h" | 
 | #include "content/browser/renderer_host/frame_tree.h" | 
 | #include "content/browser/renderer_host/navigation_controller_impl.h" | 
 | #include "content/browser/renderer_host/navigation_entry_impl.h" | 
 | #include "content/browser/renderer_host/navigation_entry_restore_context_impl.h" | 
 | #include "content/browser/renderer_host/navigation_request.h" | 
 | #include "content/browser/renderer_host/navigator.h" | 
 | #include "content/browser/renderer_host/render_frame_host_impl.h" | 
 | #include "content/browser/renderer_host/render_frame_proxy_host.h" | 
 | #include "content/browser/renderer_host/render_process_host_impl.h" | 
 | #include "content/browser/renderer_host/render_view_host_impl.h" | 
 | #include "content/browser/renderer_host/render_widget_host_view_child_frame.h" | 
 | #include "content/browser/site_info.h" | 
 | #include "content/browser/storage_partition_impl.h" | 
 | #include "content/browser/web_contents/web_contents_impl.h" | 
 | #include "content/common/content_navigation_policy.h" | 
 | #include "content/common/frame.mojom-test-utils.h" | 
 | #include "content/common/input/actions_parser.h" | 
 | #include "content/common/input/synthetic_gesture.h" | 
 | #include "content/common/input/synthetic_gesture_target.h" | 
 | #include "content/common/input/synthetic_pinch_gesture_params.h" | 
 | #include "content/common/input/synthetic_pointer_action.h" | 
 | #include "content/common/input/synthetic_tap_gesture.h" | 
 | #include "content/common/input/synthetic_touchscreen_pinch_gesture.h" | 
 | #include "content/common/renderer.mojom.h" | 
 | #include "content/common/renderer_host.mojom-test-utils.h" | 
 | #include "content/public/browser/browser_context.h" | 
 | #include "content/public/browser/browser_task_traits.h" | 
 | #include "content/public/browser/browser_thread.h" | 
 | #include "content/public/browser/context_menu_params.h" | 
 | #include "content/public/browser/global_routing_id.h" | 
 | #include "content/public/browser/gpu_data_manager_observer.h" | 
 | #include "content/public/browser/gpu_utils.h" | 
 | #include "content/public/browser/javascript_dialog_manager.h" | 
 | #include "content/public/browser/navigation_handle.h" | 
 | #include "content/public/browser/render_process_host_priority_client.h" | 
 | #include "content/public/browser/site_isolation_policy.h" | 
 | #include "content/public/common/content_client.h" | 
 | #include "content/public/common/content_features.h" | 
 | #include "content/public/common/content_switches.h" | 
 | #include "content/public/common/url_constants.h" | 
 | #include "content/public/test/back_forward_cache_util.h" | 
 | #include "content/public/test/browser_test.h" | 
 | #include "content/public/test/browser_test_utils.h" | 
 | #include "content/public/test/content_browser_test_content_browser_client.h" | 
 | #include "content/public/test/content_browser_test_utils.h" | 
 | #include "content/public/test/content_mock_cert_verifier.h" | 
 | #include "content/public/test/fenced_frame_test_util.h" | 
 | #include "content/public/test/hit_test_region_observer.h" | 
 | #include "content/public/test/navigation_handle_observer.h" | 
 | #include "content/public/test/policy_container_utils.h" | 
 | #include "content/public/test/render_frame_host_test_support.h" | 
 | #include "content/public/test/test_devtools_protocol_client.h" | 
 | #include "content/public/test/test_frame_navigation_observer.h" | 
 | #include "content/public/test/test_navigation_observer.h" | 
 | #include "content/public/test/test_navigation_throttle.h" | 
 | #include "content/public/test/test_navigation_throttle_inserter.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/main_frame_counter_test_impl.h" | 
 | #include "content/shell/common/shell_switches.h" | 
 | #include "content/test/content_browser_test_utils_internal.h" | 
 | #include "content/test/did_commit_navigation_interceptor.h" | 
 | #include "content/test/render_document_feature.h" | 
 | #include "ipc/constants.mojom.h" | 
 | #include "media/base/media_switches.h" | 
 | #include "mojo/public/cpp/bindings/pending_remote.h" | 
 | #include "mojo/public/cpp/bindings/sync_call_restrictions.h" | 
 | #include "mojo/public/cpp/test_support/test_utils.h" | 
 | #include "net/base/url_util.h" | 
 | #include "net/dns/mock_host_resolver.h" | 
 | #include "net/http/mock_http_cache.h" | 
 | #include "net/test/embedded_test_server/embedded_test_server.h" | 
 | #include "net/test/embedded_test_server/http_request.h" | 
 | #include "net/test/embedded_test_server/http_response.h" | 
 | #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" | 
 | #include "services/network/public/cpp/features.h" | 
 | #include "services/network/public/cpp/permissions_policy/origin_with_possible_wildcards.h" | 
 | #include "services/network/public/cpp/permissions_policy/permissions_policy_declaration.h" | 
 | #include "services/network/public/cpp/web_sandbox_flags.h" | 
 | #include "services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom-shared.h" | 
 | #include "services/network/public/mojom/web_sandbox_flags.mojom-shared.h" | 
 | #include "services/viz/privileged/mojom/compositing/features.mojom-features.h" | 
 | #include "testing/gmock/include/gmock/gmock.h" | 
 | #include "testing/gtest/include/gtest/gtest.h" | 
 | #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h" | 
 | #include "third_party/blink/public/common/features.h" | 
 | #include "third_party/blink/public/common/input/web_input_event.h" | 
 | #include "third_party/blink/public/common/permissions_policy/policy_value.h" | 
 | #include "third_party/blink/public/common/switches.h" | 
 | #include "third_party/blink/public/common/tokens/tokens.h" | 
 | #include "third_party/blink/public/mojom/frame/frame.mojom-test-utils.h" | 
 | #include "third_party/blink/public/mojom/frame/frame.mojom.h" | 
 | #include "third_party/blink/public/mojom/frame/frame_replication_state.mojom.h" | 
 | #include "third_party/blink/public/mojom/leak_detector/leak_detector.mojom-test-utils.h" | 
 | #include "third_party/blink/public/mojom/leak_detector/leak_detector.mojom.h" | 
 | #include "third_party/blink/public/mojom/page/widget.mojom-test-utils.h" | 
 | #include "third_party/blink/public/mojom/security_context/insecure_request_policy.mojom.h" | 
 | #include "ui/display/display_switches.h" | 
 | #include "ui/display/screen.h" | 
 | #include "ui/events/base_event_utils.h" | 
 | #include "ui/events/blink/blink_features.h" | 
 | #include "ui/events/event.h" | 
 | #include "ui/events/event_utils.h" | 
 | #include "ui/events/keycodes/dom/dom_code.h" | 
 | #include "ui/events/keycodes/dom/dom_key.h" | 
 | #include "ui/events/keycodes/keyboard_codes.h" | 
 | #include "ui/gfx/geometry/point.h" | 
 | #include "ui/gfx/geometry/rect.h" | 
 | #include "ui/gfx/geometry/transform.h" | 
 | #include "ui/latency/latency_info.h" | 
 | #include "ui/native_theme/features/native_theme_features.h" | 
 |  | 
 | #if defined(USE_AURA) | 
 | #include "content/browser/renderer_host/render_widget_host_view_aura.h" | 
 | #include "ui/aura/window.h" | 
 | #endif | 
 |  | 
 | #if BUILDFLAG(IS_ANDROID) | 
 | #include "base/android/jni_android.h" | 
 | #include "base/android/jni_string.h" | 
 | #include "base/android/scoped_java_ref.h" | 
 | #include "content/browser/android/gesture_listener_manager.h" | 
 | #include "content/browser/android/ime_adapter_android.h" | 
 | #include "content/browser/renderer_host/input/touch_selection_controller_client_manager_android.h" | 
 | #include "content/browser/renderer_host/render_widget_host_view_android.h" | 
 | #include "content/browser/web_contents/web_contents_view_android.h" | 
 | #include "content/public/browser/android/child_process_importance.h" | 
 | #include "content/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_factory.h" | 
 | #include "ui/events/android/motion_event_android_java.h" | 
 | #include "ui/events/motionevent_jni_headers/MotionEvent_jni.h" | 
 | #include "ui/gfx/geometry/point_f.h" | 
 | #endif | 
 |  | 
 | using ::testing::SizeIs; | 
 | using ::testing::WhenSorted; | 
 | using ::testing::ElementsAre; | 
 |  | 
 | namespace content { | 
 |  | 
 | namespace { | 
 |  | 
 | void VerifyChildProcessHasMainFrame( | 
 |     mojo::Remote<mojom::MainFrameCounterTest>& main_frame_counter, | 
 |     bool expected_state) { | 
 |   main_frame_counter.FlushForTesting(); | 
 |   base::test::TestFuture<bool> has_main_frame_future; | 
 |   main_frame_counter->HasMainFrame(has_main_frame_future.GetCallback()); | 
 |   EXPECT_EQ(expected_state, has_main_frame_future.Get()); | 
 | } | 
 |  | 
 | using CrashVisibility = CrossProcessFrameConnector::CrashVisibility; | 
 |  | 
 | // 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 EvalJs below, or | 
 |   // else it might miss the message of interest.  See https://crbug.com/518729. | 
 |   DOMMessageQueue msg_queue(sender_ftn->current_frame_host()); | 
 |  | 
 |   EXPECT_EQ(true, EvalJs(sender_ftn, "(" + post_message_script + ");")); | 
 |  | 
 |   std::string status; | 
 |   while (msg_queue.WaitForMessage(&status)) { | 
 |     if (status == reply_status) | 
 |       break; | 
 |   } | 
 | } | 
 |  | 
 | // Helper function to extract and return "window.receivedMessages" from the | 
 | // |sender_ftn| frame.  This variable is used in post_message.html to count the | 
 | // number of messages received via postMessage by the current window. | 
 | int GetReceivedMessages(FrameTreeNode* ftn) { | 
 |   return EvalJs(ftn, "window.receivedMessages;").ExtractInt(); | 
 | } | 
 |  | 
 | // Helper function to perform a window.open from the |caller_frame| targeting a | 
 | // frame with the specified name. | 
 | void NavigateNamedFrame(const ToRenderFrameHost& caller_frame, | 
 |                         const GURL& url, | 
 |                         const std::string& name) { | 
 |   EXPECT_EQ(true, EvalJs(caller_frame, | 
 |                          JsReplace("!!window.open($1, $2)", url, name))); | 
 | } | 
 |  | 
 | // Helper function to generate a click on the given RenderWidgetHost.  The | 
 | // mouse event is forwarded directly to the RenderWidgetHost without any | 
 | // hit-testing. | 
 | void SimulateMouseClick(RenderWidgetHost* rwh, int x, int y) { | 
 |   blink::WebMouseEvent mouse_event( | 
 |       blink::WebInputEvent::Type::kMouseDown, | 
 |       blink::WebInputEvent::kNoModifiers, | 
 |       blink::WebInputEvent::GetStaticTimeStampForTests()); | 
 |   mouse_event.button = blink::WebPointerProperties::Button::kLeft; | 
 |   mouse_event.SetPositionInWidget(x, y); | 
 |   rwh->ForwardMouseEvent(mouse_event); | 
 | } | 
 |  | 
 | // Retrieve self.origin for the frame |ftn|. | 
 | EvalJsResult GetOriginFromRenderer(FrameTreeNode* ftn) { | 
 |   return EvalJs(ftn, "self.origin;"); | 
 | } | 
 |  | 
 | // 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(const UserInteractionObserver&) = delete; | 
 |   UserInteractionObserver& operator=(const UserInteractionObserver&) = delete; | 
 |  | 
 |   ~UserInteractionObserver() override {} | 
 |  | 
 |   // Retrieve the flag. There is no need to wait on a loop since | 
 |   // DidGetUserInteraction() should be called synchronously with the input | 
 |   // event processing in the browser process. | 
 |   bool WasUserInteractionReceived() { return user_interaction_received_; } | 
 |  | 
 |   void Reset() { user_interaction_received_ = false; } | 
 |  | 
 |  private: | 
 |   // WebContentsObserver | 
 |   void DidGetUserInteraction(const blink::WebInputEvent& event) override { | 
 |     user_interaction_received_ = true; | 
 |   } | 
 |  | 
 |   bool user_interaction_received_; | 
 | }; | 
 |  | 
 | // Supports waiting until a WebContents notifies its observers that the visible | 
 | // security state changed, and a test-specific condition is true at that time. | 
 | class VisibleSecurityStateObserver : public WebContentsObserver { | 
 |  public: | 
 |   // Invoked at Wait() start and when the visible security state changes. | 
 |   // If the callback returns true, stops waiting. | 
 |   using ConditionCallback = base::RepeatingCallback<bool(WebContents*)>; | 
 |  | 
 |   // Creates a VisibleSecurityStateObserver which will wait until | 
 |   // a visible security state change is announced by |web_contents| and | 
 |   // |condition_callback| returns true (unless |condition_callback| returns true | 
 |   // in Wait() already, when it will not wait at all). | 
 |   VisibleSecurityStateObserver(WebContents* web_contents, | 
 |                                ConditionCallback condition_callback) | 
 |       : WebContentsObserver(web_contents), | 
 |         condition_callback_(condition_callback) {} | 
 |   ~VisibleSecurityStateObserver() override = default; | 
 |  | 
 |   VisibleSecurityStateObserver(const VisibleSecurityStateObserver& other) = | 
 |       delete; | 
 |   VisibleSecurityStateObserver& operator=( | 
 |       const VisibleSecurityStateObserver& other) = delete; | 
 |  | 
 |   // If the |condition_callback| passed to the constructor returns true, this | 
 |   // returns immediately. Otherwise, blocks until the |web_contents| passed to | 
 |   // the constructor notifies about a visible security state change and the | 
 |   // |condition_callback| evaluates to true. | 
 |   void Wait() { | 
 |     if (condition_callback_.Run(web_contents())) | 
 |       return; | 
 |     run_loop_.Run(); | 
 |   } | 
 |  | 
 |   void DidChangeVisibleSecurityState() override { | 
 |     if (condition_callback_.Run(web_contents())) | 
 |       run_loop_.Quit(); | 
 |   } | 
 |  | 
 |  private: | 
 |   ConditionCallback condition_callback_; | 
 |   base::RunLoop run_loop_; | 
 | }; | 
 |  | 
 | // Helper function to focus a frame by sending it a mouse click and then | 
 | // waiting for it to become focused. | 
 | void FocusFrame(FrameTreeNode* frame) { | 
 |   FrameFocusedObserver focus_observer(frame->current_frame_host()); | 
 |   SimulateMouseClick(frame->current_frame_host()->GetRenderWidgetHost(), 1, 1); | 
 |   focus_observer.Wait(); | 
 | } | 
 |  | 
 | bool ConvertJSONToPoint(const std::string& str, gfx::PointF* point) { | 
 |   std::optional<base::Value::Dict> value = base::JSONReader::ReadDict(str); | 
 |   if (!value) { | 
 |     return false; | 
 |   } | 
 |   std::optional<double> x = value->FindDouble("x"); | 
 |   std::optional<double> y = value->FindDouble("y"); | 
 |   if (!x || !y) { | 
 |     return false; | 
 |   } | 
 |   point->set_x(x.value()); | 
 |   point->set_y(y.value()); | 
 |   return true; | 
 | } | 
 |  | 
 | // Helper function to generate a permissions policy for a single feature and a | 
 | // list of origins. (Equivalent to the declared policy "feature origin1 origin2 | 
 | // ...".) If the origins list is empty, it's treated as matches all origins | 
 | // (Equivalent to the declared policy "feature *") | 
 | network::ParsedPermissionsPolicyDeclaration | 
 | CreateParsedPermissionsPolicyDeclaration( | 
 |     network::mojom::PermissionsPolicyFeature feature, | 
 |     const std::vector<GURL>& origins, | 
 |     bool match_all_origins = false, | 
 |     const std::optional<GURL> self_if_matches = std::nullopt) { | 
 |   network::ParsedPermissionsPolicyDeclaration declaration; | 
 |  | 
 |   declaration.feature = feature; | 
 |   if (self_if_matches.has_value()) { | 
 |     declaration.self_if_matches = url::Origin::Create(*self_if_matches); | 
 |   } | 
 |   declaration.matches_all_origins = match_all_origins; | 
 |   declaration.matches_opaque_src = match_all_origins; | 
 |  | 
 |   for (const auto& origin : origins) | 
 |     declaration.allowed_origins.emplace_back( | 
 |         *network::OriginWithPossibleWildcards::FromOrigin( | 
 |             url::Origin::Create(origin))); | 
 |  | 
 |   std::sort(declaration.allowed_origins.begin(), | 
 |             declaration.allowed_origins.end()); | 
 |  | 
 |   return declaration; | 
 | } | 
 |  | 
 | network::ParsedPermissionsPolicy CreateParsedPermissionsPolicy( | 
 |     const std::vector<network::mojom::PermissionsPolicyFeature>& features, | 
 |     const std::vector<GURL>& origins, | 
 |     bool match_all_origins = false, | 
 |     const std::optional<GURL> self_if_matches = std::nullopt) { | 
 |   network::ParsedPermissionsPolicy result; | 
 |   result.reserve(features.size()); | 
 |   for (const auto& feature : features) | 
 |     result.push_back(CreateParsedPermissionsPolicyDeclaration( | 
 |         feature, origins, match_all_origins, self_if_matches)); | 
 |   return result; | 
 | } | 
 |  | 
 | network::ParsedPermissionsPolicy CreateParsedPermissionsPolicyMatchesSelf( | 
 |     const std::vector<network::mojom::PermissionsPolicyFeature>& features, | 
 |     const GURL& self_if_matches) { | 
 |   return CreateParsedPermissionsPolicy(features, {}, false, self_if_matches); | 
 | } | 
 |  | 
 | network::ParsedPermissionsPolicy CreateParsedPermissionsPolicyMatchesAll( | 
 |     const std::vector<network::mojom::PermissionsPolicyFeature>& features) { | 
 |   return CreateParsedPermissionsPolicy(features, {}, true); | 
 | } | 
 |  | 
 | network::ParsedPermissionsPolicy CreateParsedPermissionsPolicyMatchesNone( | 
 |     const std::vector<network::mojom::PermissionsPolicyFeature>& features) { | 
 |   return CreateParsedPermissionsPolicy(features, {}); | 
 | } | 
 |  | 
 | // Check frame depth on node, widget, and process all match expected depth. | 
 | void CheckFrameDepth(unsigned int expected_depth, FrameTreeNode* node) { | 
 |   EXPECT_EQ(expected_depth, node->current_frame_host()->GetFrameDepth()); | 
 |   RenderProcessHostPriorityClient::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()); | 
 | } | 
 |  | 
 | void GenerateTapDownGesture(RenderWidgetHost* rwh) { | 
 |   blink::WebGestureEvent gesture_tap_down( | 
 |       blink::WebGestureEvent::Type::kGestureTapDown, | 
 |       blink::WebInputEvent::kNoModifiers, | 
 |       blink::WebInputEvent::GetStaticTimeStampForTests(), | 
 |       blink::WebGestureDevice::kTouchscreen); | 
 |   gesture_tap_down.is_source_touch_event_set_blocking = true; | 
 |   rwh->ForwardGestureEvent(gesture_tap_down); | 
 | } | 
 |  | 
 | // Overrides process reuse preference based on URL for testing purposes. | 
 | class SitePerProcessWithMainFrameThresholdAndSiteRestrictionBrowserClient | 
 |     : public ContentBrowserTestContentBrowserClient { | 
 |  public: | 
 |   SitePerProcessWithMainFrameThresholdAndSiteRestrictionBrowserClient() = | 
 |       default; | 
 |   ~SitePerProcessWithMainFrameThresholdAndSiteRestrictionBrowserClient() | 
 |       override = default; | 
 |  | 
 |   SitePerProcessWithMainFrameThresholdAndSiteRestrictionBrowserClient( | 
 |       const SitePerProcessWithMainFrameThresholdAndSiteRestrictionBrowserClient&) = | 
 |       delete; | 
 |   SitePerProcessWithMainFrameThresholdAndSiteRestrictionBrowserClient& | 
 |   operator=( | 
 |       const SitePerProcessWithMainFrameThresholdAndSiteRestrictionBrowserClient&) = | 
 |       delete; | 
 |  | 
 |   // Controls whether reuse is preferred under the main frame threshold policy. | 
 |   bool ShouldReuseExistingProcessForNewMainFrameSiteInstance( | 
 |       content::BrowserContext* browser_context, | 
 |       const GURL& site_instance_original_url) override { | 
 |     // Only reuse for foo.com/title1.html specifically. | 
 |     if (site_instance_original_url.DomainIs("foo.com") && | 
 |         site_instance_original_url.path_piece() == "/title1.html") { | 
 |       return true; | 
 |     } | 
 |     // For all other URLs, including other paths on foo.com or other domains, | 
 |     // do not force reuse via this override. Let default policies apply. | 
 |     return false; | 
 |   } | 
 | }; | 
 |  | 
 | }  // namespace | 
 |  | 
 | // | 
 | // SitePerProcessBrowserTestBase | 
 | // | 
 |  | 
 | SitePerProcessBrowserTestBase::SitePerProcessBrowserTestBase() { | 
 | #if !BUILDFLAG(IS_ANDROID) | 
 |   // TODO(bokan): Needed for scrollability check in | 
 |   // FrameOwnerPropertiesPropagationScrolling. crbug.com/662196. | 
 |   // Overlay scrollbar will be turned off with both conditions satisfied: | 
 |   // 1) feature flag `kOverlayScrollbar` is off | 
 |   // 2) always show scrollbar preference setting on. | 
 |   feature_list_.InitWithFeatures( | 
 |       /*enabled_features=*/{}, | 
 |       /*disabled_features=*/{features::kOverlayScrollbar}); | 
 | #endif | 
 | } | 
 |  | 
 | std::string SitePerProcessBrowserTestBase::DepictFrameTree( | 
 |     FrameTreeNode* node) { | 
 |   return visualizer_.DepictFrameTree(node); | 
 | } | 
 |  | 
 | std::string SitePerProcessBrowserTestBase::WaitForMessageScript( | 
 |     const std::string& result_expression) { | 
 |   return base::StringPrintf( | 
 |       "var onMessagePromise = new Promise(resolve => {" | 
 |       "  window.addEventListener('message', function(event) {" | 
 |       "    resolve(%s);" | 
 |       "  });" | 
 |       "});", | 
 |       result_expression.c_str()); | 
 | } | 
 |  | 
 | void SitePerProcessBrowserTestBase::SetUpCommandLine( | 
 |     base::CommandLine* command_line) { | 
 |   IsolateAllSitesForTesting(command_line); | 
 |  | 
 |   command_line->AppendSwitch(input::switches::kValidateInputEventStream); | 
 |   // Without this, FocusFrame can be flaky. It depends on dispatching input | 
 |   // events which can inadventently get dropped. | 
 |   command_line->AppendSwitch(blink::switches::kAllowPreCommitInput); | 
 | } | 
 |  | 
 | void SitePerProcessBrowserTestBase::SetUpOnMainThread() { | 
 |   host_resolver()->AddRule("*", "127.0.0.1"); | 
 |   SetupCrossSiteRedirector(embedded_test_server()); | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 | } | 
 |  | 
 | void SitePerProcessBrowserTestBase::ForceUpdateViewportIntersection( | 
 |     FrameTreeNode* frame_tree_node, | 
 |     const blink::mojom::ViewportIntersectionState& intersection_state) { | 
 |   frame_tree_node->render_manager() | 
 |       ->GetProxyToParent() | 
 |       ->cross_process_frame_connector() | 
 |       ->UpdateViewportIntersectionInternal(intersection_state, false); | 
 | } | 
 |  | 
 | void SitePerProcessBrowserTestBase::RunPostedTasks() { | 
 |   base::RunLoop loop; | 
 |   base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( | 
 |       FROM_HERE, loop.QuitClosure()); | 
 |   loop.Run(); | 
 | } | 
 |  | 
 | // SitePerProcessBrowserTest | 
 |  | 
 | SitePerProcessBrowserTest::SitePerProcessBrowserTest() { | 
 |   InitAndEnableRenderDocumentFeature(&feature_list_, GetParam()); | 
 | } | 
 |  | 
 | std::string SitePerProcessBrowserTest::GetExpectedOrigin( | 
 |     const std::string& host) { | 
 |   GURL url = embedded_test_server()->GetURL(host, "/"); | 
 |   return url::Origin::Create(url).Serialize(); | 
 | } | 
 |  | 
 | // SitePerProcessIgnoreCertErrorsBrowserTest | 
 |  | 
 | void SitePerProcessIgnoreCertErrorsBrowserTest::SetUpOnMainThread() { | 
 |   SitePerProcessBrowserTest::SetUpOnMainThread(); | 
 |   mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK); | 
 | } | 
 |  | 
 | void SitePerProcessIgnoreCertErrorsBrowserTest::SetUpCommandLine( | 
 |     base::CommandLine* command_line) { | 
 |   SitePerProcessBrowserTest::SetUpCommandLine(command_line); | 
 |   mock_cert_verifier_.SetUpCommandLine(command_line); | 
 | } | 
 |  | 
 | void SitePerProcessIgnoreCertErrorsBrowserTest:: | 
 |     SetUpInProcessBrowserTestFixture() { | 
 |   SitePerProcessBrowserTest::SetUpInProcessBrowserTestFixture(); | 
 |   mock_cert_verifier_.SetUpInProcessBrowserTestFixture(); | 
 | } | 
 |  | 
 | void SitePerProcessIgnoreCertErrorsBrowserTest:: | 
 |     TearDownInProcessBrowserTestFixture() { | 
 |   SitePerProcessBrowserTest::TearDownInProcessBrowserTestFixture(); | 
 |   mock_cert_verifier_.TearDownInProcessBrowserTestFixture(); | 
 | } | 
 |  | 
 | // SitePerProcessAutoplayBrowserTest | 
 |  | 
 | class SitePerProcessAutoplayBrowserTest : public SitePerProcessBrowserTest { | 
 |  public: | 
 |   SitePerProcessAutoplayBrowserTest() = default; | 
 |  | 
 |   void SetUpCommandLine(base::CommandLine* command_line) override { | 
 |     SitePerProcessBrowserTestBase::SetUpCommandLine(command_line); | 
 |     command_line->AppendSwitchASCII( | 
 |         switches::kAutoplayPolicy, | 
 |         switches::autoplay::kDocumentUserActivationRequiredPolicy); | 
 |   } | 
 |  | 
 |   bool AutoplayAllowed(const ToRenderFrameHost& adapter, | 
 |                        bool with_user_gesture) { | 
 |     return EvalJs(adapter, "attemptPlay();", | 
 |                   with_user_gesture ? EXECUTE_SCRIPT_DEFAULT_OPTIONS | 
 |                                     : EXECUTE_SCRIPT_NO_USER_GESTURE) | 
 |         .ExtractBool(); | 
 |   } | 
 | }; | 
 |  | 
 | // Certain tests require the speculative RFH to be created before the browser | 
 | // receives any data from the server. The delay of creating the RFH is set to 0 | 
 | // in these tests so that the speculative RFH is created when the request is | 
 | // sent. | 
 | class SitePerProcessBrowserTestWithoutSpeculativeRFHDelay | 
 |     : public SitePerProcessBrowserTest { | 
 |  public: | 
 |   SitePerProcessBrowserTestWithoutSpeculativeRFHDelay() { | 
 |     feature_list_for_defer_speculative_rfh_.InitAndEnableFeatureWithParameters( | 
 |         features::kDeferSpeculativeRFHCreation, | 
 |         {{"create_speculative_rfh_delay_ms", "0"}}); | 
 |   } | 
 |  | 
 |  private: | 
 |   base::test::ScopedFeatureList feature_list_for_defer_speculative_rfh_; | 
 | }; | 
 |  | 
 | // Ensure that navigating subframes in --site-per-process mode works and the | 
 | // correct documents are committed. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, CrossSiteIframe) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(a,a(a,a(a)))")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   // It is safe to obtain the root frame tree node here, as it doesn't change. | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().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")); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(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<RenderWidgetHostViewBase*> views_set = | 
 |         web_contents()->GetRenderWidgetHostViewsInWebContentsTree(); | 
 |     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()); | 
 |     EXPECT_TRUE(NavigateToURLFromRenderer(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()->GetPrimaryMainFrame()->GetRenderViewHost(), | 
 |             rvh); | 
 |   EXPECT_NE(shell()->web_contents()->GetSiteInstance(), site_instance); | 
 |   EXPECT_NE(shell()->web_contents()->GetPrimaryMainFrame()->GetProcess(), rph); | 
 |   { | 
 |     // There should be now two RenderWidgetHosts, one for each process | 
 |     // rendering a frame. | 
 |     std::set<RenderWidgetHostViewBase*> views_set = | 
 |         web_contents()->GetRenderWidgetHostViewsInWebContentsTree(); | 
 |     EXPECT_EQ(2U, views_set.size()); | 
 |   } | 
 |   mojo::Remote<mojom::MainFrameCounterTest> main_frame_counter; | 
 |   shell()->web_contents()->GetPrimaryMainFrame()->GetProcess()->BindReceiver( | 
 |       main_frame_counter.BindNewPipeAndPassReceiver()); | 
 |  | 
 |   VerifyChildProcessHasMainFrame(main_frame_counter, true); | 
 |  | 
 |   mojo::Remote<mojom::MainFrameCounterTest> main_frame_counter_child; | 
 |   rph->BindReceiver(main_frame_counter_child.BindNewPipeAndPassReceiver()); | 
 |  | 
 |   VerifyChildProcessHasMainFrame(main_frame_counter_child, false); | 
 |  | 
 |   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()); | 
 |     EXPECT_TRUE(NavigateToURLFromRenderer(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()->GetPrimaryMainFrame()->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()->GetPrimaryMainFrame()->GetProcess(), | 
 |             child->current_frame_host()->GetProcess()); | 
 |   EXPECT_NE(rph, child->current_frame_host()->GetProcess()); | 
 |   VerifyChildProcessHasMainFrame(main_frame_counter, true); | 
 |   { | 
 |     std::set<RenderWidgetHostViewBase*> views_set = | 
 |         web_contents()->GetRenderWidgetHostViewsInWebContentsTree(); | 
 |     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)); | 
 | } | 
 |  | 
 | // Simple test to set up a A(B,C) page and then navigate the C subframe to D. | 
 | // This can be used to study performance of proxy creation code. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, NavigateABCToABD) { | 
 |   GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // Add a new child frame and navigate it to B. | 
 |   RenderFrameHostCreatedObserver frame_observer(shell()->web_contents(), 1); | 
 |   EXPECT_TRUE(ExecJs( | 
 |       root, "document.body.appendChild(document.createElement('iframe'));")); | 
 |   frame_observer.Wait(); | 
 |  | 
 |   FrameTreeNode* child1 = root->child_at(0); | 
 |   { | 
 |     RenderFrameDeletedObserver deleted_observer(child1->current_frame_host()); | 
 |     GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |     EXPECT_TRUE(NavigateToURLFromRenderer(child1, b_url)); | 
 |     deleted_observer.WaitUntilDeleted(); | 
 |   } | 
 |  | 
 |   // Add a second child frame and navigate it to C. | 
 |   RenderFrameHostCreatedObserver frame_observer2(shell()->web_contents(), 1); | 
 |   EXPECT_TRUE(ExecJs( | 
 |       root, "document.body.appendChild(document.createElement('iframe'));")); | 
 |   frame_observer2.Wait(); | 
 |  | 
 |   FrameTreeNode* child2 = root->child_at(1); | 
 |   { | 
 |     RenderFrameDeletedObserver deleted_observer(child2->current_frame_host()); | 
 |     GURL c_url(embedded_test_server()->GetURL("c.com", "/title1.html")); | 
 |     EXPECT_TRUE(NavigateToURLFromRenderer(child2, c_url)); | 
 |     deleted_observer.WaitUntilDeleted(); | 
 |   } | 
 |   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://c.com/", | 
 |       DepictFrameTree(root)); | 
 |  | 
 |   // Navigate second child frame from C to D. | 
 |   { | 
 |     RenderFrameDeletedObserver deleted_observer(child2->current_frame_host()); | 
 |     GURL d_url(embedded_test_server()->GetURL("d.com", "/title1.html")); | 
 |     EXPECT_TRUE(NavigateToURLFromRenderer(child2, d_url)); | 
 |     deleted_observer.WaitUntilDeleted(); | 
 |   } | 
 |   EXPECT_EQ( | 
 |       " Site A ------------ proxies for B D\n" | 
 |       "   |--Site B ------- proxies for A D\n" | 
 |       "   +--Site D ------- proxies for A B\n" | 
 |       "Where A = http://a.com/\n" | 
 |       "      B = http://b.com/\n" | 
 |       "      D = http://d.com/", | 
 |       DepictFrameTree(root)); | 
 | } | 
 |  | 
 | // Ensure that processes for iframes correctly track whether or not they have a | 
 | // local main frame. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        CrossSiteIframeMainFrameCount) { | 
 |   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)); | 
 |  | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   TestNavigationObserver observer(shell()->web_contents()); | 
 |  | 
 |   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)); | 
 |  | 
 |   mojo::Remote<mojom::MainFrameCounterTest> main_frame_counter; | 
 |   shell()->web_contents()->GetPrimaryMainFrame()->GetProcess()->BindReceiver( | 
 |       main_frame_counter.BindNewPipeAndPassReceiver()); | 
 |   VerifyChildProcessHasMainFrame(main_frame_counter, true); | 
 |  | 
 |   GURL url = embedded_test_server()->GetURL( | 
 |       "b.com", "/cross_site_iframe_factory.html?b(a,a)"); | 
 |   { | 
 |     RenderFrameDeletedObserver deleted_observer( | 
 |         root->child_at(2)->current_frame_host()); | 
 |     EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(2), url)); | 
 |     deleted_observer.WaitUntilDeleted(); | 
 |   } | 
 |  | 
 |   EXPECT_EQ( | 
 |       " Site A ------------ proxies for B\n" | 
 |       "   |--Site A ------- proxies for B\n" | 
 |       "   |--Site A ------- proxies for B\n" | 
 |       "   +--Site B ------- proxies for A\n" | 
 |       "        |--Site A -- proxies for B\n" | 
 |       "        +--Site A -- proxies for B\n" | 
 |       "Where A = http://a.com/\n" | 
 |       "      B = http://b.com/", | 
 |       DepictFrameTree(root)); | 
 |  | 
 |   VerifyChildProcessHasMainFrame(main_frame_counter, true); | 
 |  | 
 |   mojo::Remote<mojom::MainFrameCounterTest> main_frame_counter_child; | 
 |   root->child_at(2)->current_frame_host()->GetProcess()->BindReceiver( | 
 |       main_frame_counter_child.BindNewPipeAndPassReceiver()); | 
 |   VerifyChildProcessHasMainFrame(main_frame_counter_child, false); | 
 | } | 
 |  | 
 | // Ensure that title updates affect the correct NavigationEntry after a new | 
 | // subframe navigation with an out-of-process iframe.  https://crbug.com/616609. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, TitleAfterCrossSiteIframe) { | 
 |   // Start at an initial page. | 
 |   GURL initial_url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), initial_url)); | 
 |  | 
 |   // Navigate to a same-site page with a same-site iframe. | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(a)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // Make the main frame update its title after the subframe loads. | 
 |   EXPECT_TRUE(ExecJs(shell()->web_contents(), | 
 |                      "document.querySelector('iframe').onload = " | 
 |                      "    function() { document.title = 'loaded'; };")); | 
 |   EXPECT_TRUE( | 
 |       ExecJs(shell()->web_contents(), "document.title = 'not loaded';")); | 
 |   std::u16string expected_title(u"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(ExecJs(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()); | 
 | } | 
 |  | 
 | // This test verifies that scroll bubbling from an OOPIF properly forwards | 
 | // GestureFlingStart events from the child frame to the parent frame. This | 
 | // test times out on failure. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        GestureFlingStartEventsBubble) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) | 
 |                             ->GetPrimaryFrameTree() | 
 |                             .root(); | 
 |   ASSERT_EQ(1U, root->child_count()); | 
 |  | 
 |   FrameTreeNode* child_iframe_node = root->child_at(0); | 
 |  | 
 |   RenderWidgetHost* child_rwh = | 
 |       child_iframe_node->current_frame_host()->GetRenderWidgetHost(); | 
 |  | 
 |   // The fling start won't bubble since its corresponding GSB hasn't bubbled. | 
 |   InputEventAckWaiter gesture_fling_start_ack_observer( | 
 |       child_rwh, blink::WebInputEvent::Type::kGestureFlingStart); | 
 |  | 
 |   WaitForHitTestData(child_iframe_node->current_frame_host()); | 
 |  | 
 |   gesture_fling_start_ack_observer.Reset(); | 
 |  | 
 |   GenerateTapDownGesture(child_rwh); | 
 |  | 
 |   // Send a GSB, GSU, GFS sequence and verify that the GFS bubbles. | 
 |   blink::WebGestureEvent gesture_scroll_begin( | 
 |       blink::WebGestureEvent::Type::kGestureScrollBegin, | 
 |       blink::WebInputEvent::kNoModifiers, | 
 |       blink::WebInputEvent::GetStaticTimeStampForTests(), | 
 |       blink::WebGestureDevice::kTouchscreen); | 
 |   gesture_scroll_begin.data.scroll_begin.delta_hint_units = | 
 |       ui::ScrollGranularity::kScrollByPrecisePixel; | 
 |   gesture_scroll_begin.data.scroll_begin.delta_x_hint = 0.f; | 
 |   gesture_scroll_begin.data.scroll_begin.delta_y_hint = 5.f; | 
 |  | 
 |   child_rwh->ForwardGestureEvent(gesture_scroll_begin); | 
 |  | 
 |   blink::WebGestureEvent gesture_scroll_update( | 
 |       blink::WebGestureEvent::Type::kGestureScrollUpdate, | 
 |       blink::WebInputEvent::kNoModifiers, | 
 |       blink::WebInputEvent::GetStaticTimeStampForTests(), | 
 |       blink::WebGestureDevice::kTouchscreen); | 
 |   gesture_scroll_update.data.scroll_update.delta_units = | 
 |       ui::ScrollGranularity::kScrollByPrecisePixel; | 
 |   gesture_scroll_update.data.scroll_update.delta_x = 0.f; | 
 |   gesture_scroll_update.data.scroll_update.delta_y = 5.f; | 
 |  | 
 |   child_rwh->ForwardGestureEvent(gesture_scroll_update); | 
 |  | 
 |   blink::WebGestureEvent gesture_fling_start( | 
 |       blink::WebGestureEvent::Type::kGestureFlingStart, | 
 |       blink::WebInputEvent::kNoModifiers, | 
 |       blink::WebInputEvent::GetStaticTimeStampForTests(), | 
 |       blink::WebGestureDevice::kTouchscreen); | 
 |   gesture_fling_start.data.fling_start.velocity_x = 0.f; | 
 |   gesture_fling_start.data.fling_start.velocity_y = 5.f; | 
 |  | 
 |   child_rwh->ForwardGestureEvent(gesture_fling_start); | 
 |  | 
 |   // We now wait for the fling start event to be acked by the parent | 
 |   // frame. If the test fails, then the test times out. | 
 |   gesture_fling_start_ack_observer.Wait(); | 
 | } | 
 |  | 
 | // Test that fling on an out-of-process iframe progresses properly. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        TouchscreenGestureFlingStart) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) | 
 |                             ->GetPrimaryFrameTree() | 
 |                             .root(); | 
 |   ASSERT_EQ(1U, root->child_count()); | 
 |  | 
 |   FrameTreeNode* child_iframe_node = root->child_at(0); | 
 |  | 
 |   RenderWidgetHost* child_rwh = | 
 |       child_iframe_node->current_frame_host()->GetRenderWidgetHost(); | 
 |   WaitForHitTestData(child_iframe_node->current_frame_host()); | 
 |  | 
 |   GenerateTapDownGesture(child_rwh); | 
 |   // Send a GSB to start scrolling sequence. | 
 |   blink::WebGestureEvent gesture_scroll_begin( | 
 |       blink::WebGestureEvent::Type::kGestureScrollBegin, | 
 |       blink::WebInputEvent::kNoModifiers, | 
 |       blink::WebInputEvent::GetStaticTimeStampForTests()); | 
 |   gesture_scroll_begin.SetSourceDevice(blink::WebGestureDevice::kTouchscreen); | 
 |   gesture_scroll_begin.data.scroll_begin.delta_hint_units = | 
 |       ui::ScrollGranularity::kScrollByPrecisePixel; | 
 |   gesture_scroll_begin.data.scroll_begin.delta_x_hint = 0.f; | 
 |   gesture_scroll_begin.data.scroll_begin.delta_y_hint = 5.f; | 
 |   child_rwh->ForwardGestureEvent(gesture_scroll_begin); | 
 |  | 
 |   // Send a GFS and wait for the ack of the first GSU generated from progressing | 
 |   // the fling on the browser. | 
 |   InputEventAckWaiter gesture_scroll_update_ack_observer( | 
 |       child_rwh, blink::WebInputEvent::Type::kGestureScrollUpdate); | 
 |   gesture_scroll_update_ack_observer.Reset(); | 
 |   blink::WebGestureEvent gesture_fling_start( | 
 |       blink::WebGestureEvent::Type::kGestureFlingStart, | 
 |       blink::WebInputEvent::kNoModifiers, | 
 |       blink::WebInputEvent::GetStaticTimeStampForTests()); | 
 |   gesture_fling_start.SetSourceDevice(blink::WebGestureDevice::kTouchscreen); | 
 |   gesture_fling_start.data.fling_start.velocity_x = 0.f; | 
 |   gesture_fling_start.data.fling_start.velocity_y = 50.f; | 
 |   child_rwh->ForwardGestureEvent(gesture_fling_start); | 
 |   gesture_scroll_update_ack_observer.Wait(); | 
 | } | 
 |  | 
 | // Test that fling on an out-of-process iframe progresses properly. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, TouchpadGestureFlingStart) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) | 
 |                             ->GetPrimaryFrameTree() | 
 |                             .root(); | 
 |   ASSERT_EQ(1U, root->child_count()); | 
 |  | 
 |   FrameTreeNode* child_iframe_node = root->child_at(0); | 
 |  | 
 |   RenderWidgetHost* child_rwh = | 
 |       child_iframe_node->current_frame_host()->GetRenderWidgetHost(); | 
 |  | 
 |   // Send a wheel event with phaseBegan to start scrolling sequence. | 
 |   InputEventAckWaiter gesture_scroll_begin_ack_observer( | 
 |       child_rwh, blink::WebInputEvent::Type::kGestureScrollBegin); | 
 |   blink::WebMouseWheelEvent scroll_event( | 
 |       blink::WebInputEvent::Type::kMouseWheel, | 
 |       blink::WebInputEvent::kNoModifiers, | 
 |       blink::WebInputEvent::GetStaticTimeStampForTests()); | 
 |   scroll_event.delta_units = ui::ScrollGranularity::kScrollByPrecisePixel; | 
 |   scroll_event.delta_x = 0.0f; | 
 |   scroll_event.delta_y = 5.0f; | 
 |   scroll_event.phase = blink::WebMouseWheelEvent::kPhaseBegan; | 
 |   child_rwh->ForwardWheelEvent(scroll_event); | 
 |   gesture_scroll_begin_ack_observer.Wait(); | 
 |  | 
 |   // Send a GFS and wait for the ack of the first GSU generated from progressing | 
 |   // the fling on the browser. | 
 |   InputEventAckWaiter gesture_scroll_update_ack_observer( | 
 |       child_rwh, blink::WebInputEvent::Type::kGestureScrollUpdate); | 
 |   gesture_scroll_update_ack_observer.Reset(); | 
 |   blink::WebGestureEvent gesture_fling_start( | 
 |       blink::WebGestureEvent::Type::kGestureFlingStart, | 
 |       blink::WebInputEvent::kNoModifiers, | 
 |       blink::WebInputEvent::GetStaticTimeStampForTests()); | 
 |   gesture_fling_start.SetSourceDevice(blink::WebGestureDevice::kTouchpad); | 
 |   gesture_fling_start.data.fling_start.velocity_x = 0.f; | 
 |   gesture_fling_start.data.fling_start.velocity_y = 50.f; | 
 |   child_rwh->ForwardGestureEvent(gesture_fling_start); | 
 |   // The test will pass when the GSU ack arrives, since it shows that the fling | 
 |   // controller has properly generated a GSU event from progressing the fling. | 
 |   gesture_scroll_update_ack_observer.Wait(); | 
 | } | 
 |  | 
 | // Tests OOPIF rendering by checking that the RWH of the iframe generates | 
 | // OnSwapCompositorFrame message. | 
 | IN_PROC_BROWSER_TEST_P(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()->GetPrimaryFrameTree().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_P(SitePerProcessBrowserTest, CleanupCrossSiteIframe) { | 
 |   // The test assumes the previous page gets deleted after navigation. Disable | 
 |   // back-forward cache to ensure that it doesn't get preserved in the cache. | 
 |   DisableBackForwardCacheForTesting( | 
 |       web_contents(), content::BackForwardCache::TEST_REQUIRES_NO_CACHING); | 
 |   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()->GetPrimaryFrameTree().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"); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(0), foo_url)); | 
 |   EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |   EXPECT_EQ(foo_url, observer.last_navigation_url()); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(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() | 
 |                                 ->GetDeprecatedID(); | 
 |   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(ExecJs(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_P(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()->GetPrimaryFrameTree().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")); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(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()); | 
 |     EXPECT_TRUE(NavigateToURLFromRenderer(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()); | 
 |     EXPECT_TRUE(NavigateToURLFromRenderer(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_P(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()->GetPrimaryFrameTree().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")); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(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"); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(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 | 
 |   // SiteInstanceGroup as the parent frame. If kSiteInstanceGroupsForDataUrls is | 
 |   // enabled, the data URL should be in its own SiteInstance. Otherwise it | 
 |   // shares a SiteInstance with its parent. | 
 |   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. | 
 |   if (ShouldCreateSiteInstanceForDataUrls()) { | 
 |     // Site A and Site C are in the same SiteInstanceGroup, so there are no | 
 |     // proxies for each other. | 
 |     // TODO(crbug.com/341741267, yangsharon): Update output to show that A and C | 
 |     // are in the same SiteInstanceGroup. | 
 |     EXPECT_EQ( | 
 |         " Site A\n" | 
 |         "   |--Site C\n" | 
 |         "   +--Site A\n" | 
 |         "        +--Site A\n" | 
 |         "Where A = http://a.com/\n" | 
 |         "      C = data:nonce_C", | 
 |         DepictFrameTree(root)); | 
 |   } else { | 
 |     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"); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(child, url)); | 
 |   EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |   EXPECT_EQ(url, observer.last_navigation_url()); | 
 |   if (ShouldCreateSiteInstanceForDataUrls()) { | 
 |     EXPECT_EQ( | 
 |         " Site A ------------ proxies for D\n" | 
 |         "   |--Site D ------- proxies for {A,C}\n" | 
 |         "   +--Site A ------- proxies for D\n" | 
 |         "        +--Site A -- proxies for D\n" | 
 |         "Where A = http://a.com/\n" | 
 |         "      C = data:nonce_C\n" | 
 |         "      D = http://bar.com/", | 
 |         DepictFrameTree(root)); | 
 |   } else { | 
 |     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"); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(child, url)); | 
 |   EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |   EXPECT_EQ(url, observer.last_navigation_url()); | 
 |   if (ShouldCreateSiteInstanceForDataUrls()) { | 
 |     EXPECT_EQ( | 
 |         " Site A ------------ proxies for E\n" | 
 |         "   |--Site E ------- proxies for {A,C}\n" | 
 |         "   +--Site A ------- proxies for E\n" | 
 |         "        +--Site A -- proxies for E\n" | 
 |         "Where A = http://a.com/\n" | 
 |         "      C = data:nonce_C\n" | 
 |         "      E = http://f00.com/", | 
 |         DepictFrameTree(root)); | 
 |   } else { | 
 |     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); | 
 |   EXPECT_TRUE(ExecJs(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. | 
 |   if (ShouldCreateSiteInstanceForDataUrls()) { | 
 |     EXPECT_EQ( | 
 |         " Site A ------------ proxies for E\n" | 
 |         "   |--Site E ------- proxies for {A,C}\n" | 
 |         "   +--Site A ------- proxies for E\n" | 
 |         "        +--Site A -- proxies for E\n" | 
 |         "Where A = http://a.com/\n" | 
 |         "      C = data:nonce_C\n" | 
 |         "      E = http://f00.com/", | 
 |         DepictFrameTree(root)); | 
 |   } else { | 
 |     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_P(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()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   TestNavigationObserver observer(shell()->web_contents()); | 
 |   ASSERT_EQ(2U, root->child_count()); | 
 |  | 
 |   // Make sure node2 points to the correct cross-site page. | 
 |   GURL site_b_url = embedded_test_server()->GetURL( | 
 |       "bar.com", "/cross_site_iframe_factory.html?bar.com()"); | 
 |   FrameTreeNode* node2 = root->child_at(0); | 
 |   EXPECT_EQ(site_b_url, node2->current_url()); | 
 |  | 
 |   // Kill that cross-site renderer. | 
 |   RenderProcessHost* child_process = node2->current_frame_host()->GetProcess(); | 
 |   RenderProcessHostWatcher crash_observer( | 
 |       child_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); | 
 |   child_process->Shutdown(0); | 
 |   crash_observer.Wait(); | 
 |  | 
 |   // Now navigate the second iframe (node3) to the same site as the node2. | 
 |   FrameTreeNode* node3 = root->child_at(1); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(node3, site_b_url)); | 
 |   EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |   EXPECT_EQ(site_b_url, observer.last_navigation_url()); | 
 | } | 
 |  | 
 | // This test ensures that WebContentsImpl::FocusOwningWebContents does not crash | 
 | // the browser if the currently focused frame's renderer has disappeared. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, RemoveFocusFromKilledFrame) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "foo.com", "/cross_site_iframe_factory.html?foo.com(bar.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()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   TestNavigationObserver observer(shell()->web_contents()); | 
 |   ASSERT_EQ(1U, root->child_count()); | 
 |  | 
 |   // Make sure node2 points to the correct cross-site page. | 
 |   GURL site_b_url = embedded_test_server()->GetURL( | 
 |       "bar.com", "/cross_site_iframe_factory.html?bar.com()"); | 
 |   FrameTreeNode* node2 = root->child_at(0); | 
 |   EXPECT_EQ(site_b_url, node2->current_url()); | 
 |  | 
 |   web_contents()->SetFocusedFrame( | 
 |       node2, node2->current_frame_host()->GetSiteInstance()->group()); | 
 |  | 
 |   // Kill that cross-site renderer. | 
 |   RenderProcessHost* child_process = node2->current_frame_host()->GetProcess(); | 
 |   RenderProcessHostWatcher crash_observer( | 
 |       child_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); | 
 |   child_process->Shutdown(0); | 
 |   crash_observer.Wait(); | 
 |  | 
 |   // Try to focus the root's owning WebContents. | 
 |   web_contents()->FocusOwningWebContents( | 
 |       root->current_frame_host()->GetRenderWidgetHost()); | 
 | } | 
 |  | 
 | // This test is similar to | 
 | // SitePerProcessBrowserTest.NavigateRemoteFrameToKilledProcess with | 
 | // addition that node2 also has a cross-origin frame to site C. | 
 | // | 
 | //     1          A                  A                       A | 
 | //    / \        / \                / \                     / \  . | 
 | //   2   3 ->   B   A -> Kill B -> B*   A -> Navigate 3 -> B*  B | 
 | //  /          / | 
 | // 4          C | 
 | // | 
 | // Initially, node1.proxy_hosts_ = {B, C} | 
 | // After we kill B, we make sure B stays in node1.proxy_hosts_, but | 
 | // C gets cleared from node1.proxy_hosts_. | 
 | // | 
 | // Note that due to http://crbug.com/450681, node2 cannot be re-navigated to | 
 | // site B and stays in not rendered state. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        NavigateRemoteFrameToKilledProcessWithSubtree) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(bar(baz), 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()->GetPrimaryFrameTree().root(); | 
 |   TestNavigationObserver observer(shell()->web_contents()); | 
 |  | 
 |   ASSERT_EQ(2U, root->child_count()); | 
 |  | 
 |   GURL site_b_url(embedded_test_server()->GetURL( | 
 |       "bar.com", "/cross_site_iframe_factory.html?bar(baz())")); | 
 |   // We can't use a TestNavigationObserver to verify the URL here, | 
 |   // since the frame has children that may have clobbered it in the observer. | 
 |   EXPECT_EQ(site_b_url, root->child_at(0)->current_url()); | 
 |  | 
 |   // Ensure that a new process is created for node2. | 
 |   EXPECT_NE(shell()->web_contents()->GetSiteInstance(), | 
 |             root->child_at(0)->current_frame_host()->GetSiteInstance()); | 
 |   // Ensure that a new process is *not* created for node3. | 
 |   EXPECT_EQ(shell()->web_contents()->GetSiteInstance(), | 
 |             root->child_at(1)->current_frame_host()->GetSiteInstance()); | 
 |  | 
 |   ASSERT_EQ(1U, root->child_at(0)->child_count()); | 
 |  | 
 |   // Make sure node4 points to the correct cross-site page. | 
 |   FrameTreeNode* node4 = root->child_at(0)->child_at(0); | 
 |   GURL site_c_url(embedded_test_server()->GetURL( | 
 |       "baz.com", "/cross_site_iframe_factory.html?baz()")); | 
 |   EXPECT_EQ(site_c_url, node4->current_url()); | 
 |  | 
 |   // |site_instance_c| is expected to go away once we kill |child_process_b| | 
 |   // below, so create a local scope so we can extend the lifetime of | 
 |   // |site_instance_c| with a refptr. | 
 |   { | 
 |     // Initially each frame has proxies for the other sites. | 
 |     EXPECT_EQ( | 
 |         " Site A ------------ proxies for B C\n" | 
 |         "   |--Site B ------- proxies for A C\n" | 
 |         "   |    +--Site C -- proxies for A B\n" | 
 |         "   +--Site A ------- proxies for B C\n" | 
 |         "Where A = http://a.com/\n" | 
 |         "      B = http://bar.com/\n" | 
 |         "      C = http://baz.com/", | 
 |         DepictFrameTree(root)); | 
 |  | 
 |     // Kill the render process for Site B. | 
 |     RenderProcessHost* child_process_b = | 
 |         root->child_at(0)->current_frame_host()->GetProcess(); | 
 |     RenderProcessHostWatcher crash_observer( | 
 |         child_process_b, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); | 
 |     child_process_b->Shutdown(0); | 
 |     crash_observer.Wait(); | 
 |  | 
 |     // The Site C frame (a child of the crashed Site B frame) should go away, | 
 |     // and there should be no remaining proxies for site C anywhere. | 
 |     EXPECT_EQ( | 
 |         " Site A ------------ proxies for B\n" | 
 |         "   |--Site B ------- proxies for A\n" | 
 |         "   +--Site A ------- proxies for B\n" | 
 |         "Where A = http://a.com/\n" | 
 |         "      B = http://bar.com/ (no process)", | 
 |         DepictFrameTree(root)); | 
 |   } | 
 |  | 
 |   // Now navigate the second iframe (node3) to Site B also. | 
 |   FrameTreeNode* node3 = root->child_at(1); | 
 |   GURL url = embedded_test_server()->GetURL("bar.com", "/title1.html"); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(node3, url)); | 
 |   EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |   EXPECT_EQ(url, observer.last_navigation_url()); | 
 |  | 
 |   EXPECT_EQ( | 
 |       " Site A ------------ proxies for B\n" | 
 |       "   |--Site B ------- proxies for A\n" | 
 |       "   +--Site B ------- proxies for A\n" | 
 |       "Where A = http://a.com/\n" | 
 |       "      B = http://bar.com/", | 
 |       DepictFrameTree(root)); | 
 | } | 
 |  | 
 | // Ensure that the renderer process doesn't crash when the main frame navigates | 
 | // a remote child to a page that results in a network error. | 
 | // See https://crbug.com/558016. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, NavigateRemoteAfterError) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(a)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   // It is safe to obtain the root frame tree node here, as it doesn't change. | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // Load same-site page into iframe. | 
 |   { | 
 |     TestNavigationObserver observer(shell()->web_contents()); | 
 |     FrameTreeNode* child = root->child_at(0); | 
 |     GURL http_url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |     EXPECT_TRUE(NavigateToURLFromRenderer(child, http_url)); | 
 |     EXPECT_EQ(http_url, observer.last_navigation_url()); | 
 |     EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |     observer.Wait(); | 
 |   } | 
 |  | 
 |   // Load cross-site page into iframe. | 
 |   { | 
 |     TestNavigationObserver observer(shell()->web_contents()); | 
 |     FrameTreeNode* child = root->child_at(0); | 
 |     GURL url = embedded_test_server()->GetURL("foo.com", "/title2.html"); | 
 |     EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(0), url)); | 
 |     EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |     EXPECT_EQ(url, observer.last_navigation_url()); | 
 |     observer.Wait(); | 
 |  | 
 |     // Ensure that we have created a new process for the subframe. | 
 |     EXPECT_EQ( | 
 |         " Site A ------------ proxies for B\n" | 
 |         "   +--Site B ------- proxies for A\n" | 
 |         "Where A = http://a.com/\n" | 
 |         "      B = http://foo.com/", | 
 |         DepictFrameTree(root)); | 
 |     SiteInstance* site_instance = | 
 |         child->current_frame_host()->GetSiteInstance(); | 
 |     EXPECT_NE(shell()->web_contents()->GetSiteInstance(), site_instance); | 
 |   } | 
 |  | 
 |   // Stop the test server and try to navigate the remote frame. | 
 |   { | 
 |     GURL url = embedded_test_server()->GetURL("bar.com", "/title3.html"); | 
 |     EXPECT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete()); | 
 |     NavigateIframeToURL(shell()->web_contents(), "child-0", url); | 
 |   } | 
 | } | 
 |  | 
 | // Ensure that a cross-site page ends up in the correct process when it | 
 | // successfully loads after earlier encountering a network error for it. | 
 | // See https://crbug.com/560511. | 
 | // TODO(creis): Make the net error page show in the correct process as well, | 
 | // per https://crbug.com/588314. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, ProcessTransferAfterError) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(a)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   // It is safe to obtain the root frame tree node here, as it doesn't change. | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   FrameTreeNode* child = root->child_at(0); | 
 |   GURL url_a = child->current_url(); | 
 |  | 
 |   // Disable host resolution in the test server and try to navigate the subframe | 
 |   // cross-site, which will lead to a committed net error. | 
 |   GURL url_b = embedded_test_server()->GetURL("b.com", "/title3.html"); | 
 |   auto url_loader_interceptor = std::make_unique<URLLoaderInterceptor>( | 
 |       base::BindRepeating([](URLLoaderInterceptor::RequestParams* params) { | 
 |         network::URLLoaderCompletionStatus status; | 
 |         status.error_code = net::ERR_NOT_IMPLEMENTED; | 
 |         params->client->OnComplete(status); | 
 |         return true; | 
 |       })); | 
 |  | 
 |   TestNavigationObserver observer(shell()->web_contents()); | 
 |   NavigateIframeToURL(shell()->web_contents(), "child-0", url_b); | 
 |   EXPECT_FALSE(observer.last_navigation_succeeded()); | 
 |   EXPECT_EQ(url_b, observer.last_navigation_url()); | 
 |   EXPECT_EQ(2, shell()->web_contents()->GetController().GetEntryCount()); | 
 |  | 
 |   // Ensure that we have created a new process for the subframe. | 
 |   EXPECT_EQ( | 
 |       " Site A ------------ proxies for B\n" | 
 |       "   +--Site B ------- proxies for A\n" | 
 |       "Where A = http://a.com/\n" | 
 |       "      B = http://b.com/", | 
 |       DepictFrameTree(root)); | 
 |   EXPECT_NE(shell()->web_contents()->GetSiteInstance(), | 
 |             child->current_frame_host()->GetSiteInstance()); | 
 |  | 
 |   // We have switched RenderFrameHosts for the subframe, so the last successful | 
 |   // url should be empty (since the frame only loaded an error page). | 
 |   EXPECT_EQ(GURL(), child->current_frame_host()->last_successful_url()); | 
 |   EXPECT_EQ(url_b, child->current_url()); | 
 |   EXPECT_EQ("null", child->current_origin().Serialize()); | 
 |  | 
 |   // Try again after re-enabling host resolution. | 
 |   url_loader_interceptor.reset(); | 
 |  | 
 |   // Activate the root frame by executing a dummy script. | 
 |   // | 
 |   // TODO(mustaq): Why does the |back_load_observer.Wait()| below time out | 
 |   // without the user activation? | 
 |   EXPECT_TRUE(ExecJs(root, "// No-op script")); | 
 |   NavigateIframeToURL(shell()->web_contents(), "child-0", url_b); | 
 |   EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |   EXPECT_EQ(url_b, observer.last_navigation_url()); | 
 |  | 
 |   // The FrameTreeNode should have updated its URL and origin. | 
 |   EXPECT_EQ(url_b, child->current_frame_host()->last_successful_url()); | 
 |   EXPECT_EQ(url_b, child->current_url()); | 
 |   EXPECT_EQ(url_b.DeprecatedGetOriginAsURL().spec(), | 
 |             child->current_origin().Serialize() + '/'); | 
 |  | 
 |   // Ensure that we have created a new process for the subframe. | 
 |   EXPECT_EQ( | 
 |       " Site A ------------ proxies for B\n" | 
 |       "   +--Site B ------- proxies for A\n" | 
 |       "Where A = http://a.com/\n" | 
 |       "      B = http://b.com/", | 
 |       DepictFrameTree(root)); | 
 |   EXPECT_NE(shell()->web_contents()->GetSiteInstance(), | 
 |             child->current_frame_host()->GetSiteInstance()); | 
 |  | 
 |   // Make sure that the navigation replaced the error page and that going back | 
 |   // ends up on the original site. | 
 |   EXPECT_EQ(2, shell()->web_contents()->GetController().GetEntryCount()); | 
 |   { | 
 |     RenderFrameDeletedObserver deleted_observer(child->current_frame_host()); | 
 |     TestNavigationObserver back_load_observer(shell()->web_contents()); | 
 |     shell()->web_contents()->GetController().GoBack(); | 
 |     back_load_observer.Wait(); | 
 |  | 
 |     // Wait for the old process to exit, to verify that the proxies go away. | 
 |     deleted_observer.WaitUntilDeleted(); | 
 |   } | 
 |   EXPECT_EQ( | 
 |       " Site A\n" | 
 |       "   +--Site A\n" | 
 |       "Where A = http://a.com/", | 
 |       DepictFrameTree(root)); | 
 |   EXPECT_EQ(shell()->web_contents()->GetSiteInstance(), | 
 |             child->current_frame_host()->GetSiteInstance()); | 
 |   EXPECT_EQ(url_a, child->current_frame_host()->last_successful_url()); | 
 |   EXPECT_EQ(url_a, child->current_url()); | 
 |   EXPECT_EQ(url_a.DeprecatedGetOriginAsURL().spec(), | 
 |             child->current_origin().Serialize() + '/'); | 
 | } | 
 |  | 
 | // Verify that killing a cross-site frame's process B and then navigating a | 
 | // frame to B correctly recreates all proxies in B. | 
 | // | 
 | //      1           A                    A          A | 
 | //    / | \       / | \                / | \      / | \  . | 
 | //   2  3  4 ->  B  A  A -> Kill B -> B* A  A -> B* B  A | 
 | // | 
 | // After the last step, the test sends a postMessage from node 3 to node 4, | 
 | // verifying that a proxy for node 4 has been recreated in process B.  This | 
 | // verifies the fix for https://crbug.com/478892. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        NavigatingToKilledProcessRestoresAllProxies) { | 
 |   // Navigate to a page with three frames: one cross-site and two same-site. | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/frame_tree/page_with_three_frames.html")); | 
 |   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()->GetPrimaryFrameTree().root(); | 
 |   TestNavigationObserver observer(shell()->web_contents()); | 
 |  | 
 |   EXPECT_EQ( | 
 |       " Site A ------------ proxies for B\n" | 
 |       "   |--Site B ------- proxies for A\n" | 
 |       "   |--Site A ------- proxies for B\n" | 
 |       "   +--Site A ------- proxies for B\n" | 
 |       "Where A = http://a.com/\n" | 
 |       "      B = http://b.com/", | 
 |       DepictFrameTree(root)); | 
 |  | 
 |   // Kill the first subframe's b.com renderer. | 
 |   RenderProcessHost* child_process = | 
 |       root->child_at(0)->current_frame_host()->GetProcess(); | 
 |   RenderProcessHostWatcher crash_observer( | 
 |       child_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); | 
 |   child_process->Shutdown(0); | 
 |   crash_observer.Wait(); | 
 |  | 
 |   // Navigate the second subframe to b.com to recreate the b.com process. | 
 |   GURL b_url = embedded_test_server()->GetURL("b.com", "/post_message.html"); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(1), b_url)); | 
 |   EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |   EXPECT_EQ(b_url, observer.last_navigation_url()); | 
 |   EXPECT_TRUE(root->child_at(1)->current_frame_host()->IsRenderFrameLive()); | 
 |  | 
 |   EXPECT_EQ( | 
 |       " Site A ------------ proxies for B\n" | 
 |       "   |--Site B ------- proxies for A\n" | 
 |       "   |--Site B ------- proxies for A\n" | 
 |       "   +--Site A ------- proxies for B\n" | 
 |       "Where A = http://a.com/\n" | 
 |       "      B = http://b.com/", | 
 |       DepictFrameTree(root)); | 
 |  | 
 |   // Check that third subframe's proxy is available in the b.com process by | 
 |   // sending it a postMessage from second subframe, and waiting for a reply. | 
 |   PostMessageAndWaitForReply(root->child_at(1), | 
 |                              "postToSibling('subframe-msg','frame3')", | 
 |                              "\"done-frame2\""); | 
 | } | 
 |  | 
 | // Verify that proxy creation doesn't recreate a crashed process if no frame | 
 | // will be created in it. | 
 | // | 
 | //      1           A                    A          A | 
 | //    / | \       / | \                / | \      / | \    . | 
 | //   2  3  4 ->  B  A  A -> Kill B -> B* A  A -> B* A  A | 
 | //                                                      \  . | 
 | //                                                       A | 
 | // | 
 | // The test kills process B (node 2), creates a child frame of node 4 in | 
 | // process A, and then checks that process B isn't resurrected to create a | 
 | // proxy for the new child frame.  See https://crbug.com/476846. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        CreateChildFrameAfterKillingProcess) { | 
 |   // Navigate to a page with three frames: one cross-site and two same-site. | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/frame_tree/page_with_three_frames.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   // It is safe to obtain the root frame tree node here, as it doesn't change. | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   EXPECT_EQ( | 
 |       " Site A ------------ proxies for B\n" | 
 |       "   |--Site B ------- proxies for A\n" | 
 |       "   |--Site A ------- proxies for B\n" | 
 |       "   +--Site A ------- proxies for B\n" | 
 |       "Where A = http://a.com/\n" | 
 |       "      B = http://b.com/", | 
 |       DepictFrameTree(root)); | 
 |   SiteInstanceImpl* b_site_instance = | 
 |       root->child_at(0)->current_frame_host()->GetSiteInstance(); | 
 |  | 
 |   // Kill the first subframe's renderer (B). | 
 |   RenderProcessHost* child_process = | 
 |       root->child_at(0)->current_frame_host()->GetProcess(); | 
 |   RenderProcessHostWatcher crash_observer( | 
 |       child_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); | 
 |   child_process->Shutdown(0); | 
 |   crash_observer.Wait(); | 
 |  | 
 |   // Add a new child frame to the third subframe. | 
 |   RenderFrameHostCreatedObserver frame_observer(shell()->web_contents(), 1); | 
 |   EXPECT_TRUE( | 
 |       ExecJs(root->child_at(2), | 
 |              "document.body.appendChild(document.createElement('iframe'));")); | 
 |   frame_observer.Wait(); | 
 |  | 
 |   // The new frame should have a RenderFrameProxyHost for B, but it should not | 
 |   // be alive, and B should still not have a process (verified by last line of | 
 |   // expected DepictFrameTree output). | 
 |   EXPECT_EQ( | 
 |       " Site A ------------ proxies for B\n" | 
 |       "   |--Site B ------- proxies for A\n" | 
 |       "   |--Site A ------- proxies for B\n" | 
 |       "   +--Site A ------- proxies for B\n" | 
 |       "        +--Site A -- proxies for B\n" | 
 |       "Where A = http://a.com/\n" | 
 |       "      B = http://b.com/ (no process)", | 
 |       DepictFrameTree(root)); | 
 |   FrameTreeNode* grandchild = root->child_at(2)->child_at(0); | 
 |   RenderFrameProxyHost* grandchild_rfph = | 
 |       grandchild->current_frame_host() | 
 |           ->browsing_context_state() | 
 |           ->GetRenderFrameProxyHost(b_site_instance->group()); | 
 |   EXPECT_FALSE(grandchild_rfph->is_render_frame_proxy_live()); | 
 |  | 
 |   // Navigate the second subframe to b.com to recreate process B. | 
 |   TestNavigationObserver observer(shell()->web_contents()); | 
 |   GURL b_url = embedded_test_server()->GetURL("b.com", "/title1.html"); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(1), b_url)); | 
 |   EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |   EXPECT_EQ(b_url, observer.last_navigation_url()); | 
 |  | 
 |   // Ensure that the grandchild `blink::RemoteFrame` in B was created when | 
 |   // process B was restored. | 
 |   EXPECT_TRUE(grandchild_rfph->is_render_frame_proxy_live()); | 
 | } | 
 |  | 
 | // Verify that creating a child frame after killing and reloading an opener | 
 | // process doesn't crash. See https://crbug.com/501152. | 
 | //   1. Navigate to site A. | 
 | //   2. Open a popup with window.open and navigate it cross-process to site B. | 
 | //   3. Kill process A for the original tab. | 
 | //   4. Reload the original tab to resurrect process A. | 
 | //   5. Add a child frame to the top-level frame in the popup tab B. | 
 | // In step 5, we try to create proxies for the child frame in all SiteInstances | 
 | // for which its parent has proxies.  This includes A.  However, even though | 
 | // process A is live (step 4), the parent proxy in A is not live (which was | 
 | // incorrectly assumed previously).  This is because step 4 does not resurrect | 
 | // proxies for popups opened before the crash. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        CreateChildFrameAfterKillingOpener) { | 
 |   GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   // It is safe to obtain the root frame tree node here, as it doesn't change. | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   SiteInstanceImpl* site_instance_a = | 
 |       root->current_frame_host()->GetSiteInstance(); | 
 |  | 
 |   // Open a popup and navigate it cross-process to b.com. | 
 |   ShellAddedObserver new_shell_observer; | 
 |   EXPECT_TRUE(ExecJs(root, "popup = window.open('about:blank');")); | 
 |   Shell* popup = new_shell_observer.GetShell(); | 
 |   GURL popup_url(embedded_test_server()->GetURL("b.com", "/title2.html")); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(popup, popup_url)); | 
 |  | 
 |   // Verify that each top-level frame has proxies in the other's SiteInstance. | 
 |   FrameTreeNode* popup_root = | 
 |       static_cast<WebContentsImpl*>(popup->web_contents()) | 
 |           ->GetPrimaryFrameTree() | 
 |           .root(); | 
 |   EXPECT_EQ( | 
 |       " Site A ------------ proxies for B\n" | 
 |       "Where A = http://a.com/\n" | 
 |       "      B = http://b.com/", | 
 |       DepictFrameTree(root)); | 
 |   EXPECT_EQ( | 
 |       " Site B ------------ proxies for A\n" | 
 |       "Where A = http://a.com/\n" | 
 |       "      B = http://b.com/", | 
 |       DepictFrameTree(popup_root)); | 
 |  | 
 |   // Kill the first window's renderer (a.com). | 
 |   RenderProcessHost* child_process = root->current_frame_host()->GetProcess(); | 
 |   RenderProcessHostWatcher crash_observer( | 
 |       child_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); | 
 |   child_process->Shutdown(0); | 
 |   crash_observer.Wait(); | 
 |   EXPECT_FALSE(root->current_frame_host()->IsRenderFrameLive()); | 
 |  | 
 |   // The proxy for the popup in a.com should've died. | 
 |   RenderFrameProxyHost* rfph = | 
 |       popup_root->current_frame_host() | 
 |           ->browsing_context_state() | 
 |           ->GetRenderFrameProxyHost(site_instance_a->group()); | 
 |   EXPECT_FALSE(rfph->is_render_frame_proxy_live()); | 
 |  | 
 |   // Recreate the a.com renderer. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |   EXPECT_TRUE(root->current_frame_host()->IsRenderFrameLive()); | 
 |  | 
 |   // The popup's proxy in a.com should still not be live. Re-navigating the | 
 |   // main window to a.com doesn't reinitialize a.com proxies for popups | 
 |   // previously opened from the main window. | 
 |   EXPECT_FALSE(rfph->is_render_frame_proxy_live()); | 
 |  | 
 |   // Add a new child frame on the popup. | 
 |   RenderFrameHostCreatedObserver frame_observer(popup->web_contents(), 1); | 
 |   EXPECT_TRUE(ExecJs( | 
 |       popup, "document.body.appendChild(document.createElement('iframe'));")); | 
 |   frame_observer.Wait(); | 
 |  | 
 |   // Both the child frame's and its parent's proxies should still not be live. | 
 |   // The main page can't reach them since it lost reference to the popup after | 
 |   // it crashed, so there is no need to create them. | 
 |   EXPECT_FALSE(rfph->is_render_frame_proxy_live()); | 
 |   RenderFrameProxyHost* child_rfph = | 
 |       popup_root->child_at(0) | 
 |           ->current_frame_host() | 
 |           ->browsing_context_state() | 
 |           ->GetRenderFrameProxyHost(site_instance_a->group()); | 
 |   EXPECT_TRUE(child_rfph); | 
 |   EXPECT_FALSE(child_rfph->is_render_frame_proxy_live()); | 
 | } | 
 |  | 
 | // In A-embed-B-embed-C scenario, verify that killing process B clears proxies | 
 | // of C from the tree. | 
 | // | 
 | //     1          A                  A | 
 | //    / \        / \                / \    . | 
 | //   2   3 ->   B   A -> Kill B -> B*  A | 
 | //  /          / | 
 | // 4          C | 
 | // | 
 | // node1 is the root. | 
 | // Initially, both node1.proxy_hosts_ and node3.proxy_hosts_ contain C. | 
 | // After we kill B, make sure proxies for C are cleared. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        KillingRendererClearsDescendantProxies) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/frame_tree/page_with_two_frames_nested.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()->GetPrimaryFrameTree().root(); | 
 |   ASSERT_EQ(2U, root->child_count()); | 
 |  | 
 |   GURL site_b_url(embedded_test_server()->GetURL( | 
 |       "bar.com", "/frame_tree/page_with_one_frame.html")); | 
 |   // We can't use a TestNavigationObserver to verify the URL here, | 
 |   // since the frame has children that may have clobbered it in the observer. | 
 |   EXPECT_EQ(site_b_url, root->child_at(0)->current_url()); | 
 |  | 
 |   // Ensure that a new process is created for node2. | 
 |   EXPECT_NE(shell()->web_contents()->GetSiteInstance(), | 
 |             root->child_at(0)->current_frame_host()->GetSiteInstance()); | 
 |   // Ensure that a new process is *not* created for node3. | 
 |   EXPECT_EQ(shell()->web_contents()->GetSiteInstance(), | 
 |             root->child_at(1)->current_frame_host()->GetSiteInstance()); | 
 |  | 
 |   ASSERT_EQ(1U, root->child_at(0)->child_count()); | 
 |  | 
 |   // Make sure node4 points to the correct cross-site-page. | 
 |   FrameTreeNode* node4 = root->child_at(0)->child_at(0); | 
 |   GURL site_c_url(embedded_test_server()->GetURL("baz.com", "/title1.html")); | 
 |   EXPECT_EQ(site_c_url, node4->current_url()); | 
 |  | 
 |   // |site_instance_c_group|'s frames and proxies are expected to go away once | 
 |   // we kill |child_process_b| below. | 
 |   scoped_refptr<SiteInstanceGroup> site_instance_c_group = | 
 |       node4->current_frame_host()->GetSiteInstance()->group(); | 
 |  | 
 |   // Initially proxies for both B and C will be present in the root. | 
 |   EXPECT_EQ( | 
 |       " Site A ------------ proxies for B C\n" | 
 |       "   |--Site B ------- proxies for A C\n" | 
 |       "   |    +--Site C -- proxies for A B\n" | 
 |       "   +--Site A ------- proxies for B C\n" | 
 |       "Where A = http://a.com/\n" | 
 |       "      B = http://bar.com/\n" | 
 |       "      C = http://baz.com/", | 
 |       DepictFrameTree(root)); | 
 |  | 
 |   EXPECT_GT(site_instance_c_group->active_frame_count(), 0U); | 
 |  | 
 |   // Kill process B. | 
 |   RenderProcessHost* child_process_b = | 
 |       root->child_at(0)->current_frame_host()->GetProcess(); | 
 |   RenderProcessHostWatcher crash_observer( | 
 |       child_process_b, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); | 
 |   child_process_b->Shutdown(0); | 
 |   crash_observer.Wait(); | 
 |  | 
 |   // Make sure proxy C has gone from root. | 
 |   // Make sure proxy C has gone from node3 as well. | 
 |   // Make sure proxy B stays around in root and node3. | 
 |   EXPECT_EQ( | 
 |       " Site A ------------ proxies for B\n" | 
 |       "   |--Site B ------- proxies for A\n" | 
 |       "   +--Site A ------- proxies for B\n" | 
 |       "Where A = http://a.com/\n" | 
 |       "      B = http://bar.com/ (no process)", | 
 |       DepictFrameTree(root)); | 
 |  | 
 |   EXPECT_EQ(0U, site_instance_c_group->active_frame_count()); | 
 | } | 
 |  | 
 | // Crash a subframe and ensures its children are cleared from the FrameTree. | 
 | // See http://crbug.com/338508. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, CrashSubframe) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   // Check the subframe process. | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   EXPECT_EQ( | 
 |       " Site A ------------ proxies for B\n" | 
 |       "   +--Site B ------- proxies for A\n" | 
 |       "Where A = http://a.com/\n" | 
 |       "      B = http://b.com/", | 
 |       DepictFrameTree(root)); | 
 |   FrameTreeNode* child = root->child_at(0); | 
 |   EXPECT_TRUE( | 
 |       child->current_frame_host()->render_view_host()->IsRenderViewLive()); | 
 |   EXPECT_TRUE(child->current_frame_host()->IsRenderFrameLive()); | 
 |  | 
 |   // Crash the subframe process. | 
 |   RenderProcessHost* root_process = root->current_frame_host()->GetProcess(); | 
 |   RenderProcessHost* child_process = child->current_frame_host()->GetProcess(); | 
 |   { | 
 |     RenderProcessHostWatcher crash_observer( | 
 |         child_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); | 
 |     child_process->Shutdown(0); | 
 |     crash_observer.Wait(); | 
 |   } | 
 |  | 
 |   // Ensure that the child frame still exists but has been cleared. | 
 |   EXPECT_EQ( | 
 |       " Site A ------------ proxies for B\n" | 
 |       "   +--Site B ------- proxies for A\n" | 
 |       "Where A = http://a.com/\n" | 
 |       "      B = http://b.com/ (no process)", | 
 |       DepictFrameTree(root)); | 
 |   EXPECT_EQ(1U, root->child_count()); | 
 |   EXPECT_EQ(main_url, root->current_url()); | 
 |   EXPECT_EQ(GURL(), child->current_url()); | 
 |  | 
 |   EXPECT_FALSE( | 
 |       child->current_frame_host()->render_view_host()->IsRenderViewLive()); | 
 |   EXPECT_FALSE(child->current_frame_host()->IsRenderFrameLive()); | 
 |  | 
 |   // Now crash the top-level page to clear the child frame. | 
 |   { | 
 |     RenderProcessHostWatcher crash_observer( | 
 |         root_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); | 
 |     root_process->Shutdown(0); | 
 |     crash_observer.Wait(); | 
 |   } | 
 |   EXPECT_EQ(0U, root->child_count()); | 
 |   EXPECT_EQ(GURL(), root->current_url()); | 
 | } | 
 |  | 
 | // When a new subframe is added, related SiteInstances that can reach the | 
 | // subframe should create proxies for it (https://crbug.com/423587).  This test | 
 | // checks that if A embeds B and later adds a new subframe A2, A2 gets a proxy | 
 | // in B's process. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, CreateProxiesForNewFrames) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "b.com", "/frame_tree/page_with_one_frame.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   // It is safe to obtain the root frame tree node here, as it doesn't change. | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   ASSERT_EQ(1U, root->child_count()); | 
 |  | 
 |   // Make sure the frame starts out at the correct cross-site URL. | 
 |   EXPECT_EQ(embedded_test_server()->GetURL("baz.com", "/title1.html"), | 
 |             root->child_at(0)->current_url()); | 
 |  | 
 |   EXPECT_EQ( | 
 |       " Site A ------------ proxies for B\n" | 
 |       "   +--Site B ------- proxies for A\n" | 
 |       "Where A = http://b.com/\n" | 
 |       "      B = http://baz.com/", | 
 |       DepictFrameTree(root)); | 
 |  | 
 |   // Add a new child frame to the top-level frame. | 
 |   RenderFrameHostCreatedObserver frame_observer(shell()->web_contents(), 1); | 
 |   EXPECT_TRUE(ExecJs(shell(), "addFrame('about:blank');")); | 
 |   frame_observer.Wait(); | 
 |  | 
 |   // The new frame should have a proxy in Site B, for use by the old frame. | 
 |   EXPECT_EQ( | 
 |       " Site A ------------ proxies for B\n" | 
 |       "   |--Site B ------- proxies for A\n" | 
 |       "   +--Site A ------- proxies for B\n" | 
 |       "Where A = http://b.com/\n" | 
 |       "      B = http://baz.com/", | 
 |       DepictFrameTree(root)); | 
 | } | 
 |  | 
 | // TODO(nasko): Disable this test until out-of-process iframes is ready and the | 
 | // security checks are back in place. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        DISABLED_CrossSiteIframeRedirectOnce) { | 
 |   net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS); | 
 |   https_server.ServeFilesFromSourceDirectory(GetTestDataFilePath()); | 
 |   ASSERT_TRUE(https_server.Start()); | 
 |  | 
 |   GURL main_url(embedded_test_server()->GetURL("/site_per_process_main.html")); | 
 |   GURL http_url(embedded_test_server()->GetURL("/title1.html")); | 
 |   GURL https_url(https_server.GetURL("/title1.html")); | 
 |  | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   TestNavigationObserver observer(shell()->web_contents()); | 
 |   { | 
 |     // Load cross-site client-redirect page into Iframe. | 
 |     // Should be blocked. | 
 |     GURL client_redirect_https_url( | 
 |         https_server.GetURL("/client-redirect?/title1.html")); | 
 |     EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test", | 
 |                                     client_redirect_https_url)); | 
 |     // DidFailProvisionalLoad when navigating to client_redirect_https_url. | 
 |     EXPECT_EQ(observer.last_navigation_url(), client_redirect_https_url); | 
 |     EXPECT_FALSE(observer.last_navigation_succeeded()); | 
 |   } | 
 |  | 
 |   { | 
 |     // Load cross-site server-redirect page into Iframe, | 
 |     // which redirects to same-site page. | 
 |     GURL server_redirect_http_url( | 
 |         https_server.GetURL("/server-redirect?" + http_url.spec())); | 
 |     EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test", | 
 |                                     server_redirect_http_url)); | 
 |     EXPECT_EQ(observer.last_navigation_url(), http_url); | 
 |     EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |   } | 
 |  | 
 |   { | 
 |     // Load cross-site server-redirect page into Iframe, | 
 |     // which redirects to cross-site page. | 
 |     GURL server_redirect_http_url( | 
 |         https_server.GetURL("/server-redirect?/title1.html")); | 
 |     EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test", | 
 |                                     server_redirect_http_url)); | 
 |     // DidFailProvisionalLoad when navigating to https_url. | 
 |     EXPECT_EQ(observer.last_navigation_url(), https_url); | 
 |     EXPECT_FALSE(observer.last_navigation_succeeded()); | 
 |   } | 
 |  | 
 |   { | 
 |     // Load same-site server-redirect page into Iframe, | 
 |     // which redirects to cross-site page. | 
 |     GURL server_redirect_http_url( | 
 |         embedded_test_server()->GetURL("/server-redirect?" + https_url.spec())); | 
 |     EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test", | 
 |                                     server_redirect_http_url)); | 
 |  | 
 |     EXPECT_EQ(observer.last_navigation_url(), https_url); | 
 |     EXPECT_FALSE(observer.last_navigation_succeeded()); | 
 |   } | 
 |  | 
 |   { | 
 |     // Load same-site client-redirect page into Iframe, | 
 |     // which redirects to cross-site page. | 
 |     GURL client_redirect_http_url( | 
 |         embedded_test_server()->GetURL("/client-redirect?" + https_url.spec())); | 
 |  | 
 |     LoadStopObserver load_observer2(shell()->web_contents()); | 
 |  | 
 |     EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test", | 
 |                                     client_redirect_http_url)); | 
 |  | 
 |     // Same-site Client-Redirect Page should be loaded successfully. | 
 |     EXPECT_EQ(observer.last_navigation_url(), client_redirect_http_url); | 
 |     EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |  | 
 |     // Redirecting to Cross-site Page should be blocked. | 
 |     load_observer2.Wait(); | 
 |     EXPECT_EQ(observer.last_navigation_url(), https_url); | 
 |     EXPECT_FALSE(observer.last_navigation_succeeded()); | 
 |   } | 
 |  | 
 |   { | 
 |     // Load same-site server-redirect page into Iframe, | 
 |     // which redirects to same-site page. | 
 |     GURL server_redirect_http_url( | 
 |         embedded_test_server()->GetURL("/server-redirect?/title1.html")); | 
 |     EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test", | 
 |                                     server_redirect_http_url)); | 
 |     EXPECT_EQ(observer.last_navigation_url(), http_url); | 
 |     EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |   } | 
 |  | 
 |   { | 
 |     // Load same-site client-redirect page into Iframe, | 
 |     // which redirects to same-site page. | 
 |     GURL client_redirect_http_url( | 
 |         embedded_test_server()->GetURL("/client-redirect?" + http_url.spec())); | 
 |     LoadStopObserver load_observer2(shell()->web_contents()); | 
 |  | 
 |     EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test", | 
 |                                     client_redirect_http_url)); | 
 |  | 
 |     // Same-site Client-Redirect Page should be loaded successfully. | 
 |     EXPECT_EQ(observer.last_navigation_url(), client_redirect_http_url); | 
 |     EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |  | 
 |     // Redirecting to Same-site Page should be loaded successfully. | 
 |     load_observer2.Wait(); | 
 |     EXPECT_EQ(observer.last_navigation_url(), http_url); | 
 |     EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |   } | 
 | } | 
 |  | 
 | // TODO(nasko): Disable this test until out-of-process iframes is ready and the | 
 | // security checks are back in place. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        DISABLED_CrossSiteIframeRedirectTwice) { | 
 |   net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS); | 
 |   https_server.ServeFilesFromSourceDirectory(GetTestDataFilePath()); | 
 |   ASSERT_TRUE(https_server.Start()); | 
 |  | 
 |   GURL main_url(embedded_test_server()->GetURL("/site_per_process_main.html")); | 
 |   GURL http_url(embedded_test_server()->GetURL("/title1.html")); | 
 |   GURL https_url(https_server.GetURL("/title1.html")); | 
 |  | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   TestNavigationObserver observer(shell()->web_contents()); | 
 |   { | 
 |     // Load client-redirect page pointing to a cross-site client-redirect page, | 
 |     // which eventually redirects back to same-site page. | 
 |     GURL client_redirect_https_url( | 
 |         https_server.GetURL("/client-redirect?" + http_url.spec())); | 
 |     GURL client_redirect_http_url(embedded_test_server()->GetURL( | 
 |         "/client-redirect?" + client_redirect_https_url.spec())); | 
 |  | 
 |     // We should wait until second client redirect get cancelled. | 
 |     LoadStopObserver load_observer2(shell()->web_contents()); | 
 |  | 
 |     EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test", | 
 |                                     client_redirect_http_url)); | 
 |  | 
 |     // DidFailProvisionalLoad when navigating to client_redirect_https_url. | 
 |     load_observer2.Wait(); | 
 |     EXPECT_EQ(observer.last_navigation_url(), client_redirect_https_url); | 
 |     EXPECT_FALSE(observer.last_navigation_succeeded()); | 
 |   } | 
 |  | 
 |   { | 
 |     // Load server-redirect page pointing to a cross-site server-redirect page, | 
 |     // which eventually redirect back to same-site page. | 
 |     GURL server_redirect_https_url( | 
 |         https_server.GetURL("/server-redirect?" + http_url.spec())); | 
 |     GURL server_redirect_http_url(embedded_test_server()->GetURL( | 
 |         "/server-redirect?" + server_redirect_https_url.spec())); | 
 |     EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test", | 
 |                                     server_redirect_http_url)); | 
 |     EXPECT_EQ(observer.last_navigation_url(), http_url); | 
 |     EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |   } | 
 |  | 
 |   { | 
 |     // Load server-redirect page pointing to a cross-site server-redirect page, | 
 |     // which eventually redirects back to cross-site page. | 
 |     GURL server_redirect_https_url( | 
 |         https_server.GetURL("/server-redirect?" + https_url.spec())); | 
 |     GURL server_redirect_http_url(embedded_test_server()->GetURL( | 
 |         "/server-redirect?" + server_redirect_https_url.spec())); | 
 |     EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test", | 
 |                                     server_redirect_http_url)); | 
 |  | 
 |     // DidFailProvisionalLoad when navigating to https_url. | 
 |     EXPECT_EQ(observer.last_navigation_url(), https_url); | 
 |     EXPECT_FALSE(observer.last_navigation_succeeded()); | 
 |   } | 
 |  | 
 |   { | 
 |     // Load server-redirect page pointing to a cross-site client-redirect page, | 
 |     // which eventually redirects back to same-site page. | 
 |     GURL client_redirect_http_url( | 
 |         https_server.GetURL("/client-redirect?" + http_url.spec())); | 
 |     GURL server_redirect_http_url(embedded_test_server()->GetURL( | 
 |         "/server-redirect?" + client_redirect_http_url.spec())); | 
 |     EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "test", | 
 |                                     server_redirect_http_url)); | 
 |  | 
 |     // DidFailProvisionalLoad when navigating to client_redirect_http_url. | 
 |     EXPECT_EQ(observer.last_navigation_url(), client_redirect_http_url); | 
 |     EXPECT_FALSE(observer.last_navigation_succeeded()); | 
 |   } | 
 | } | 
 |  | 
 | // Ensure that when navigating a frame cross-process RenderFrameProxyHosts are | 
 | // created in the FrameTree skipping the subtree of the navigating frame (but | 
 | // not the navigating frame itself). | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, ProxyCreationSkipsSubtree) { | 
 |   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()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   EXPECT_TRUE(root->child_at(1) != nullptr); | 
 |   EXPECT_EQ(2U, root->child_at(1)->child_count()); | 
 |  | 
 |   { | 
 |     // Load same-site page into iframe. | 
 |     TestNavigationObserver observer(shell()->web_contents()); | 
 |     GURL http_url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |     EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(0), http_url)); | 
 |     EXPECT_EQ(http_url, observer.last_navigation_url()); | 
 |     EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |     EXPECT_EQ( | 
 |         " Site A\n" | 
 |         "   |--Site A\n" | 
 |         "   +--Site A\n" | 
 |         "        |--Site A\n" | 
 |         "        +--Site A\n" | 
 |         "             +--Site A\n" | 
 |         "Where A = http://a.com/", | 
 |         DepictFrameTree(root)); | 
 |   } | 
 |  | 
 |   // Create the cross-site URL to navigate to. | 
 |   GURL cross_site_url = | 
 |       embedded_test_server()->GetURL("foo.com", "/frame_tree/title2.html"); | 
 |  | 
 |   // Load cross-site page into the second iframe without waiting for the | 
 |   // navigation to complete. Once LoadURLWithParams returns, we would expect | 
 |   // proxies to have been created in the frame tree, but children of the | 
 |   // navigating frame to still be present. The reason is that we don't run the | 
 |   // message loop, so no IPCs that alter the frame tree can be processed. | 
 |   FrameTreeNode* child = root->child_at(1); | 
 |   SiteInstance* site = nullptr; | 
 |   std::string cross_site_rfh_type = "speculative"; | 
 |   { | 
 |     TestNavigationObserver observer(shell()->web_contents()); | 
 |     TestNavigationManager navigation_manager(shell()->web_contents(), | 
 |                                              cross_site_url); | 
 |     NavigationController::LoadURLParams params(cross_site_url); | 
 |     params.transition_type = PageTransitionFromInt(ui::PAGE_TRANSITION_LINK); | 
 |     params.frame_tree_node_id = child->frame_tree_node_id(); | 
 |     child->navigator().controller().LoadURLWithParams(params); | 
 |     navigation_manager.WaitForSpeculativeRenderFrameHostCreation(); | 
 |  | 
 |     site = child->render_manager()->speculative_frame_host()->GetSiteInstance(); | 
 |     EXPECT_NE(shell()->web_contents()->GetSiteInstance(), site); | 
 |  | 
 |     std::string tree = base::StringPrintf( | 
 |         " Site A ------------ proxies for B\n" | 
 |         "   |--Site A ------- proxies for B\n" | 
 |         "   +--Site A (B %s) -- proxies for B\n" | 
 |         "        |--Site A\n" | 
 |         "        +--Site A\n" | 
 |         "             +--Site A\n" | 
 |         "Where A = http://a.com/\n" | 
 |         "      B = http://foo.com/", | 
 |         cross_site_rfh_type.c_str()); | 
 |     EXPECT_EQ(tree, DepictFrameTree(root)); | 
 |  | 
 |     // Now that the verification is done, run the message loop and wait for the | 
 |     // navigation to complete. | 
 |     ASSERT_TRUE(navigation_manager.WaitForNavigationFinished()); | 
 |     EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |     EXPECT_EQ(cross_site_url, observer.last_navigation_url()); | 
 |  | 
 |     EXPECT_EQ( | 
 |         " Site A ------------ proxies for B\n" | 
 |         "   |--Site A ------- proxies for B\n" | 
 |         "   +--Site B ------- proxies for A\n" | 
 |         "Where A = http://a.com/\n" | 
 |         "      B = http://foo.com/", | 
 |         DepictFrameTree(root)); | 
 |   } | 
 |  | 
 |   // Load another cross-site page into the same iframe. | 
 |   cross_site_url = embedded_test_server()->GetURL("bar.com", "/title3.html"); | 
 |   { | 
 |     // Perform the same checks as the first cross-site navigation, since | 
 |     // there have been issues in subsequent cross-site navigations. Also ensure | 
 |     // that the SiteInstance has properly changed. | 
 |     // TODO(nasko): Once we have proper cleanup of resources, add code to | 
 |     // verify that the intermediate SiteInstance/RenderFrameHost have been | 
 |     // properly cleaned up. | 
 |     TestNavigationObserver observer(shell()->web_contents()); | 
 |     TestNavigationManager navigation_manager(shell()->web_contents(), | 
 |                                              cross_site_url); | 
 |     NavigationController::LoadURLParams params(cross_site_url); | 
 |     params.transition_type = PageTransitionFromInt(ui::PAGE_TRANSITION_LINK); | 
 |     params.frame_tree_node_id = child->frame_tree_node_id(); | 
 |     child->navigator().controller().LoadURLWithParams(params); | 
 |     navigation_manager.WaitForSpeculativeRenderFrameHostCreation(); | 
 |  | 
 |     SiteInstance* site2 = | 
 |         child->render_manager()->speculative_frame_host()->GetSiteInstance(); | 
 |     EXPECT_NE(shell()->web_contents()->GetSiteInstance(), site2); | 
 |     EXPECT_NE(site, site2); | 
 |  | 
 |     std::string tree = base::StringPrintf( | 
 |         " Site A ------------ proxies for B C\n" | 
 |         "   |--Site A ------- proxies for B C\n" | 
 |         "   +--Site B (C %s) -- proxies for A C\n" | 
 |         "Where A = http://a.com/\n" | 
 |         "      B = http://foo.com/\n" | 
 |         "      C = http://bar.com/", | 
 |         cross_site_rfh_type.c_str()); | 
 |     EXPECT_EQ(tree, DepictFrameTree(root)); | 
 |  | 
 |     ASSERT_TRUE(navigation_manager.WaitForNavigationFinished()); | 
 |     EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |     EXPECT_EQ(cross_site_url, observer.last_navigation_url()); | 
 |     EXPECT_EQ(0U, child->child_count()); | 
 |   } | 
 | } | 
 |  | 
 | // Verify origin replication with an A-embed-B-embed-C-embed-A hierarchy. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, OriginReplication) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b(c(a),b), a)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   // It is safe to obtain the root frame tree node here, as it doesn't change. | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   EXPECT_EQ( | 
 |       " Site A ------------ proxies for B C\n" | 
 |       "   |--Site B ------- proxies for A C\n"       // tiptop_child | 
 |       "   |    |--Site C -- proxies for A B\n"       // middle_child | 
 |       "   |    |    +--Site A -- proxies for B C\n"  // lowest_child | 
 |       "   |    +--Site B -- proxies for A C\n" | 
 |       "   +--Site A ------- proxies for B C\n" | 
 |       "Where A = http://a.com/\n" | 
 |       "      B = http://b.com/\n" | 
 |       "      C = http://c.com/", | 
 |       DepictFrameTree(root)); | 
 |  | 
 |   url::Origin a_origin = | 
 |       url::Origin::Create(embedded_test_server()->GetURL("a.com", "/")); | 
 |   url::Origin b_origin = | 
 |       url::Origin::Create(embedded_test_server()->GetURL("b.com", "/")); | 
 |   url::Origin c_origin = | 
 |       url::Origin::Create(embedded_test_server()->GetURL("c.com", "/")); | 
 |   FrameTreeNode* tiptop_child = root->child_at(0); | 
 |   FrameTreeNode* middle_child = root->child_at(0)->child_at(0); | 
 |   FrameTreeNode* lowest_child = root->child_at(0)->child_at(0)->child_at(0); | 
 |  | 
 |   // Check that b.com frame's location.ancestorOrigins contains the correct | 
 |   // origin for the parent.  The origin should have been replicated as part of | 
 |   // the mojom::Renderer::CreateView message that created the parent's | 
 |   // `blink::RemoteFrame` in b.com's process. | 
 |   EXPECT_EQ(ListValueOf(a_origin), | 
 |             EvalJs(tiptop_child, "Array.from(location.ancestorOrigins);")); | 
 |  | 
 |   // Check that c.com frame's location.ancestorOrigins contains the correct | 
 |   // origin for its two ancestors. The topmost parent origin should be | 
 |   // replicated as part of mojom::Renderer::CreateView, and the middle frame | 
 |   // (b.com's) origin should be replicated as part of | 
 |   // blink::mojom::RemoteFrame::CreateRemoteChild sent for b.com's frame in | 
 |   // c.com's process. | 
 |   EXPECT_EQ(ListValueOf(b_origin, a_origin), | 
 |             EvalJs(middle_child, "Array.from(location.ancestorOrigins);")); | 
 |  | 
 |   // Check that the nested a.com frame's location.ancestorOrigins contains the | 
 |   // correct origin for its three ancestors. | 
 |   EXPECT_EQ(ListValueOf(c_origin, b_origin, a_origin), | 
 |             EvalJs(lowest_child, "Array.from(location.ancestorOrigins);")); | 
 | } | 
 |  | 
 | // Test that HasReceivedUserGesture and HasReceivedUserGestureBeforeNavigation | 
 | // are propagated correctly across origins. | 
 | // TODO(crbug.com/40653035): This test is flaky. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessAutoplayBrowserTest, | 
 |                        DISABLED_PropagateUserGestureFlag) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "example.com", "/media/autoplay/autoplay-enabled.html")); | 
 |   GURL foo_url(embedded_test_server()->GetURL( | 
 |       "foo.com", "/media/autoplay/autoplay-enabled.html")); | 
 |   GURL bar_url(embedded_test_server()->GetURL( | 
 |       "bar.com", "/media/autoplay/autoplay-enabled.html")); | 
 |   GURL secondary_url(embedded_test_server()->GetURL( | 
 |       "test.example.com", "/media/autoplay/autoplay-enabled.html")); | 
 |   GURL disabled_url(embedded_test_server()->GetURL( | 
 |       "test.example.com", "/media/autoplay/autoplay-disabled.html")); | 
 |  | 
 |   // Load a page with an iframe that has autoplay. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // Navigate the subframes to cross-origin pages. | 
 |   EXPECT_TRUE(NavigateFrameToURL(root->child_at(0), foo_url)); | 
 |   EXPECT_TRUE(NavigateFrameToURL(root->child_at(0)->child_at(0), bar_url)); | 
 |  | 
 |   // Test that all frames can autoplay if there has been a gesture in the top | 
 |   // frame. | 
 |   EXPECT_TRUE(AutoplayAllowed(shell(), true)); | 
 |   EXPECT_TRUE(AutoplayAllowed(root->child_at(0), false)); | 
 |   EXPECT_TRUE(AutoplayAllowed(root->child_at(0)->child_at(0), false)); | 
 |  | 
 |   // Navigate to a new page on the same origin. | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(shell(), secondary_url)); | 
 |   root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // Navigate the subframes to cross-origin pages. | 
 |   EXPECT_TRUE(NavigateFrameToURL(root->child_at(0), foo_url)); | 
 |   EXPECT_TRUE(NavigateFrameToURL(root->child_at(0)->child_at(0), bar_url)); | 
 |  | 
 |   // Test that all frames can autoplay because the gesture bit has been passed | 
 |   // through the navigation. | 
 |   EXPECT_TRUE(AutoplayAllowed(shell(), false)); | 
 |   EXPECT_TRUE(AutoplayAllowed(root->child_at(0), false)); | 
 |   EXPECT_TRUE(AutoplayAllowed(root->child_at(0)->child_at(0), false)); | 
 |  | 
 |   // Navigate to a page with autoplay disabled. | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(shell(), disabled_url)); | 
 |   EXPECT_TRUE(NavigateFrameToURL(root->child_at(0), foo_url)); | 
 |  | 
 |   // Test that autoplay is no longer allowed. | 
 |   EXPECT_TRUE(AutoplayAllowed(shell(), false)); | 
 |   EXPECT_FALSE(AutoplayAllowed(root->child_at(0), false)); | 
 |  | 
 |   // Navigate to another origin and make sure autoplay is disabled. | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(shell(), foo_url)); | 
 |   EXPECT_TRUE(NavigateFrameToURL(root->child_at(0), bar_url)); | 
 |   EXPECT_FALSE(AutoplayAllowed(shell(), false)); | 
 |   EXPECT_FALSE(AutoplayAllowed(shell(), false)); | 
 | } | 
 |  | 
 | // Check that iframe sandbox flags are replicated correctly. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, SandboxFlagsReplication) { | 
 |   GURL main_url(embedded_test_server()->GetURL("/sandboxed_frames.html")); | 
 |   const url::Origin main_origin = url::Origin::Create(main_url); | 
 |   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()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   TestNavigationObserver observer(shell()->web_contents()); | 
 |  | 
 |   // Navigate the second (sandboxed) subframe to a cross-site page with a | 
 |   // subframe. | 
 |   GURL foo_url( | 
 |       embedded_test_server()->GetURL("foo.com", "/frame_tree/1-1.html")); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(1), foo_url)); | 
 |   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); | 
 |  | 
 |   // We can't use a TestNavigationObserver to verify the URL here, | 
 |   // since the frame has children that may have clobbered it in the observer. | 
 |   EXPECT_EQ(foo_url, root->child_at(1)->current_url()); | 
 |  | 
 |   // Load cross-site page into subframe's subframe. | 
 |   ASSERT_EQ(2U, root->child_at(1)->child_count()); | 
 |   GURL bar_url(embedded_test_server()->GetURL("bar.com", "/title1.html")); | 
 |   EXPECT_TRUE( | 
 |       NavigateToURLFromRenderer(root->child_at(1)->child_at(0), bar_url)); | 
 |   EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |   EXPECT_EQ(bar_url, observer.last_navigation_url()); | 
 |  | 
 |   // Opening a popup in the sandboxed foo.com iframe should fail. | 
 |   EXPECT_EQ(false, EvalJs(root->child_at(1), | 
 |                           "!!window.open('data:text/html,dataurl');")); | 
 |   EXPECT_EQ(1u, Shell::windows().size()); | 
 |  | 
 |   // Opening a popup in a frame whose parent is sandboxed should also fail. | 
 |   // Here, bar.com frame's sandboxed parent frame is a remote frame in | 
 |   // bar.com's process. | 
 |   EXPECT_EQ(false, EvalJs(root->child_at(1)->child_at(0), | 
 |                           "!!window.open('data:text/html,dataurl');")); | 
 |   EXPECT_EQ(1u, Shell::windows().size()); | 
 |  | 
 |   // Same, but now try the case where bar.com frame's sandboxed parent is a | 
 |   // local frame in bar.com's process. | 
 |   EXPECT_EQ(false, EvalJs(root->child_at(2)->child_at(0), | 
 |                           "!!window.open('data:text/html,dataurl');")); | 
 |   EXPECT_EQ(1u, Shell::windows().size()); | 
 |  | 
 |   // Check that foo.com frame's location.ancestorOrigins contains the correct | 
 |   // origin for the parent, which should be unaffected by sandboxing. | 
 |   EXPECT_EQ(ListValueOf(main_origin), | 
 |             EvalJs(root->child_at(1), "Array.from(location.ancestorOrigins);")); | 
 |  | 
 |   // Now check location.ancestorOrigins for the bar.com frame. The middle frame | 
 |   // (foo.com's) origin should be unique, since that frame is sandboxed, and | 
 |   // the top frame should match |main_url|. | 
 |   EXPECT_EQ(ListValueOf("null", main_origin), | 
 |             EvalJs(root->child_at(1)->child_at(0), | 
 |                    "Array.from(location.ancestorOrigins);")); | 
 | } | 
 |  | 
 | // Check that dynamic updates to iframe sandbox flags are propagated correctly. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, DynamicSandboxFlags) { | 
 |   bool sandboxed_iframes_are_isolated = | 
 |       SiteIsolationPolicy::AreIsolatedSandboxedIframesEnabled(); | 
 |   GURL main_url( | 
 |       embedded_test_server()->GetURL("/frame_tree/page_with_two_frames.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   // It is safe to obtain the root frame tree node here, as it doesn't change. | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   TestNavigationObserver observer(shell()->web_contents()); | 
 |   ASSERT_EQ(2U, root->child_count()); | 
 |  | 
 |   // Make sure first frame starts out at the correct cross-site page. | 
 |   EXPECT_EQ(embedded_test_server()->GetURL("bar.com", "/title1.html"), | 
 |             root->child_at(0)->current_url()); | 
 |  | 
 |   // Navigate second frame to another cross-site page. | 
 |   GURL baz_url(embedded_test_server()->GetURL("baz.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(1), baz_url)); | 
 |   EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |   EXPECT_EQ(baz_url, observer.last_navigation_url()); | 
 |  | 
 |   // Both frames should not be sandboxed to start with. | 
 |   EXPECT_EQ(network::mojom::WebSandboxFlags::kNone, | 
 |             root->child_at(0)->pending_frame_policy().sandbox_flags); | 
 |   EXPECT_EQ(network::mojom::WebSandboxFlags::kNone, | 
 |             root->child_at(0)->effective_frame_policy().sandbox_flags); | 
 |   EXPECT_EQ(network::mojom::WebSandboxFlags::kNone, | 
 |             root->child_at(1)->pending_frame_policy().sandbox_flags); | 
 |   EXPECT_EQ(network::mojom::WebSandboxFlags::kNone, | 
 |             root->child_at(1)->effective_frame_policy().sandbox_flags); | 
 |  | 
 |   // Dynamically update sandbox flags for the first frame. | 
 |   EXPECT_TRUE(ExecJs( | 
 |       shell(), "document.querySelector('iframe').sandbox='allow-scripts';")); | 
 |  | 
 |   // Check that updated sandbox flags are propagated to browser process. | 
 |   // The new flags should be reflected in pending_frame_policy().sandbox_flags, | 
 |   // while effective_frame_policy().sandbox_flags should still reflect the old | 
 |   // flags, because sandbox flag updates take place only after navigations. | 
 |   // "allow-scripts" resets both SandboxFlags::Scripts and | 
 |   // SandboxFlags::AutomaticFeatures bits per blink::parseSandboxPolicy(). | 
 |   network::mojom::WebSandboxFlags expected_flags = | 
 |       network::mojom::WebSandboxFlags::kAll & | 
 |       ~network::mojom::WebSandboxFlags::kScripts & | 
 |       ~network::mojom::WebSandboxFlags::kAutomaticFeatures; | 
 |   EXPECT_EQ(expected_flags, | 
 |             root->child_at(0)->pending_frame_policy().sandbox_flags); | 
 |   EXPECT_EQ(network::mojom::WebSandboxFlags::kNone, | 
 |             root->child_at(0)->effective_frame_policy().sandbox_flags); | 
 |  | 
 |   // Navigate the first frame to a page on the same site.  The new sandbox | 
 |   // flags should take effect. | 
 |   GURL bar_url( | 
 |       embedded_test_server()->GetURL("bar.com", "/frame_tree/2-4.html")); | 
 |   { | 
 |     RenderFrameDeletedObserver deleted_observer( | 
 |         root->child_at(0)->current_frame_host()); | 
 |     EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(0), bar_url)); | 
 |     if (sandboxed_iframes_are_isolated) { | 
 |       deleted_observer.WaitUntilDeleted(); | 
 |     } | 
 |   } | 
 |   // (The new page has a subframe; wait for it to load as well.) | 
 |   ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); | 
 |   EXPECT_EQ(bar_url, root->child_at(0)->current_url()); | 
 |   ASSERT_EQ(1U, root->child_at(0)->child_count()); | 
 |  | 
 |   EXPECT_EQ( | 
 |       base::StringPrintf(" Site A ------------ proxies for B C\n" | 
 |                          "   |--Site B ------- proxies for A C\n" | 
 |                          "   |    +--Site B -- proxies for A C\n" | 
 |                          "   +--Site C ------- proxies for A B\n" | 
 |                          "Where A = http://127.0.0.1/\n" | 
 |                          "      B = http://bar.com/%s\n" | 
 |                          "      C = http://baz.com/", | 
 |                          sandboxed_iframes_are_isolated ? " (sandboxed)" : ""), | 
 |       DepictFrameTree(root)); | 
 |  | 
 |   // Confirm that the browser process has updated the frame's current sandbox | 
 |   // flags. | 
 |   EXPECT_EQ(expected_flags, | 
 |             root->child_at(0)->pending_frame_policy().sandbox_flags); | 
 |   EXPECT_EQ(expected_flags, | 
 |             root->child_at(0)->effective_frame_policy().sandbox_flags); | 
 |  | 
 |   // Opening a popup in the now-sandboxed frame should fail. | 
 |   EXPECT_EQ(false, EvalJs(root->child_at(0), | 
 |                           "!!window.open('data:text/html,dataurl');")); | 
 |   EXPECT_EQ(1u, Shell::windows().size()); | 
 |  | 
 |   // Navigate the child of the now-sandboxed frame to a page on baz.com.  The | 
 |   // child should inherit the latest sandbox flags from its parent frame, which | 
 |   // is currently a proxy in baz.com's renderer process.  This checks that the | 
 |   // proxies of |root->child_at(0)| were also updated with the latest sandbox | 
 |   // flags. | 
 |   // TODO(crbug.com/40943240): When IsolateSandboxedIframes is enabled, | 
 |   // this test no longer uses the proxy inheritance mentioned above, because | 
 |   // sandboxed and unsandboxed baz.com pages will be in different SiteInstances. | 
 |   // Restructure the test so it still provides coverage for proxy inheritance | 
 |   // when IsolateSandboxedIframes is enabled. | 
 |   GURL baz_child_url(embedded_test_server()->GetURL("baz.com", "/title2.html")); | 
 |   { | 
 |     RenderFrameDeletedObserver deleted_observer( | 
 |         root->child_at(0)->child_at(0)->current_frame_host()); | 
 |     EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(0)->child_at(0), | 
 |                                           baz_child_url)); | 
 |     deleted_observer.WaitUntilDeleted(); | 
 |   } | 
 |   EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |   EXPECT_EQ(baz_child_url, observer.last_navigation_url()); | 
 |  | 
 |   if (sandboxed_iframes_are_isolated) { | 
 |     switch (blink::features::kIsolateSandboxedIframesGroupingParam.Get()) { | 
 |       case blink::features::IsolateSandboxedIframesGrouping::kPerSite: | 
 |       case blink::features::IsolateSandboxedIframesGrouping::kPerOrigin: | 
 |         EXPECT_EQ( | 
 |             " Site A ------------ proxies for B C D\n" | 
 |             "   |--Site B ------- proxies for A C D\n" | 
 |             "   |    +--Site D -- proxies for A B C\n" | 
 |             "   +--Site C ------- proxies for A B D\n" | 
 |             "Where A = http://127.0.0.1/\n" | 
 |             "      B = http://bar.com/ (sandboxed)\n" | 
 |             "      C = http://baz.com/\n" | 
 |             "      D = http://baz.com/ (sandboxed)", | 
 |             DepictFrameTree(root)); | 
 |         break; | 
 |       case blink::features::IsolateSandboxedIframesGrouping::kPerDocument: | 
 |         // TODO(crbug.com/40941714): Add output for the PerDocument | 
 |         // case, and parameterize this test to run all variants (none, per-site, | 
 |         // per-origin, per-document). | 
 |         break; | 
 |     } | 
 |   } else { | 
 |     EXPECT_EQ( | 
 |         " Site A ------------ proxies for B C\n" | 
 |         "   |--Site B ------- proxies for A C\n" | 
 |         "   |    +--Site C -- proxies for A B\n" | 
 |         "   +--Site C ------- proxies for A B\n" | 
 |         "Where A = http://127.0.0.1/\n" | 
 |         "      B = http://bar.com/\n" | 
 |         "      C = http://baz.com/", | 
 |         DepictFrameTree(root)); | 
 |   } | 
 |  | 
 |   // Opening a popup in the child of a sandboxed frame should fail. | 
 |   EXPECT_EQ(false, EvalJs(root->child_at(0)->child_at(0), | 
 |                           "!!window.open('data:text/html,dataurl');")); | 
 |   EXPECT_EQ(1u, Shell::windows().size()); | 
 |  | 
 |   // Child of a sandboxed frame should also be sandboxed on the browser side. | 
 |   EXPECT_EQ( | 
 |       expected_flags, | 
 |       root->child_at(0)->child_at(0)->effective_frame_policy().sandbox_flags); | 
 | } | 
 |  | 
 | // Check that dynamic updates to iframe sandbox flags are propagated correctly. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        DynamicSandboxFlagsRemoteToLocal) { | 
 |   GURL main_url( | 
 |       embedded_test_server()->GetURL("/frame_tree/page_with_two_frames.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   // It is safe to obtain the root frame tree node here, as it doesn't change. | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   TestNavigationObserver observer(shell()->web_contents()); | 
 |   ASSERT_EQ(2U, root->child_count()); | 
 |  | 
 |   // Make sure the two frames starts out at correct URLs. | 
 |   EXPECT_EQ(embedded_test_server()->GetURL("bar.com", "/title1.html"), | 
 |             root->child_at(0)->current_url()); | 
 |   EXPECT_EQ(embedded_test_server()->GetURL("/title1.html"), | 
 |             root->child_at(1)->current_url()); | 
 |  | 
 |   // Update the second frame's sandbox flags. | 
 |   EXPECT_TRUE( | 
 |       ExecJs(shell(), | 
 |              "document.querySelectorAll('iframe')[1].sandbox='allow-scripts'")); | 
 |  | 
 |   // Check that the current sandbox flags are updated but the effective | 
 |   // sandbox flags are not. | 
 |   network::mojom::WebSandboxFlags expected_flags = | 
 |       network::mojom::WebSandboxFlags::kAll & | 
 |       ~network::mojom::WebSandboxFlags::kScripts & | 
 |       ~network::mojom::WebSandboxFlags::kAutomaticFeatures; | 
 |   EXPECT_EQ(expected_flags, | 
 |             root->child_at(1)->pending_frame_policy().sandbox_flags); | 
 |   EXPECT_EQ(network::mojom::WebSandboxFlags::kNone, | 
 |             root->child_at(1)->effective_frame_policy().sandbox_flags); | 
 |  | 
 |   // Navigate the second subframe to a page on bar.com.  This will trigger a | 
 |   // remote-to-local frame swap in bar.com's process. | 
 |   GURL bar_url(embedded_test_server()->GetURL( | 
 |       "bar.com", "/frame_tree/page_with_one_frame.html")); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(1), bar_url)); | 
 |   EXPECT_EQ(bar_url, root->child_at(1)->current_url()); | 
 |   ASSERT_EQ(1U, root->child_at(1)->child_count()); | 
 |  | 
 |   // Confirm that the browser process has updated the current sandbox flags. | 
 |   EXPECT_EQ(expected_flags, | 
 |             root->child_at(1)->pending_frame_policy().sandbox_flags); | 
 |   EXPECT_EQ(expected_flags, | 
 |             root->child_at(1)->effective_frame_policy().sandbox_flags); | 
 |  | 
 |   // Opening a popup in the sandboxed second frame should fail. | 
 |   EXPECT_EQ(false, EvalJs(root->child_at(1), | 
 |                           "!!window.open('data:text/html,dataurl');")); | 
 |   EXPECT_EQ(1u, Shell::windows().size()); | 
 |  | 
 |   // Make sure that the child frame inherits the sandbox flags of its | 
 |   // now-sandboxed parent frame. | 
 |   EXPECT_EQ(false, EvalJs(root->child_at(1)->child_at(0), | 
 |                           "!!window.open('data:text/html,dataurl');")); | 
 |   EXPECT_EQ(1u, Shell::windows().size()); | 
 | } | 
 |  | 
 | // Check that dynamic updates to iframe sandbox flags are propagated correctly. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        DynamicSandboxFlagsRendererInitiatedNavigation) { | 
 |   GURL main_url( | 
 |       embedded_test_server()->GetURL("/frame_tree/page_with_one_frame.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   // It is safe to obtain the root frame tree node here, as it doesn't change. | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   TestNavigationObserver observer(shell()->web_contents()); | 
 |   ASSERT_EQ(1U, root->child_count()); | 
 |  | 
 |   // Make sure the frame starts out at the correct cross-site page. | 
 |   EXPECT_EQ(embedded_test_server()->GetURL("baz.com", "/title1.html"), | 
 |             root->child_at(0)->current_url()); | 
 |  | 
 |   // The frame should not be sandboxed to start with. | 
 |   EXPECT_EQ(network::mojom::WebSandboxFlags::kNone, | 
 |             root->child_at(0)->pending_frame_policy().sandbox_flags); | 
 |   EXPECT_EQ(network::mojom::WebSandboxFlags::kNone, | 
 |             root->child_at(0)->effective_frame_policy().sandbox_flags); | 
 |  | 
 |   // Dynamically update the frame's sandbox flags. | 
 |   EXPECT_TRUE(ExecJs( | 
 |       shell(), "document.querySelector('iframe').sandbox='allow-scripts';")); | 
 |  | 
 |   // Check that updated sandbox flags are propagated to browser process. | 
 |   // The new flags should be set in pending_frame_policy().sandbox_flags, while | 
 |   // effective_frame_policy().sandbox_flags should still reflect the old flags, | 
 |   // because sandbox flag updates take place only after navigations. | 
 |   // "allow-scripts" resets both SandboxFlags::Scripts and | 
 |   // SandboxFlags::AutomaticFeatures bits per blink::parseSandboxPolicy(). | 
 |   network::mojom::WebSandboxFlags expected_flags = | 
 |       network::mojom::WebSandboxFlags::kAll & | 
 |       ~network::mojom::WebSandboxFlags::kScripts & | 
 |       ~network::mojom::WebSandboxFlags::kAutomaticFeatures; | 
 |   EXPECT_EQ(expected_flags, | 
 |             root->child_at(0)->pending_frame_policy().sandbox_flags); | 
 |   EXPECT_EQ(network::mojom::WebSandboxFlags::kNone, | 
 |             root->child_at(0)->effective_frame_policy().sandbox_flags); | 
 |  | 
 |   // Perform a renderer-initiated same-site navigation in the first frame. The | 
 |   // new sandbox flags should take effect. | 
 |   TestFrameNavigationObserver frame_observer(root->child_at(0)); | 
 |   ASSERT_TRUE(ExecJs(root->child_at(0), "window.location.href='/title2.html'")); | 
 |   frame_observer.Wait(); | 
 |   EXPECT_EQ(embedded_test_server()->GetURL("baz.com", "/title2.html"), | 
 |             root->child_at(0)->current_url()); | 
 |  | 
 |   // Confirm that the browser process has updated the frame's current sandbox | 
 |   // flags. | 
 |   EXPECT_EQ(expected_flags, | 
 |             root->child_at(0)->pending_frame_policy().sandbox_flags); | 
 |   EXPECT_EQ(expected_flags, | 
 |             root->child_at(0)->effective_frame_policy().sandbox_flags); | 
 |  | 
 |   // Opening a popup in the now-sandboxed frame should fail. | 
 |   EXPECT_EQ(false, EvalJs(root->child_at(0), | 
 |                           "!!window.open('data:text/html,dataurl');")); | 
 |   EXPECT_EQ(1u, Shell::windows().size()); | 
 | } | 
 |  | 
 | // Verify that when a new child frame is added, the proxies created for it in | 
 | // other SiteInstances have correct sandbox flags and origin. | 
 | // | 
 | //     A         A           A | 
 | //    /         / \         / \    . | 
 | //   B    ->   B   A   ->  B   A | 
 | //                              \  . | 
 | //                               B | 
 | // | 
 | // The test checks sandbox flags and origin for the proxy added in step 2, by | 
 | // checking whether the grandchild frame added in step 3 sees proper sandbox | 
 | // flags and origin for its (remote) parent.  This wasn't addressed when | 
 | // https://crbug.com/423587 was fixed. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        ProxiesForNewChildFramesHaveCorrectReplicationState) { | 
 |   GURL main_url( | 
 |       embedded_test_server()->GetURL("/frame_tree/page_with_one_frame.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   // It is safe to obtain the root frame tree node here, as it doesn't change. | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   EXPECT_EQ( | 
 |       " Site A ------------ proxies for B\n" | 
 |       "   +--Site B ------- proxies for A\n" | 
 |       "Where A = http://127.0.0.1/\n" | 
 |       "      B = http://baz.com/", | 
 |       DepictFrameTree(root)); | 
 |  | 
 |   // In the root frame, add a new sandboxed local frame, which itself has a | 
 |   // child frame on baz.com.  Wait for three RenderFrameHosts to be created: | 
 |   // the new sandboxed local frame, its child (while it's still local), and a | 
 |   // speculative RFH when starting the cross-site navigation to baz.com. | 
 |   RenderFrameHostCreatedObserver frame_observer(shell()->web_contents(), 3); | 
 |   EXPECT_TRUE(ExecJs(root, | 
 |                      "addFrame('/frame_tree/page_with_one_frame.html'," | 
 |                      "         'allow-scripts allow-same-origin')")); | 
 |   frame_observer.Wait(); | 
 |  | 
 |   // Wait for the cross-site navigation to baz.com in the grandchild to finish. | 
 |   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); | 
 |   FrameTreeNode* bottom_child = root->child_at(1)->child_at(0); | 
 |   EXPECT_EQ(embedded_test_server()->GetURL("baz.com", "/title1.html"), | 
 |             bottom_child->current_url()); | 
 |  | 
 |   EXPECT_EQ( | 
 |       " Site A ------------ proxies for B\n" | 
 |       "   |--Site B ------- proxies for A\n" | 
 |       "   +--Site A ------- proxies for B\n" | 
 |       "        +--Site B -- proxies for A\n" | 
 |       "Where A = http://127.0.0.1/\n" | 
 |       "      B = http://baz.com/", | 
 |       DepictFrameTree(root)); | 
 |  | 
 |   // Use location.ancestorOrigins to check that the grandchild on baz.com sees | 
 |   // correct origin for its parent and grandparent, which are at the same URL | 
 |   // and origin (namely, page_with_one_frame.html on the server's default | 
 |   // origin). | 
 |   EXPECT_EQ( | 
 |       ListValueOf(url::Origin::Create(main_url), url::Origin::Create(main_url)), | 
 |       EvalJs(bottom_child, "Array.from(location.ancestorOrigins);")); | 
 |  | 
 |   // Check that the sandbox flags in the browser process are correct. | 
 |   // "allow-scripts" resets both network::mojom::WebSandboxFlags::Scripts and | 
 |   // network::mojom::WebSandboxFlags::AutomaticFeatures bits per | 
 |   // blink::parseSandboxPolicy(). | 
 |   network::mojom::WebSandboxFlags expected_flags = | 
 |       network::mojom::WebSandboxFlags::kAll & | 
 |       ~network::mojom::WebSandboxFlags::kScripts & | 
 |       ~network::mojom::WebSandboxFlags::kAutomaticFeatures & | 
 |       ~network::mojom::WebSandboxFlags::kOrigin; | 
 |   EXPECT_EQ(expected_flags, | 
 |             root->child_at(1)->effective_frame_policy().sandbox_flags); | 
 |  | 
 |   // The child of the sandboxed frame should've inherited sandbox flags, so it | 
 |   // should not be able to create popups. | 
 |   EXPECT_EQ(expected_flags, | 
 |             bottom_child->effective_frame_policy().sandbox_flags); | 
 |   EXPECT_EQ(false, | 
 |             EvalJs(bottom_child, "!!window.open('data:text/html,dataurl')")); | 
 |   EXPECT_EQ(1u, Shell::windows().size()); | 
 | } | 
 |  | 
 | // Verify that a child frame can retrieve the name property set by its parent. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, WindowNameReplication) { | 
 |   GURL main_url(embedded_test_server()->GetURL("/frame_tree/2-4.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()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   TestNavigationObserver observer(shell()->web_contents()); | 
 |  | 
 |   // Load cross-site page into iframe. | 
 |   GURL frame_url = | 
 |       embedded_test_server()->GetURL("foo.com", "/frame_tree/3-1.html"); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(0), frame_url)); | 
 |   EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |   EXPECT_EQ(frame_url, observer.last_navigation_url()); | 
 |  | 
 |   // Ensure that a new process is created for the subframe. | 
 |   EXPECT_NE(shell()->web_contents()->GetSiteInstance(), | 
 |             root->child_at(0)->current_frame_host()->GetSiteInstance()); | 
 |  | 
 |   // Check that the window.name seen by the frame matches the name attribute | 
 |   // specified by its parent in the iframe tag. | 
 |   EXPECT_EQ("3-1-name", EvalJs(root->child_at(0), "window.name;")); | 
 | } | 
 |  | 
 | // Verify that dynamic updates to a frame's window.name propagate to the | 
 | // frame's proxies, so that the latest frame names can be used in navigations. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, DynamicWindowName) { | 
 |   GURL main_url(embedded_test_server()->GetURL("/frame_tree/2-4.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()->GetPrimaryFrameTree().root(); | 
 |   TestNavigationObserver observer(shell()->web_contents()); | 
 |  | 
 |   // Load cross-site page into iframe. | 
 |   GURL frame_url = | 
 |       embedded_test_server()->GetURL("foo.com", "/frame_tree/3-1.html"); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(0), frame_url)); | 
 |   EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |   EXPECT_EQ(frame_url, observer.last_navigation_url()); | 
 |  | 
 |   // Browser process should know the child frame's original window.name | 
 |   // specified in the iframe element. | 
 |   EXPECT_EQ(root->child_at(0)->frame_name(), "3-1-name"); | 
 |  | 
 |   // Update the child frame's window.name. | 
 |   EXPECT_TRUE(ExecJs(root->child_at(0), "window.name = 'updated-name';")); | 
 |  | 
 |   // The change should propagate to the browser process. | 
 |   EXPECT_EQ(root->child_at(0)->frame_name(), "updated-name"); | 
 |  | 
 |   // The proxy in the parent process should also receive the updated name. | 
 |   // Now iframe's name and the content window's name differ, so it shouldn't | 
 |   // be possible to access to the content window with the updated name. | 
 |   EXPECT_EQ(true, EvalJs(shell(), "frames['updated-name'] === undefined;")); | 
 |   // Change iframe's name to match the content window's name so that it can | 
 |   // reference the child frame by its new name in case of cross origin. | 
 |   EXPECT_TRUE(ExecJs(root, "window['3-1-id'].name = 'updated-name';")); | 
 |   EXPECT_EQ(true, EvalJs(shell(), "frames['updated-name'] == frames[0];")); | 
 |  | 
 |   // Issue a renderer-initiated navigation from the root frame to the child | 
 |   // frame using the frame's name. Make sure correct frame is navigated. | 
 |   // | 
 |   // TODO(alexmos): When blink::createWindow is refactored to handle | 
 |   // RemoteFrames, this should also be tested via window.open(url, frame_name) | 
 |   // and a more complicated frame hierarchy (https://crbug.com/463742) | 
 |   TestFrameNavigationObserver frame_observer(root->child_at(0)); | 
 |   GURL foo_url(embedded_test_server()->GetURL("foo.com", "/title1.html")); | 
 |   EXPECT_TRUE( | 
 |       ExecJs(shell(), | 
 |              JsReplace("frames['updated-name'].location.href = $1", foo_url))); | 
 |   frame_observer.Wait(); | 
 |   EXPECT_EQ(foo_url, root->child_at(0)->current_url()); | 
 | } | 
 |  | 
 | // Verify that when a frame is navigated to a new origin, the origin update | 
 | // propagates to the frame's proxies. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, OriginUpdatesReachProxies) { | 
 |   GURL main_url( | 
 |       embedded_test_server()->GetURL("/frame_tree/page_with_two_frames.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   // It is safe to obtain the root frame tree node here, as it doesn't change. | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   TestNavigationObserver observer(shell()->web_contents()); | 
 |  | 
 |   EXPECT_EQ( | 
 |       " Site A ------------ proxies for B\n" | 
 |       "   |--Site B ------- proxies for A\n" | 
 |       "   +--Site A ------- proxies for B\n" | 
 |       "Where A = http://127.0.0.1/\n" | 
 |       "      B = http://bar.com/", | 
 |       DepictFrameTree(root)); | 
 |  | 
 |   // Navigate second subframe to a baz.com.  This should send an origin update | 
 |   // to the frame's proxy in the bar.com (first frame's) process. | 
 |   GURL frame_url = embedded_test_server()->GetURL("baz.com", "/title2.html"); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(1), frame_url)); | 
 |   EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |   EXPECT_EQ(frame_url, observer.last_navigation_url()); | 
 |  | 
 |   // The first frame can't directly observe the second frame's origin with | 
 |   // JavaScript.  Instead, try to navigate the second frame from the first | 
 |   // frame.  This should fail with a console error message, which should | 
 |   // contain the second frame's updated origin (see blink::Frame::canNavigate). | 
 |   WebContentsConsoleObserver console_observer(shell()->web_contents()); | 
 |   console_observer.SetPattern("Unsafe attempt to initiate navigation*"); | 
 |  | 
 |   // frames[1] can't be used due to a bug where RemoteFrames are created out of | 
 |   // order (https://crbug.com/478792).  Instead, target second frame by name. | 
 |   EXPECT_TRUE(ExecJs(root->child_at(0), | 
 |                      "try { parent.frames['frame2'].location.href = " | 
 |                      "'data:text/html,foo'; } catch (e) {}")); | 
 |   ASSERT_TRUE(console_observer.Wait()); | 
 |  | 
 |   std::string frame_origin = root->child_at(1)->current_origin().Serialize(); | 
 |   EXPECT_EQ(frame_origin + "/", frame_url.DeprecatedGetOriginAsURL().spec()); | 
 |   EXPECT_TRUE(base::MatchPattern(console_observer.GetMessageAt(0u), | 
 |                                  "*" + frame_origin + "*")) | 
 |       << "Error message does not contain the frame's latest origin (" | 
 |       << frame_origin << ")"; | 
 | } | 
 |  | 
 | // Ensure that navigating subframes in --site-per-process mode properly fires | 
 | // the DidStopLoading event on WebContentsObserver. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, CrossSiteDidStopLoading) { | 
 |   GURL main_url(embedded_test_server()->GetURL("/site_per_process_main.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()->GetPrimaryFrameTree().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("/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(child, http_url)); | 
 |   EXPECT_EQ(http_url, observer.last_navigation_url()); | 
 |   EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |  | 
 |   // Load cross-site page into iframe. | 
 |   TestNavigationObserver nav_observer(shell()->web_contents(), 1); | 
 |   GURL url = embedded_test_server()->GetURL("foo.com", "/title2.html"); | 
 |   NavigationController::LoadURLParams params(url); | 
 |   params.transition_type = ui::PAGE_TRANSITION_LINK; | 
 |   params.frame_tree_node_id = child->frame_tree_node_id(); | 
 |   child->navigator().controller().LoadURLWithParams(params); | 
 |   nav_observer.Wait(); | 
 |  | 
 |   // 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 the renderer does not crash when navigating a frame that has a | 
 | // sibling RemoteFrame.  See https://crbug.com/426953. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        NavigateWithSiblingRemoteFrame) { | 
 |   GURL main_url( | 
 |       embedded_test_server()->GetURL("/frame_tree/page_with_two_frames.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   // It is safe to obtain the root frame tree node here, as it doesn't change. | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   TestNavigationObserver observer(shell()->web_contents()); | 
 |  | 
 |   // Make sure the first frame is out of process. | 
 |   ASSERT_EQ(2U, root->child_count()); | 
 |   FrameTreeNode* node2 = root->child_at(0); | 
 |   EXPECT_NE(root->current_frame_host()->GetSiteInstance(), | 
 |             node2->current_frame_host()->GetSiteInstance()); | 
 |  | 
 |   // Make sure the second frame is in the parent's process. | 
 |   FrameTreeNode* node3 = root->child_at(1); | 
 |   EXPECT_EQ(root->current_frame_host()->GetSiteInstance(), | 
 |             node3->current_frame_host()->GetSiteInstance()); | 
 |  | 
 |   // Navigate the second iframe (node3) to a URL in its own process. | 
 |   GURL title_url = embedded_test_server()->GetURL("/title2.html"); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(node3, title_url)); | 
 |   EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |   EXPECT_EQ(title_url, observer.last_navigation_url()); | 
 |   EXPECT_EQ(root->current_frame_host()->GetSiteInstance(), | 
 |             node3->current_frame_host()->GetSiteInstance()); | 
 |   EXPECT_TRUE(node3->current_frame_host()->IsRenderFrameLive()); | 
 | } | 
 |  | 
 | // Ensure that the renderer does not crash when a local frame with a remote | 
 | // parent frame is swapped from local to remote, then back to local again. | 
 | // See https://crbug.com/585654. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        NavigateSiblingsToSameProcess) { | 
 |   GURL main_url( | 
 |       embedded_test_server()->GetURL("/frame_tree/page_with_two_frames.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   // It is safe to obtain the root frame tree node here, as it doesn't change. | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   FrameTreeNode* node2 = root->child_at(0); | 
 |   FrameTreeNode* node3 = root->child_at(1); | 
 |  | 
 |   // Navigate the second iframe to the same process as the first. | 
 |   GURL frame_url = embedded_test_server()->GetURL("bar.com", "/title1.html"); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(node3, frame_url)); | 
 |  | 
 |   // Verify that they are in the same process. | 
 |   EXPECT_EQ(node2->current_frame_host()->GetSiteInstance(), | 
 |             node3->current_frame_host()->GetSiteInstance()); | 
 |   EXPECT_NE(root->current_frame_host()->GetSiteInstance(), | 
 |             node3->current_frame_host()->GetSiteInstance()); | 
 |  | 
 |   // Navigate the first iframe into its parent's process. | 
 |   GURL title_url = embedded_test_server()->GetURL("/title2.html"); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(node2, title_url)); | 
 |   EXPECT_NE(node2->current_frame_host()->GetSiteInstance(), | 
 |             node3->current_frame_host()->GetSiteInstance()); | 
 |  | 
 |   // Return the first iframe to the same process as its sibling, and ensure | 
 |   // that it does not crash. | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(node2, frame_url)); | 
 |   EXPECT_EQ(node2->current_frame_host()->GetSiteInstance(), | 
 |             node3->current_frame_host()->GetSiteInstance()); | 
 |   EXPECT_TRUE(node2->current_frame_host()->IsRenderFrameLive()); | 
 | } | 
 |  | 
 | // Verify that load events for iframe elements work when the child frame is | 
 | // out-of-process.  In such cases, the load event is forwarded from the child | 
 | // frame to the parent frame via the browser process. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, LoadEventForwarding) { | 
 |   // Load a page with a cross-site frame.  The parent page has an onload | 
 |   // handler in the iframe element that appends "LOADED" to the document title. | 
 |   { | 
 |     GURL main_url( | 
 |         embedded_test_server()->GetURL("/frame_with_load_event.html")); | 
 |     std::u16string expected_title(u"LOADED"); | 
 |     TitleWatcher title_watcher(shell()->web_contents(), expected_title); | 
 |     EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |     EXPECT_EQ(title_watcher.WaitAndGetTitle(), expected_title); | 
 |   } | 
 |  | 
 |   // It is safe to obtain the root frame tree node here, as it doesn't change. | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // Load another cross-site page into the iframe and check that the load event | 
 |   // is fired. | 
 |   { | 
 |     GURL foo_url(embedded_test_server()->GetURL("foo.com", "/title1.html")); | 
 |     std::u16string expected_title(u"LOADEDLOADED"); | 
 |     TitleWatcher title_watcher(shell()->web_contents(), expected_title); | 
 |     TestNavigationObserver observer(shell()->web_contents()); | 
 |     EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(0), foo_url)); | 
 |     EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |     EXPECT_EQ(foo_url, observer.last_navigation_url()); | 
 |     EXPECT_EQ(title_watcher.WaitAndGetTitle(), expected_title); | 
 |   } | 
 | } | 
 |  | 
 | // Check that postMessage can be routed between cross-site iframes. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, SubframePostMessage) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "/frame_tree/page_with_post_message_frames.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   // It is safe to obtain the root frame tree node here, as it doesn't change. | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   ASSERT_EQ(2U, root->child_count()); | 
 |  | 
 |   // Verify the frames start at correct URLs.  First frame should be | 
 |   // same-site; second frame should be cross-site. | 
 |   GURL same_site_url(embedded_test_server()->GetURL("/post_message.html")); | 
 |   EXPECT_EQ(same_site_url, root->child_at(0)->current_url()); | 
 |   GURL foo_url(embedded_test_server()->GetURL("foo.com", "/post_message.html")); | 
 |   EXPECT_EQ(foo_url, root->child_at(1)->current_url()); | 
 |   EXPECT_NE(root->child_at(0)->current_frame_host()->GetSiteInstance(), | 
 |             root->child_at(1)->current_frame_host()->GetSiteInstance()); | 
 |  | 
 |   // Send a message from first, same-site frame to second, cross-site frame. | 
 |   // Expect the second frame to reply back to the first frame. | 
 |   PostMessageAndWaitForReply(root->child_at(0), | 
 |                              "postToSibling('subframe-msg','subframe2')", | 
 |                              "\"done-subframe1\""); | 
 |  | 
 |   // Send a postMessage from second, cross-site frame to its parent.  Expect | 
 |   // parent to send a reply to the frame. | 
 |   std::u16string expected_title(u"subframe-msg"); | 
 |   TitleWatcher title_watcher(shell()->web_contents(), expected_title); | 
 |   PostMessageAndWaitForReply(root->child_at(1), "postToParent('subframe-msg')", | 
 |                              "\"done-subframe2\""); | 
 |   EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle()); | 
 |  | 
 |   // Verify the total number of received messages for each subframe.  First | 
 |   // frame should have one message (reply from second frame).  Second frame | 
 |   // should have two messages (message from first frame and reply from parent). | 
 |   // Parent should have one message (from second frame). | 
 |   EXPECT_EQ(1, GetReceivedMessages(root->child_at(0))); | 
 |   EXPECT_EQ(2, GetReceivedMessages(root->child_at(1))); | 
 |   EXPECT_EQ(1, GetReceivedMessages(root)); | 
 | } | 
 |  | 
 | // Check that postMessage can be sent from a subframe on a cross-process opener | 
 | // tab, and that its event.source points to a valid proxy. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        PostMessageWithSubframeOnOpenerChain) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/frame_tree/page_with_post_message_frames.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   // It is safe to obtain the root frame tree node here, as it doesn't change. | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   ASSERT_EQ(2U, root->child_count()); | 
 |  | 
 |   // Verify the initial state of the world.  First frame should be same-site; | 
 |   // second frame should be cross-site. | 
 |   EXPECT_EQ( | 
 |       " Site A ------------ proxies for B\n" | 
 |       "   |--Site A ------- proxies for B\n" | 
 |       "   +--Site B ------- proxies for A\n" | 
 |       "Where A = http://a.com/\n" | 
 |       "      B = http://foo.com/", | 
 |       DepictFrameTree(root)); | 
 |  | 
 |   // Open a popup from the first subframe (so that popup's window.opener points | 
 |   // to the subframe) and navigate it to bar.com. | 
 |   ShellAddedObserver new_shell_observer; | 
 |   EXPECT_TRUE(ExecJs(root->child_at(0), "openPopup('about:blank');")); | 
 |   Shell* popup = new_shell_observer.GetShell(); | 
 |   GURL popup_url( | 
 |       embedded_test_server()->GetURL("bar.com", "/post_message.html")); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(popup, popup_url)); | 
 |  | 
 |   // From the popup, open another popup for baz.com.  This will be used to | 
 |   // check that the whole opener chain is processed when creating proxies and | 
 |   // not just an immediate opener. | 
 |   ShellAddedObserver new_shell_observer2; | 
 |   EXPECT_TRUE(ExecJs(popup, "openPopup('about:blank');")); | 
 |   Shell* popup2 = new_shell_observer2.GetShell(); | 
 |   GURL popup2_url( | 
 |       embedded_test_server()->GetURL("baz.com", "/post_message.html")); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(popup2, popup2_url)); | 
 |  | 
 |   // Ensure that we've created proxies for SiteInstances of both popups (C, D) | 
 |   // in the main window's frame tree. | 
 |   EXPECT_EQ( | 
 |       " Site A ------------ proxies for B C D\n" | 
 |       "   |--Site A ------- proxies for B C D\n" | 
 |       "   +--Site B ------- proxies for A C D\n" | 
 |       "Where A = http://a.com/\n" | 
 |       "      B = http://foo.com/\n" | 
 |       "      C = http://bar.com/\n" | 
 |       "      D = http://baz.com/", | 
 |       DepictFrameTree(root)); | 
 |  | 
 |   // Check the first popup's frame tree as well.  Note that it doesn't have a | 
 |   // proxy for foo.com, since foo.com can't reach the popup.  It does have a | 
 |   // proxy for its opener a.com (which can reach it via the window.open | 
 |   // reference) and second popup (which can reach it via window.opener). | 
 |   FrameTreeNode* popup_root = | 
 |       static_cast<WebContentsImpl*>(popup->web_contents()) | 
 |           ->GetPrimaryFrameTree() | 
 |           .root(); | 
 |   EXPECT_EQ( | 
 |       " Site C ------------ proxies for A D\n" | 
 |       "Where A = http://a.com/\n" | 
 |       "      C = http://bar.com/\n" | 
 |       "      D = http://baz.com/", | 
 |       DepictFrameTree(popup_root)); | 
 |  | 
 |   // Send a message from first subframe on main page to the first popup and | 
 |   // wait for a reply back. The reply verifies that the proxy for the opener | 
 |   // tab's subframe is targeted properly. | 
 |   PostMessageAndWaitForReply(root->child_at(0), "postToPopup('subframe-msg')", | 
 |                              "\"done-subframe1\""); | 
 |  | 
 |   // Send a postMessage from the popup to window.opener and ensure that it | 
 |   // reaches subframe1.  This verifies that the subframe opener information | 
 |   // propagated to the popup's RenderFrame.  Wait for subframe1 to send a reply | 
 |   // message to the popup. | 
 |   EXPECT_TRUE(ExecJs(popup, "window.name = 'popup';")); | 
 |   PostMessageAndWaitForReply(popup_root, "postToOpener('subframe-msg', '*')", | 
 |                              "\"done-popup\""); | 
 |  | 
 |   // Second a postMessage from popup2 to window.opener.opener, which should | 
 |   // resolve to subframe1.  This tests opener chains of length greater than 1. | 
 |   // As before, subframe1 will send a reply to popup2. | 
 |   FrameTreeNode* popup2_root = | 
 |       static_cast<WebContentsImpl*>(popup2->web_contents()) | 
 |           ->GetPrimaryFrameTree() | 
 |           .root(); | 
 |   EXPECT_TRUE(ExecJs(popup2, "window.name = 'popup2';")); | 
 |   PostMessageAndWaitForReply(popup2_root, | 
 |                              "postToOpenerOfOpener('subframe-msg', '*')", | 
 |                              "\"done-popup2\""); | 
 |  | 
 |   // Verify the total number of received messages for each subframe: | 
 |   //  - 3 for first subframe (two from first popup, one from second popup) | 
 |   //  - 2 for popup (both from first subframe) | 
 |   //  - 1 for popup2 (reply from first subframe) | 
 |   //  - 0 for other frames | 
 |   EXPECT_EQ(0, GetReceivedMessages(root)); | 
 |   EXPECT_EQ(3, GetReceivedMessages(root->child_at(0))); | 
 |   EXPECT_EQ(0, GetReceivedMessages(root->child_at(1))); | 
 |   EXPECT_EQ(2, GetReceivedMessages(popup_root)); | 
 |   EXPECT_EQ(1, GetReceivedMessages(popup2_root)); | 
 | } | 
 |  | 
 | // Check that in certain situations, postMessage has to create proxies on demand | 
 | // so that event.source may be used to reply. In particular, this test starts on | 
 | // A(B) and opens C(D) from A. At this point, D can reach B (via | 
 | // parent.opener.frames[0], but B cannot see D. However, if D sends a | 
 | // postMessage to B, B now gains a reference to D through event.source, and | 
 | // therefore the message should create a proxy for D in B's process. See | 
 | // https://crbug.com/40261772. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        PostMessageCreatesProxyOnDemand) { | 
 |   // Start on A(B). | 
 |   GURL opener_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), opener_url)); | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // From A, open a popup with a C(D) page. | 
 |   GURL popup_url(embedded_test_server()->GetURL( | 
 |       "c.com", "/cross_site_iframe_factory.html?c(d)")); | 
 |   Shell* new_shell = OpenPopup(root, popup_url, ""); | 
 |   EXPECT_TRUE(new_shell); | 
 |   FrameTreeNode* popup_root = | 
 |       static_cast<WebContentsImpl*>(new_shell->web_contents()) | 
 |           ->GetPrimaryFrameTree() | 
 |           .root(); | 
 |   EXPECT_EQ(root, popup_root->opener()); | 
 |  | 
 |   // Verify proxy setup. Both A and B should be visible to all four frames: | 
 |   // - C can reach A via opener | 
 |   // - C can reach B via opener.frames[0] | 
 |   // - D can reach A via parent.opener | 
 |   // - D can reach B via parent.opener.frames[0] | 
 |   EXPECT_EQ( | 
 |       " Site A ------------ proxies for B C D\n" | 
 |       "   +--Site B ------- proxies for A C D\n" | 
 |       "Where A = http://a.com/\n" | 
 |       "      B = http://b.com/\n" | 
 |       "      C = http://c.com/\n" | 
 |       "      D = http://d.com/", | 
 |       DepictFrameTree(root)); | 
 |   // C and D are only visible to each other and to A, via A's window.open | 
 |   // reference. B doesn't have a way to reach them. | 
 |   EXPECT_EQ( | 
 |       " Site C ------------ proxies for A D\n" | 
 |       "   +--Site D ------- proxies for A C\n" | 
 |       "Where A = http://a.com/\n" | 
 |       "      C = http://c.com/\n" | 
 |       "      D = http://d.com/", | 
 |       DepictFrameTree(popup_root)); | 
 |  | 
 |   // Install a postMessage handler in B that echoes back event.data with | 
 |   // "-reply" appended to it. This requires having a valid event.source. | 
 |   EXPECT_TRUE(ExecJs(root->child_at(0)->current_frame_host(), | 
 |                      "window.addEventListener('message', function(event) {\n" | 
 |                      "  event.source.postMessage(event.data + '-reply', '*');\n" | 
 |                      "});")); | 
 |  | 
 |   // Send a message from D to B and wait for a response. While processing the | 
 |   // message, the browser process should create a proxy for D in B, which B will | 
 |   // use when replying. | 
 |   EXPECT_TRUE( | 
 |       ExecJs(popup_root->child_at(0), WaitForMessageScript("event.data"))); | 
 |   EXPECT_TRUE(ExecJs(popup_root->child_at(0), | 
 |                      "parent.opener.frames[0].postMessage('popup-ping', '*')")); | 
 |   EXPECT_EQ("popup-ping-reply", | 
 |             EvalJs(popup_root->child_at(0), "onMessagePromise")); | 
 |  | 
 |   // Verify the final proxies. The proxies for the opener window shouldn't have | 
 |   // changed (A and B are still visible to all other frames). | 
 |   EXPECT_EQ( | 
 |       " Site A ------------ proxies for B C D\n" | 
 |       "   +--Site B ------- proxies for A C D\n" | 
 |       "Where A = http://a.com/\n" | 
 |       "      B = http://b.com/\n" | 
 |       "      C = http://c.com/\n" | 
 |       "      D = http://d.com/", | 
 |       DepictFrameTree(root)); | 
 |   // Frames C and D should now have proxies in site B, since frame B can now | 
 |   // reach both of them (D via event.source, and C via event.source.top). | 
 |   EXPECT_EQ( | 
 |       " Site C ------------ proxies for A B D\n" | 
 |       "   +--Site D ------- proxies for A B C\n" | 
 |       "Where A = http://a.com/\n" | 
 |       "      B = http://b.com/\n" | 
 |       "      C = http://c.com/\n" | 
 |       "      D = http://d.com/", | 
 |       DepictFrameTree(popup_root)); | 
 | } | 
 |  | 
 | // Check that parent.frames[num] references correct sibling frames when the | 
 | // parent is remote.  See https://crbug.com/478792. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, IndexedFrameAccess) { | 
 |   // Start on a page with three same-site subframes. | 
 |   GURL main_url( | 
 |       embedded_test_server()->GetURL("a.com", "/frame_tree/top.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()->GetPrimaryFrameTree().root(); | 
 |   ASSERT_EQ(3U, root->child_count()); | 
 |   FrameTreeNode* child0 = root->child_at(0); | 
 |   FrameTreeNode* child1 = root->child_at(1); | 
 |   FrameTreeNode* child2 = root->child_at(2); | 
 |  | 
 |   // Send each of the frames to a different site.  Each new renderer will first | 
 |   // create proxies for the parent and two sibling subframes and then create | 
 |   // and insert the new RenderFrame into the frame tree. | 
 |   GURL b_url(embedded_test_server()->GetURL("b.com", "/post_message.html")); | 
 |   GURL c_url(embedded_test_server()->GetURL("c.com", "/post_message.html")); | 
 |   GURL d_url(embedded_test_server()->GetURL("d.com", "/post_message.html")); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(child0, b_url)); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(child1, c_url)); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(child2, d_url)); | 
 |  | 
 |   EXPECT_EQ( | 
 |       " Site A ------------ proxies for B C D\n" | 
 |       "   |--Site B ------- proxies for A C D\n" | 
 |       "   |--Site C ------- proxies for A B D\n" | 
 |       "   +--Site D ------- proxies for A B C\n" | 
 |       "Where A = http://a.com/\n" | 
 |       "      B = http://b.com/\n" | 
 |       "      C = http://c.com/\n" | 
 |       "      D = http://d.com/", | 
 |       DepictFrameTree(root)); | 
 |  | 
 |   // Check that each subframe sees itself at correct index in parent.frames. | 
 |   EXPECT_EQ(true, EvalJs(child0, "window === parent.frames[0];")); | 
 |   EXPECT_EQ(true, EvalJs(child1, "window === parent.frames[1];")); | 
 |   EXPECT_EQ(true, EvalJs(child2, "window === parent.frames[2];")); | 
 |  | 
 |   // Send a postMessage from B to parent.frames[1], which should go to C, and | 
 |   // wait for reply. | 
 |   PostMessageAndWaitForReply(child0, "postToSibling('subframe-msg', 1)", | 
 |                              "\"done-1-1-name\""); | 
 |  | 
 |   // Send a postMessage from C to parent.frames[2], which should go to D, and | 
 |   // wait for reply. | 
 |   PostMessageAndWaitForReply(child1, "postToSibling('subframe-msg', 2)", | 
 |                              "\"done-1-2-name\""); | 
 |  | 
 |   // Verify the total number of received messages for each subframe. | 
 |   EXPECT_EQ(1, GetReceivedMessages(child0)); | 
 |   EXPECT_EQ(2, GetReceivedMessages(child1)); | 
 |   EXPECT_EQ(1, GetReceivedMessages(child2)); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, RFPHDestruction) { | 
 |   GURL main_url(embedded_test_server()->GetURL("/site_per_process_main.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()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   TestNavigationObserver observer(shell()->web_contents()); | 
 |  | 
 |   // Load cross-site page into iframe. | 
 |   FrameTreeNode* child = root->child_at(0); | 
 |   GURL url = embedded_test_server()->GetURL("foo.com", "/title2.html"); | 
 |   { | 
 |     RenderFrameDeletedObserver deleted_observer(child->current_frame_host()); | 
 |     EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(0), url)); | 
 |     deleted_observer.WaitUntilDeleted(); | 
 |   } | 
 |   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" | 
 |       "        +--Site A -- proxies for B\n" | 
 |       "             +--Site A -- proxies for B\n" | 
 |       "Where A = http://127.0.0.1/\n" | 
 |       "      B = http://foo.com/", | 
 |       DepictFrameTree(root)); | 
 |  | 
 |   // Load another cross-site page. | 
 |   url = embedded_test_server()->GetURL("bar.com", "/title3.html"); | 
 |   { | 
 |     RenderFrameDeletedObserver deleted_observer(child->current_frame_host()); | 
 |     NavigateIframeToURL(shell()->web_contents(), "test", url); | 
 |     deleted_observer.WaitUntilDeleted(); | 
 |   } | 
 |   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" | 
 |       "        +--Site A -- proxies for C\n" | 
 |       "             +--Site A -- proxies for C\n" | 
 |       "Where A = http://127.0.0.1/\n" | 
 |       "      C = http://bar.com/", | 
 |       DepictFrameTree(root)); | 
 |  | 
 |   // Navigate back to the parent's origin. | 
 |   { | 
 |     RenderFrameDeletedObserver deleted_observer(child->current_frame_host()); | 
 |     url = embedded_test_server()->GetURL("/title1.html"); | 
 |     EXPECT_TRUE(NavigateToURLFromRenderer(child, url)); | 
 |     // Wait for the old process to exit, to verify that the proxies go away. | 
 |     deleted_observer.WaitUntilDeleted(); | 
 |   } | 
 |   EXPECT_EQ(url, observer.last_navigation_url()); | 
 |   EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |  | 
 |   EXPECT_EQ( | 
 |       " Site A\n" | 
 |       "   |--Site A\n" | 
 |       "   +--Site A\n" | 
 |       "        |--Site A\n" | 
 |       "        +--Site A\n" | 
 |       "             +--Site A\n" | 
 |       "Where A = http://127.0.0.1/", | 
 |       DepictFrameTree(root)); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, OpenPopupWithRemoteParent) { | 
 |   GURL main_url( | 
 |       embedded_test_server()->GetURL("a.com", "/site_per_process_main.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()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // Navigate first child cross-site. | 
 |   GURL frame_url(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(0), frame_url)); | 
 |  | 
 |   // Open a popup from the first child. | 
 |   Shell* new_shell = | 
 |       OpenPopup(root->child_at(0), GURL(url::kAboutBlankURL), ""); | 
 |   EXPECT_TRUE(new_shell); | 
 |  | 
 |   // Check that the popup's opener is correct on both the browser and renderer | 
 |   // sides. | 
 |   FrameTreeNode* popup_root = | 
 |       static_cast<WebContentsImpl*>(new_shell->web_contents()) | 
 |           ->GetPrimaryFrameTree() | 
 |           .root(); | 
 |   EXPECT_EQ(root->child_at(0), popup_root->opener()); | 
 |  | 
 |   EXPECT_EQ(frame_url.spec(), | 
 |             EvalJs(popup_root, "window.opener.location.href;")); | 
 |  | 
 |   // Now try the same with a cross-site popup and make sure it ends up in a new | 
 |   // process and with a correct opener. | 
 |   GURL popup_url(embedded_test_server()->GetURL("c.com", "/title2.html")); | 
 |   Shell* cross_site_popup = OpenPopup(root->child_at(0), popup_url, ""); | 
 |   EXPECT_TRUE(cross_site_popup); | 
 |  | 
 |   FrameTreeNode* cross_site_popup_root = | 
 |       static_cast<WebContentsImpl*>(cross_site_popup->web_contents()) | 
 |           ->GetPrimaryFrameTree() | 
 |           .root(); | 
 |   EXPECT_EQ(cross_site_popup_root->current_url(), popup_url); | 
 |  | 
 |   EXPECT_NE(shell()->web_contents()->GetSiteInstance(), | 
 |             cross_site_popup->web_contents()->GetSiteInstance()); | 
 |   EXPECT_NE(root->child_at(0)->current_frame_host()->GetSiteInstance(), | 
 |             cross_site_popup->web_contents()->GetSiteInstance()); | 
 |  | 
 |   EXPECT_EQ(root->child_at(0), cross_site_popup_root->opener()); | 
 |  | 
 |   // Ensure the popup's window.opener points to the right subframe.  Note that | 
 |   // we can't check the opener's location as above since it's cross-origin. | 
 |   EXPECT_EQ(true, EvalJs(cross_site_popup_root, | 
 |                          "window.opener === window.opener.top.frames[0];")); | 
 | } | 
 |  | 
 | // Test that cross-process popups can't be navigated to disallowed URLs by | 
 | // their opener.  This ensures that proper URL validation is performed when | 
 | // RenderFrameProxyHosts are navigated.  See https://crbug.com/595339. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, NavigatePopupToIllegalURL) { | 
 |   GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   // Open a cross-site popup. | 
 |   GURL popup_url(embedded_test_server()->GetURL("b.com", "/title2.html")); | 
 |   Shell* popup = OpenPopup(shell(), popup_url, "foo"); | 
 |   EXPECT_TRUE(popup); | 
 |   EXPECT_NE(popup->web_contents()->GetSiteInstance(), | 
 |             shell()->web_contents()->GetSiteInstance()); | 
 |  | 
 |   WebContentsConsoleObserver console_observer(web_contents()); | 
 |   console_observer.SetPattern("Not allowed to load local resource:*"); | 
 |  | 
 |   // From the opener, navigate the popup to a file:/// URL.  This should result | 
 |   // in a console error and stay on the old page. | 
 |   GURL file_url("file:///"); | 
 |   NavigateNamedFrame(shell(), file_url, "foo"); | 
 |   EXPECT_TRUE(WaitForLoadStop(popup->web_contents())); | 
 |   EXPECT_EQ(popup_url, popup->web_contents()->GetLastCommittedURL()); | 
 |   EXPECT_TRUE(base::MatchPattern(console_observer.GetMessageAt(0u), | 
 |                                  "Not allowed to load local resource: file:*")); | 
 |  | 
 |   // Now try the same test with a chrome:// URL. | 
 |   GURL chrome_url(std::string(kChromeUIScheme) + "://" + | 
 |                   std::string(kChromeUIGpuHost)); | 
 |   NavigateNamedFrame(shell(), chrome_url, "foo"); | 
 |   EXPECT_TRUE(WaitForLoadStop(popup->web_contents())); | 
 |   EXPECT_EQ(popup_url, popup->web_contents()->GetLastCommittedURL()); | 
 |   EXPECT_TRUE( | 
 |       base::MatchPattern(console_observer.GetMessageAt(1u), | 
 |                          std::string("Not allowed to load local resource: ") + | 
 |                              kChromeUIScheme + ":*")); | 
 | } | 
 |  | 
 | // Verify that named frames are discoverable from their opener's ancestors. | 
 | // See https://crbug.com/511474. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        DiscoverNamedFrameFromAncestorOfOpener) { | 
 |   GURL main_url( | 
 |       embedded_test_server()->GetURL("a.com", "/site_per_process_main.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()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // Navigate first child cross-site. | 
 |   GURL frame_url(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(0), frame_url)); | 
 |  | 
 |   // Open a popup named "foo" from the first child. | 
 |   Shell* foo_shell = | 
 |       OpenPopup(root->child_at(0), GURL(url::kAboutBlankURL), "foo"); | 
 |   EXPECT_TRUE(foo_shell); | 
 |  | 
 |   // Check that a proxy was created for the "foo" popup in a.com. | 
 |   FrameTreeNode* foo_root = | 
 |       static_cast<WebContentsImpl*>(foo_shell->web_contents()) | 
 |           ->GetPrimaryFrameTree() | 
 |           .root(); | 
 |   SiteInstanceImpl* site_instance_a = | 
 |       root->current_frame_host()->GetSiteInstance(); | 
 |   RenderFrameProxyHost* popup_rfph_for_a = | 
 |       foo_root->current_frame_host() | 
 |           ->browsing_context_state() | 
 |           ->GetRenderFrameProxyHost(site_instance_a->group()); | 
 |   EXPECT_TRUE(popup_rfph_for_a); | 
 |  | 
 |   // Verify that the main frame can find the "foo" popup by name.  If | 
 |   // window.open targets the correct frame, the "foo" popup's current URL | 
 |   // should be updated to |named_frame_url|. | 
 |   GURL named_frame_url(embedded_test_server()->GetURL("c.com", "/title2.html")); | 
 |   NavigateNamedFrame(shell(), named_frame_url, "foo"); | 
 |   EXPECT_TRUE(WaitForLoadStop(foo_shell->web_contents())); | 
 |   EXPECT_EQ(named_frame_url, foo_root->current_url()); | 
 |  | 
 |   // Navigate the popup cross-site and ensure it's still reachable via | 
 |   // window.open from the main frame. | 
 |   GURL d_url(embedded_test_server()->GetURL("d.com", "/title3.html")); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(foo_shell, d_url)); | 
 |   EXPECT_EQ(d_url, foo_root->current_url()); | 
 |   NavigateNamedFrame(shell(), named_frame_url, "foo"); | 
 |   EXPECT_TRUE(WaitForLoadStop(foo_shell->web_contents())); | 
 |   EXPECT_EQ(named_frame_url, foo_root->current_url()); | 
 | } | 
 |  | 
 | class SitePerProcessFencedFrameTest : public SitePerProcessBrowserTestBase { | 
 |  public: | 
 |   SitePerProcessFencedFrameTest() { | 
 |     fenced_frame_helper_ = | 
 |         std::make_unique<content::test::FencedFrameTestHelper>(); | 
 |   } | 
 |  | 
 |   void SetUpOnMainThread() override { | 
 |     SitePerProcessBrowserTestBase::SetUpOnMainThread(); | 
 |     https_server_.ServeFilesFromSourceDirectory(GetTestDataFilePath()); | 
 |     ASSERT_TRUE(https_server_.Start()); | 
 |   } | 
 |  | 
 |  protected: | 
 |   net::EmbeddedTestServer& https_server() { return https_server_; } | 
 |  | 
 |   content::RenderFrameHost* CreateFencedFrame(content::RenderFrameHost* parent, | 
 |                                               const GURL& url) { | 
 |     if (fenced_frame_helper_) { | 
 |       return fenced_frame_helper_->CreateFencedFrame(parent, url); | 
 |     } | 
 |  | 
 |     // FencedFrameTestHelper only supports the MPArch version of fenced frames. | 
 |     // So need to maually create a fenced frame for the ShadowDOM version. | 
 |     content::TestNavigationManager navigation(web_contents(), url); | 
 |  | 
 |     constexpr char kAddFencedFrameScript[] = R"({ | 
 |         const fenced_frame = document.createElement('fencedframe'); | 
 |         fenced_frame.src = $1; | 
 |         document.body.appendChild(fenced_frame); | 
 |     })"; | 
 |     EXPECT_TRUE(ExecJs(parent, content::JsReplace(kAddFencedFrameScript, url))); | 
 |     EXPECT_TRUE(navigation.WaitForNavigationFinished()); | 
 |  | 
 |     return ChildFrameAt(parent, 0); | 
 |   } | 
 |  | 
 |  private: | 
 |   base::test::ScopedFeatureList feature_list_; | 
 |   std::unique_ptr<content::test::FencedFrameTestHelper> fenced_frame_helper_; | 
 |   net::EmbeddedTestServer https_server_{net::EmbeddedTestServer::TYPE_HTTPS}; | 
 | }; | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(SitePerProcessFencedFrameTest, | 
 |                        PopupFromFencedFrameDoesNotCreateProxy) { | 
 |   GURL main_url(embedded_test_server()->GetURL("/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   // It is safe to obtain the root frame tree node here, as it doesn't change. | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // Create a fenced frame. | 
 |   GURL fenced_frame_url(https_server().GetURL("/fenced_frames/title1.html")); | 
 |   RenderFrameHost* fenced_frame_host = CreateFencedFrame( | 
 |       web_contents()->GetPrimaryMainFrame(), fenced_frame_url); | 
 |   EXPECT_NE(nullptr, fenced_frame_host); | 
 |  | 
 |   // Open a popup named "foo" from the fenced frame. | 
 |   Shell* popup_shell = | 
 |       OpenPopup(fenced_frame_host, GURL(url::kAboutBlankURL), "foo", "", false); | 
 |   EXPECT_TRUE(popup_shell); | 
 |  | 
 |   // Check that the popup from the fenced frame didn't create a proxy. | 
 |   // Opening popups from fenced frames forces noopener, which makes named | 
 |   // frames not discoverable. | 
 |   FrameTreeNode* popup_root = | 
 |       static_cast<WebContentsImpl*>(popup_shell->web_contents()) | 
 |           ->GetPrimaryFrameTree() | 
 |           .root(); | 
 |   EXPECT_EQ(nullptr, popup_root->opener()); | 
 |  | 
 |   SiteInstanceImpl* site_instance = | 
 |       root->current_frame_host()->GetSiteInstance(); | 
 |   EXPECT_FALSE(popup_root->current_frame_host() | 
 |                    ->browsing_context_state() | 
 |                    ->GetRenderFrameProxyHost(site_instance->group())); | 
 |  | 
 |   SiteInstanceImpl* embedder_site_instance = | 
 |       static_cast<RenderFrameHostImpl*>(fenced_frame_host)->GetSiteInstance(); | 
 |   EXPECT_FALSE(popup_root->current_frame_host() | 
 |                    ->browsing_context_state() | 
 |                    ->GetRenderFrameProxyHost(embedder_site_instance->group())); | 
 | } | 
 |  | 
 | // Similar to DiscoverNamedFrameFromAncestorOfOpener, but check that if a | 
 | // window is created without a name and acquires window.name later, it will | 
 | // still be discoverable from its opener's ancestors.  Also, instead of using | 
 | // an opener's ancestor, this test uses a popup with same origin as that | 
 | // ancestor. See https://crbug.com/511474. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        DiscoverFrameAfterSettingWindowName) { | 
 |   GURL main_url( | 
 |       embedded_test_server()->GetURL("a.com", "/site_per_process_main.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()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // Open a same-site popup from the main frame. | 
 |   GURL a_com_url(embedded_test_server()->GetURL("a.com", "/title3.html")); | 
 |   Shell* a_com_shell = OpenPopup(root->child_at(0), a_com_url, ""); | 
 |   EXPECT_TRUE(a_com_shell); | 
 |  | 
 |   // Navigate first child on main frame cross-site. | 
 |   GURL frame_url(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(0), frame_url)); | 
 |  | 
 |   // Open an unnamed popup from the first child frame. | 
 |   Shell* foo_shell = | 
 |       OpenPopup(root->child_at(0), GURL(url::kAboutBlankURL), ""); | 
 |   EXPECT_TRUE(foo_shell); | 
 |  | 
 |   // There should be no proxy created for the "foo" popup in a.com, since | 
 |   // there's no way for the two a.com frames to access it yet. | 
 |   FrameTreeNode* foo_root = | 
 |       static_cast<WebContentsImpl*>(foo_shell->web_contents()) | 
 |           ->GetPrimaryFrameTree() | 
 |           .root(); | 
 |   SiteInstanceImpl* site_instance_a = | 
 |       root->current_frame_host()->GetSiteInstance(); | 
 |   EXPECT_FALSE(foo_root->current_frame_host() | 
 |                    ->browsing_context_state() | 
 |                    ->GetRenderFrameProxyHost(site_instance_a->group())); | 
 |  | 
 |   // Set window.name in the popup's frame. | 
 |   EXPECT_TRUE(ExecJs(foo_shell, "window.name = 'foo'")); | 
 |  | 
 |   // A proxy for the popup should now exist in a.com. | 
 |   EXPECT_TRUE(foo_root->current_frame_host() | 
 |                   ->browsing_context_state() | 
 |                   ->GetRenderFrameProxyHost(site_instance_a->group())); | 
 |  | 
 |   // Verify that the a.com popup can now find the "foo" popup by name. | 
 |   GURL named_frame_url(embedded_test_server()->GetURL("c.com", "/title2.html")); | 
 |   NavigateNamedFrame(a_com_shell, named_frame_url, "foo"); | 
 |   EXPECT_TRUE(WaitForLoadStop(foo_shell->web_contents())); | 
 |   EXPECT_EQ(named_frame_url, foo_root->current_url()); | 
 | } | 
 |  | 
 | // Check that frame opener updates work with subframes.  Set up a window with a | 
 | // popup and update openers for the popup's main frame and subframe to | 
 | // subframes on first window, as follows: | 
 | // | 
 | //    foo      +---- bar | 
 | //    / \      |     / \      . | 
 | // bar   foo <-+  bar   foo | 
 | //  ^                    | | 
 | //  +--------------------+ | 
 | // | 
 | // The sites are carefully set up so that both opener updates are cross-process | 
 | // but still allowed by Blink's navigation checks. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, UpdateSubframeOpener) { | 
 |   GURL main_url = embedded_test_server()->GetURL( | 
 |       "foo.com", "/frame_tree/page_with_two_frames.html"); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   EXPECT_EQ(2U, root->child_count()); | 
 |  | 
 |   // From the top frame, open a popup and navigate it to a cross-site page with | 
 |   // two subframes. | 
 |   Shell* popup_shell = OpenPopup(shell(), GURL(url::kAboutBlankURL), "popup"); | 
 |   EXPECT_TRUE(popup_shell); | 
 |   GURL popup_url(embedded_test_server()->GetURL( | 
 |       "bar.com", "/frame_tree/page_with_post_message_frames.html")); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(popup_shell, popup_url)); | 
 |  | 
 |   FrameTreeNode* popup_root = | 
 |       static_cast<WebContentsImpl*>(popup_shell->web_contents()) | 
 |           ->GetPrimaryFrameTree() | 
 |           .root(); | 
 |   EXPECT_EQ(2U, popup_root->child_count()); | 
 |  | 
 |   // Popup's opener should point to main frame to start with. | 
 |   EXPECT_EQ(root, popup_root->opener()); | 
 |  | 
 |   // Update the popup's opener to the second subframe on the main page (which | 
 |   // is same-origin with the top frame, i.e., foo.com). | 
 |   EXPECT_EQ(true, EvalJs(root->child_at(1), "!!window.open('','popup');")); | 
 |  | 
 |   // Check that updated opener propagated to the browser process and the | 
 |   // popup's bar.com process. | 
 |   EXPECT_EQ(root->child_at(1), popup_root->opener()); | 
 |  | 
 |   EXPECT_EQ(true, | 
 |             EvalJs(popup_shell, | 
 |                    "window.opener === window.opener.parent.frames['frame2'];")); | 
 |  | 
 |   // Now update opener on the popup's second subframe (foo.com) to the main | 
 |   // page's first subframe (bar.com). | 
 |   EXPECT_EQ(true, EvalJs(root->child_at(0), "!!window.open('','subframe2');")); | 
 |  | 
 |   // Check that updated opener propagated to the browser process and the | 
 |   // foo.com process. | 
 |   EXPECT_EQ(root->child_at(0), popup_root->child_at(1)->opener()); | 
 |  | 
 |   EXPECT_EQ(true, | 
 |             EvalJs(popup_root->child_at(1), | 
 |                    "window.opener === window.opener.parent.frames['frame1'];")); | 
 | } | 
 |  | 
 | // Check that when a subframe navigates to a new SiteInstance, the new | 
 | // SiteInstance will get a proxy for the opener of subframe's parent.  I.e., | 
 | // accessing parent.opener from the subframe should still work after a | 
 | // cross-process navigation. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        NavigatingSubframePreservesOpenerInParent) { | 
 |   GURL main_url = embedded_test_server()->GetURL("a.com", "/post_message.html"); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // Open a popup with a cross-site page that has a subframe. | 
 |   GURL popup_url(embedded_test_server()->GetURL( | 
 |       "b.com", "/cross_site_iframe_factory.html?b(b)")); | 
 |   Shell* popup_shell = OpenPopup(shell(), popup_url, "popup"); | 
 |   EXPECT_TRUE(popup_shell); | 
 |   FrameTreeNode* popup_root = | 
 |       static_cast<WebContentsImpl*>(popup_shell->web_contents()) | 
 |           ->GetPrimaryFrameTree() | 
 |           .root(); | 
 |   EXPECT_EQ(1U, popup_root->child_count()); | 
 |  | 
 |   // Check that the popup's opener is correct in the browser process. | 
 |   EXPECT_EQ(root, popup_root->opener()); | 
 |  | 
 |   // Navigate popup's subframe to another site. | 
 |   GURL frame_url(embedded_test_server()->GetURL("c.com", "/post_message.html")); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(popup_root->child_at(0), frame_url)); | 
 |  | 
 |   // Check that the new subframe process still sees correct opener for its | 
 |   // parent by sending a postMessage to subframe's parent.opener. | 
 |   EXPECT_EQ(true, EvalJs(popup_root->child_at(0), "!!parent.opener;")); | 
 |  | 
 |   std::u16string expected_title = u"msg"; | 
 |   TitleWatcher title_watcher(shell()->web_contents(), expected_title); | 
 |   EXPECT_EQ(true, EvalJs(popup_root->child_at(0), | 
 |                          "postToOpenerOfParent('msg','*');")); | 
 |   EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle()); | 
 | } | 
 |  | 
 | // Check that if a subframe has an opener, that opener is preserved when the | 
 | // subframe navigates cross-site. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, NavigateSubframeWithOpener) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "foo.com", "/frame_tree/page_with_two_frames.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   EXPECT_EQ( | 
 |       " Site A ------------ proxies for B\n" | 
 |       "   |--Site B ------- proxies for A\n" | 
 |       "   +--Site A ------- proxies for B\n" | 
 |       "Where A = http://foo.com/\n" | 
 |       "      B = http://bar.com/", | 
 |       DepictFrameTree(root)); | 
 |  | 
 |   // Update the first (cross-site) subframe's opener to root frame. | 
 |   EXPECT_EQ(true, EvalJs(root, "!!window.open('','frame1');")); | 
 |  | 
 |   // Check that updated opener propagated to the browser process and subframe's | 
 |   // process. | 
 |   EXPECT_EQ(root, root->child_at(0)->opener()); | 
 |  | 
 |   EXPECT_EQ(true, | 
 |             EvalJs(root->child_at(0), "window.opener === window.parent;")); | 
 |  | 
 |   // Navigate the subframe with opener to another site. | 
 |   GURL frame_url(embedded_test_server()->GetURL("baz.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(0), frame_url)); | 
 |  | 
 |   // Check that the subframe still sees correct opener in its new process. | 
 |   EXPECT_EQ(true, | 
 |             EvalJs(root->child_at(0), "window.opener === window.parent;")); | 
 |  | 
 |   // Navigate second subframe to a new site.  Check that the proxy that's | 
 |   // created for the first subframe in the new SiteInstance has correct opener. | 
 |   GURL frame2_url(embedded_test_server()->GetURL("qux.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(1), frame2_url)); | 
 |  | 
 |   EXPECT_EQ(true, EvalJs(root->child_at(1), | 
 |                          "parent.frames['frame1'].opener === parent;")); | 
 | } | 
 |  | 
 | // Check that if a subframe has an opener, that opener is preserved when a new | 
 | // `blink::RemoteFrame` is created for that subframe in another renderer | 
 | // process. Similar to NavigateSubframeWithOpener, but this test verifies the | 
 | // subframe opener plumbing for blink::mojom::RemoteFrame::CreateRemoteChild(), | 
 | // whereas NavigateSubframeWithOpener targets mojom::Renderer::CreateFrame(). | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        NewRenderFrameProxyPreservesOpener) { | 
 |   GURL main_url( | 
 |       embedded_test_server()->GetURL("foo.com", "/post_message.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // Open a popup with a cross-site page that has two subframes. | 
 |   GURL popup_url(embedded_test_server()->GetURL( | 
 |       "bar.com", "/frame_tree/page_with_post_message_frames.html")); | 
 |   Shell* popup_shell = OpenPopup(shell(), popup_url, "popup"); | 
 |   EXPECT_TRUE(popup_shell); | 
 |   FrameTreeNode* popup_root = | 
 |       static_cast<WebContentsImpl*>(popup_shell->web_contents()) | 
 |           ->GetPrimaryFrameTree() | 
 |           .root(); | 
 |   EXPECT_EQ( | 
 |       " Site A ------------ proxies for B\n" | 
 |       "   |--Site A ------- proxies for B\n" | 
 |       "   +--Site B ------- proxies for A\n" | 
 |       "Where A = http://bar.com/\n" | 
 |       "      B = http://foo.com/", | 
 |       DepictFrameTree(popup_root)); | 
 |  | 
 |   // Update the popup's second subframe's opener to root frame.  This is | 
 |   // allowed because that subframe is in the same foo.com SiteInstance as the | 
 |   // root frame. | 
 |   EXPECT_EQ(true, EvalJs(root, "!!window.open('','subframe2');")); | 
 |  | 
 |   // Check that the opener update propagated to the browser process and bar.com | 
 |   // process. | 
 |   EXPECT_EQ(root, popup_root->child_at(1)->opener()); | 
 |   EXPECT_EQ(true, | 
 |             EvalJs(popup_root->child_at(0), | 
 |                    "parent.frames['subframe2'].opener && " | 
 |                    "    parent.frames['subframe2'].opener === parent.opener;")); | 
 |  | 
 |   // Navigate the popup's first subframe to another site. | 
 |   GURL frame_url( | 
 |       embedded_test_server()->GetURL("baz.com", "/post_message.html")); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(popup_root->child_at(0), frame_url)); | 
 |  | 
 |   // Check that the second subframe's opener is still correct in the first | 
 |   // subframe's new process.  Verify it both in JS and with a postMessage. | 
 |   EXPECT_EQ(true, | 
 |             EvalJs(popup_root->child_at(0), | 
 |                    "parent.frames['subframe2'].opener && " | 
 |                    "    parent.frames['subframe2'].opener === parent.opener;")); | 
 |  | 
 |   std::u16string expected_title = u"msg"; | 
 |   TitleWatcher title_watcher(shell()->web_contents(), expected_title); | 
 |   EXPECT_EQ(true, EvalJs(popup_root->child_at(0), | 
 |                          "postToOpenerOfSibling('subframe2', 'msg', '*');")); | 
 |   EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle()); | 
 | } | 
 |  | 
 | // Test for https://crbug.com/515302. Perform two navigations, A1 -> B2 -> A3, | 
 | // and drop the mojo::AgentSchedulingGroupHost::DidUnloadRenderFrame from the A1 | 
 | // -> B2 navigation, so that the second B2 -> A3 navigation is initiated before | 
 | // the first page receives the | 
 | // mojo::AgentSchedulingGroupHost::DidUnloadRenderFrame. Ensure that this | 
 | // doesn't crash and that the RVH(A1) is not reused in that case. | 
 | #if BUILDFLAG(IS_MAC) | 
 | #define MAYBE_RenderViewHostIsNotReusedAfterDelayedUnloadACK \ | 
 |   DISABLED_RenderViewHostIsNotReusedAfterDelayedUnloadACK | 
 | #else | 
 | #define MAYBE_RenderViewHostIsNotReusedAfterDelayedUnloadACK \ | 
 |   RenderViewHostIsNotReusedAfterDelayedUnloadACK | 
 | #endif | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        MAYBE_RenderViewHostIsNotReusedAfterDelayedUnloadACK) { | 
 |   GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), a_url)); | 
 |  | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   RenderFrameHostImpl* rfh = root->current_frame_host(); | 
 |   RenderViewHostImpl* rvh = rfh->render_view_host(); | 
 |   int rvh_routing_id = rvh->GetRoutingID(); | 
 |   int rvh_process_id = rvh->GetProcess()->GetDeprecatedID(); | 
 |   SiteInstanceImpl* site_instance = rfh->GetSiteInstance(); | 
 |   RenderFrameDeletedObserver deleted_observer(rfh); | 
 |  | 
 |   // Install a BrowserMessageFilter to drop | 
 |   // mojo::AgentSchedulingGroupHost::DidUnloadRenderFrame messages in A's | 
 |   // process. | 
 |   auto unload_ack_filter = base::BindRepeating([] { return true; }); | 
 |   rfh->SetUnloadACKCallbackForTesting(unload_ack_filter); | 
 |   rfh->DisableUnloadTimerForTesting(); | 
 |  | 
 |   // Navigate to B.  This must wait for DidCommitProvisionalLoad and not | 
 |   // DidStopLoading, so that the Unload timer doesn't call OnUnloaded and | 
 |   // destroy |rfh| and |rvh| before they are checked in the test. | 
 |   GURL b_url(embedded_test_server()->GetURL("b.com", "/title2.html")); | 
 |   TestFrameNavigationObserver commit_observer(root); | 
 |   EXPECT_TRUE(ExecJs(shell(), JsReplace("location = $1", b_url))); | 
 |   commit_observer.WaitForCommit(); | 
 |   EXPECT_FALSE(deleted_observer.deleted()); | 
 |  | 
 |   // The previous RFH should be either: | 
 |   // 1) In the BackForwardCache, if back-forward cache is enabled. | 
 |   // 2) Pending deletion otherwise, since the | 
 |   // mojo::AgentSchedulingGroupHost::DidUnloadRenderFrame for A->B is dropped. | 
 |   EXPECT_THAT( | 
 |       rfh->lifecycle_state(), | 
 |       testing::AnyOf( | 
 |           testing::Eq( | 
 |               RenderFrameHostImpl::LifecycleStateImpl::kRunningUnloadHandlers), | 
 |           testing::Eq( | 
 |               RenderFrameHostImpl::LifecycleStateImpl::kInBackForwardCache))); | 
 |  | 
 |   // Without the mojo::AgentSchedulingGroupHost::DidUnloadRenderFrame and timer, | 
 |   // the process A will never shutdown. Simulate the process being killed now. | 
 |   content::RenderProcessHostWatcher crash_observer( | 
 |       rvh->GetProcess(), | 
 |       content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); | 
 |   EXPECT_TRUE(rvh->GetProcess()->Shutdown(0)); | 
 |   crash_observer.Wait(); | 
 |  | 
 |   // Verify that the RVH and RFH for A were cleaned up. | 
 |   EXPECT_FALSE(root->frame_tree().GetRenderViewHost(site_instance->group())); | 
 |   EXPECT_TRUE(deleted_observer.deleted()); | 
 |  | 
 |   // Start a navigation back to A, being careful to stay in the same | 
 |   // BrowsingInstance, and check that the RenderViewHost wasn't reused. | 
 |   TestNavigationManager navigation_manager(shell()->web_contents(), a_url); | 
 |   shell()->LoadURLForFrame(a_url, std::string(), | 
 |                            ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK)); | 
 |   navigation_manager.WaitForSpeculativeRenderFrameHostCreation(); | 
 |   RenderFrameHostImpl* pending_rfh = | 
 |       root->render_manager()->speculative_frame_host(); | 
 |   RenderViewHostImpl* pending_rvh = pending_rfh->render_view_host(); | 
 |  | 
 |   // When ProactivelySwapBrowsingInstance A1 and A3 aren't using the same | 
 |   // BrowsingInstance. | 
 |   if (CanCrossSiteNavigationsProactivelySwapBrowsingInstances()) | 
 |     EXPECT_NE(site_instance, pending_rfh->GetSiteInstance()); | 
 |   else | 
 |     EXPECT_EQ(site_instance, pending_rfh->GetSiteInstance()); | 
 |  | 
 |   EXPECT_FALSE(rvh_routing_id == pending_rvh->GetRoutingID() && | 
 |                rvh_process_id == pending_rvh->GetProcess()->GetDeprecatedID()); | 
 |  | 
 |   // Make sure the last navigation finishes without crashing. | 
 |   ASSERT_TRUE(navigation_manager.WaitForNavigationFinished()); | 
 | } | 
 |  | 
 | // Test for https://crbug.com/591478, where navigating to a cross-site page with | 
 | // a subframe on the old site caused a crash while trying to reuse the old | 
 | // RenderViewHost. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        ReusePendingDeleteRenderViewHostForSubframe) { | 
 |   GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   std::string script = | 
 |       "window.onunload = function() { " | 
 |       "  var start = Date.now();" | 
 |       "  while (Date.now() - start < 1000);" | 
 |       "}"; | 
 |   EXPECT_TRUE(ExecJs(shell(), script)); | 
 |  | 
 |   // Navigating cross-site with an iframe to the original site shouldn't crash. | 
 |   GURL second_url(embedded_test_server()->GetURL( | 
 |       "b.com", "/cross_site_iframe_factory.html?b(a)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), second_url)); | 
 |  | 
 |   // If the subframe is created while the main frame is pending deletion, then | 
 |   // the RVH will be reused.  The main frame should've been swapped with a | 
 |   // proxy despite being the last active frame in the progress (see | 
 |   // https://crbug.com/568836), and this proxy should also be reused by the new | 
 |   // page. | 
 |   // | 
 |   // TODO(creis, alexmos): Find a way to assert this that isn't flaky. For now, | 
 |   // the test is just likely (not certain) to catch regressions by crashing. | 
 | } | 
 |  | 
 | // Check that when a cross-process frame acquires focus, the old focused frame | 
 | // loses focus and fires blur events.  Starting on a page with a cross-site | 
 | // subframe, simulate mouse clicks to switch focus from root frame to subframe | 
 | // and then back to root frame. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        CrossProcessFocusChangeFiresBlurEvents) { | 
 |   GURL main_url( | 
 |       embedded_test_server()->GetURL("a.com", "/page_with_input_field.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   SimulateEndOfPaintHoldingOnPrimaryMainFrame(web_contents()); | 
 |  | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   EXPECT_EQ( | 
 |       " Site A ------------ proxies for B\n" | 
 |       "   +--Site B ------- proxies for A\n" | 
 |       "Where A = http://a.com/\n" | 
 |       "      B = http://b.com/", | 
 |       DepictFrameTree(root)); | 
 |  | 
 |   // Focus the main frame's text field.  The return value "input-focus" | 
 |   // indicates that the focus event was fired correctly. | 
 |   EXPECT_EQ("input-focus", EvalJs(shell(), "focusInputField()")); | 
 |  | 
 |   // The main frame should be focused. | 
 |   EXPECT_EQ(root, root->frame_tree().GetFocusedFrame()); | 
 |  | 
 |   DOMMessageQueue msg_queue(web_contents()); | 
 |  | 
 |   // Click on the cross-process subframe. | 
 |   SimulateMouseClick( | 
 |       root->child_at(0)->current_frame_host()->GetRenderWidgetHost(), 1, 1); | 
 |  | 
 |   // Check that the main frame lost focus and fired blur event on the input | 
 |   // text field. | 
 |   EXPECT_EQ(true, EvalJs(shell(), "waitForBlur()")); | 
 |  | 
 |   // The subframe should now be focused. | 
 |   EXPECT_EQ(root->child_at(0), root->frame_tree().GetFocusedFrame()); | 
 |  | 
 |   // Click on the root frame. | 
 |   SimulateMouseClick(shell() | 
 |                          ->web_contents() | 
 |                          ->GetPrimaryMainFrame() | 
 |                          ->GetRenderViewHost() | 
 |                          ->GetWidget(), | 
 |                      1, 1); | 
 |  | 
 |   // Check that the subframe lost focus and fired blur event on its | 
 |   // document's body. | 
 |   std::string status; | 
 |   while (msg_queue.WaitForMessage(&status)) { | 
 |     if (status == "\"document-blur\"") | 
 |       break; | 
 |   } | 
 |  | 
 |   // The root frame should be focused again. | 
 |   EXPECT_EQ(root, root->frame_tree().GetFocusedFrame()); | 
 | } | 
 |  | 
 | // Check that when a cross-process subframe is focused, its parent's | 
 | // document.activeElement correctly returns the corresponding <iframe> element. | 
 | // The test sets up an A-embed-B-embed-C page and shifts focus A->B->A->C, | 
 | // checking document.activeElement after each change. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, DocumentActiveElement) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b(c))")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   SimulateEndOfPaintHoldingOnPrimaryMainFrame(web_contents()); | 
 |  | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   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://c.com/", | 
 |       DepictFrameTree(root)); | 
 |  | 
 |   FrameTreeNode* child = root->child_at(0); | 
 |   FrameTreeNode* grandchild = root->child_at(0)->child_at(0); | 
 |  | 
 |   // The main frame should be focused to start with. | 
 |   EXPECT_EQ(root, root->frame_tree().GetFocusedFrame()); | 
 |  | 
 |   // Focus the b.com frame. | 
 |   FocusFrame(child); | 
 |   EXPECT_EQ(child, root->frame_tree().GetFocusedFrame()); | 
 |  | 
 |   // Helper function to check a property of document.activeElement in the | 
 |   // specified frame. | 
 |   auto verify_active_element_property = [](RenderFrameHost* rfh, | 
 |                                            const std::string& property, | 
 |                                            const std::string& expected_value) { | 
 |     std::string script = base::StringPrintf( | 
 |         "document.activeElement.%s.toLowerCase();", property.c_str()); | 
 |     EXPECT_EQ(expected_value, EvalJs(rfh, script)); | 
 |   }; | 
 |  | 
 |   // Verify that document.activeElement on main frame points to the <iframe> | 
 |   // element for the b.com frame. | 
 |   RenderFrameHost* root_rfh = root->current_frame_host(); | 
 |   verify_active_element_property(root_rfh, "tagName", "iframe"); | 
 |   verify_active_element_property(root_rfh, "src", child->current_url().spec()); | 
 |  | 
 |   // Focus the a.com main frame again. | 
 |   FocusFrame(root); | 
 |   EXPECT_EQ(root, root->frame_tree().GetFocusedFrame()); | 
 |  | 
 |   // Main frame document's <body> should now be the active element. | 
 |   verify_active_element_property(root_rfh, "tagName", "body"); | 
 |  | 
 |   // Now shift focus from main frame to c.com frame. | 
 |   FocusFrame(grandchild); | 
 |  | 
 |   // Check document.activeElement in main frame.  It should still point to | 
 |   // <iframe> for the b.com frame, since Blink computes the focused iframe | 
 |   // element by walking the parent chain of the focused frame until it hits the | 
 |   // current frame.  This logic should still work with remote frames. | 
 |   verify_active_element_property(root_rfh, "tagName", "iframe"); | 
 |   verify_active_element_property(root_rfh, "src", child->current_url().spec()); | 
 |  | 
 |   // Check document.activeElement in b.com subframe.  It should point to | 
 |   // <iframe> for the c.com frame.  This is a tricky case where B needs to find | 
 |   // out that focus changed from one remote frame to another (A to C). | 
 |   RenderFrameHost* child_rfh = child->current_frame_host(); | 
 |   verify_active_element_property(child_rfh, "tagName", "iframe"); | 
 |   verify_active_element_property(child_rfh, "src", | 
 |                                  grandchild->current_url().spec()); | 
 | } | 
 |  | 
 | // Check that window.focus works for cross-process subframes. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, SubframeWindowFocus) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b,c)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   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://c.com/", | 
 |       DepictFrameTree(root)); | 
 |  | 
 |   FrameTreeNode* child1 = root->child_at(0); | 
 |   FrameTreeNode* child2 = root->child_at(1); | 
 |  | 
 |   // The main frame should be focused to start with. | 
 |   EXPECT_EQ(root, root->frame_tree().GetFocusedFrame()); | 
 |  | 
 |   // Register focus and blur events that will send messages when each frame's | 
 |   // window gets or loses focus, and configure some utility functions useful for | 
 |   // waiting for these messages. | 
 |   const char kSetupFocusEvents[] = R"( | 
 |       window.addEventListener('focus', function() { | 
 |         window.top.postMessage('%s-got-focus', '*'); | 
 |       }); | 
 |       window.addEventListener('blur', function() { | 
 |         window.top.postMessage('%s-lost-focus', '*'); | 
 |       }); | 
 |       function onEvent(target, eventName, property, value) { | 
 |         return new Promise((resolve, reject) => { | 
 |           function listener(event) { | 
 |             if (event[property] == value) { | 
 |               resolve(); | 
 |               target.removeEventListener(eventName, listener); | 
 |             } | 
 |           }; | 
 |           target.addEventListener(eventName, listener); | 
 |         }); | 
 |       } | 
 |       function expectMessages(messageList) { | 
 |         var promiseList = messageList.map( | 
 |             (dataValue) => onEvent(window, 'message', 'data', dataValue)); | 
 |         return Promise.all(promiseList); | 
 |       } | 
 |   )"; | 
 |   std::string script = base::StringPrintf(kSetupFocusEvents, "main", "main"); | 
 |   ExecuteScriptAsync(shell(), script); | 
 |   script = base::StringPrintf(kSetupFocusEvents, "child1", "child1"); | 
 |   ExecuteScriptAsync(child1, script); | 
 |   script = base::StringPrintf(kSetupFocusEvents, "child2", "child2"); | 
 |   ExecuteScriptAsync(child2, script); | 
 |  | 
 |   // Execute window.focus on the B subframe from the A main frame. | 
 |   // Process A should fire a blur event, and process B should fire a focus | 
 |   // event.  Wait for both events. | 
 |   EXPECT_EQ(true, EvalJs(root, R"((async function() { | 
 |       allMessages = []; | 
 |       window.addEventListener('message', (event) => { | 
 |         allMessages.push(event.data); | 
 |       }); | 
 |  | 
 |       var messages = expectMessages(['main-lost-focus', 'child1-got-focus']); | 
 |       frames[0].focus(); | 
 |       await messages; | 
 |  | 
 |       return allMessages.length == 2 || allMessages; | 
 |   })())")); | 
 |  | 
 |   EXPECT_EQ(child1, root->frame_tree().GetFocusedFrame()); | 
 |  | 
 |   // Now, execute window.focus on the C subframe from A main frame.  This | 
 |   // checks that we can shift focus from one remote frame to another. | 
 |   // | 
 |   // Wait for the two subframes (B and C) to fire blur and focus events. | 
 |   EXPECT_EQ(true, EvalJs(root, R"((async function() { | 
 |       var messages = expectMessages(['child1-lost-focus', 'child2-got-focus']); | 
 |       frames[1].focus(); | 
 |       await messages; | 
 |       return allMessages.length == 4 || allMessages; | 
 |   })())")); | 
 |  | 
 |   // The C subframe should now be focused. | 
 |   EXPECT_EQ(child2, root->frame_tree().GetFocusedFrame()); | 
 |  | 
 |   // Install event listeners in the A main frame, expecting the main frame to | 
 |   // obtain focus. | 
 |   EXPECT_TRUE( | 
 |       ExecJs(root, | 
 |              "var messages = " | 
 |              "    expectMessages(['child2-lost-focus', 'main-got-focus']);")); | 
 |  | 
 |   // window.focus the main frame from the C subframe. | 
 |   ExecuteScriptAsync(child2, "parent.focus()"); | 
 |  | 
 |   // Wait for the messages to arrive in the A main frame. | 
 |   EXPECT_EQ(true, EvalJs(root, R"((async function() { | 
 |       await messages; | 
 |       return allMessages.length == 6 || allMessages; | 
 |   })())")); | 
 |  | 
 |   // The main frame should now be focused. | 
 |   EXPECT_EQ(root, root->frame_tree().GetFocusedFrame()); | 
 | } | 
 |  | 
 | // Check that when a subframe has focus, and another subframe navigates | 
 | // cross-site to a new renderer process, this doesn't reset the focused frame | 
 | // to the main frame.  See https://crbug.com/802156. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        SubframeFocusNotLostWhenAnotherFrameNavigatesCrossSite) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(a,a)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   FrameTreeNode* child1 = root->child_at(0); | 
 |   FrameTreeNode* child2 = root->child_at(1); | 
 |  | 
 |   // The main frame should be focused to start with. | 
 |   EXPECT_EQ(root, root->frame_tree().GetFocusedFrame()); | 
 |  | 
 |   // Add an <input> element to the first subframe. | 
 |   ExecuteScriptAsync( | 
 |       child1, "document.body.appendChild(document.createElement('input'))"); | 
 |  | 
 |   // Focus the first subframe using window.focus(). | 
 |   FrameFocusedObserver focus_observer(child1->current_frame_host()); | 
 |   ExecuteScriptAsync(root, "frames[0].focus()"); | 
 |   focus_observer.Wait(); | 
 |   EXPECT_EQ(child1, root->frame_tree().GetFocusedFrame()); | 
 |  | 
 |   // Give focus to the <input> element in the first subframe. | 
 |   ExecuteScriptAsync(child1, "document.querySelector('input').focus()"); | 
 |  | 
 |   // Now, navigate second subframe cross-site.  Ensure that this won't change | 
 |   // the focused frame. | 
 |   GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(child2, b_url)); | 
 |   // This is needed because the incorrect focused frame change as in | 
 |   // https://crbug.com/802156 requires an additional post-commit IPC roundtrip. | 
 |   base::RunLoop().RunUntilIdle(); | 
 |   EXPECT_EQ(child1, root->frame_tree().GetFocusedFrame()); | 
 |  | 
 |   // The <input> in first subframe should still be the activeElement. | 
 |   EXPECT_EQ( | 
 |       "input", | 
 |       base::ToLowerASCII( | 
 |           EvalJs(child1, "document.activeElement.tagName").ExtractString())); | 
 | } | 
 |  | 
 | // Tests that we are using the correct `blink::RemoteFrame` when navigating an | 
 | // opener window. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, OpenerSetLocation) { | 
 |   // Navigate the main window. | 
 |   GURL main_url(embedded_test_server()->GetURL("/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |   EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), main_url); | 
 |  | 
 |   // Load cross-site page into a new window. | 
 |   GURL cross_url = embedded_test_server()->GetURL("foo.com", "/title1.html"); | 
 |   Shell* popup = OpenPopup(shell(), cross_url, ""); | 
 |   EXPECT_EQ(popup->web_contents()->GetLastCommittedURL(), cross_url); | 
 |  | 
 |   // Use new window to navigate main window. | 
 |   EXPECT_TRUE( | 
 |       ExecJs(popup, JsReplace("window.opener.location.href = $1", cross_url))); | 
 |   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); | 
 |   EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), cross_url); | 
 | } | 
 |  | 
 | // crbug.com/1281755 | 
 | #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) | 
 | #define MAYBE_NavigateProxyAndDetachBeforeProvisionalFrameCreation \ | 
 |   DISABLED_NavigateProxyAndDetachBeforeProvisionalFrameCreation | 
 | #else | 
 | #define MAYBE_NavigateProxyAndDetachBeforeProvisionalFrameCreation \ | 
 |   NavigateProxyAndDetachBeforeProvisionalFrameCreation | 
 | #endif | 
 | // Test for https://crbug.com/526304, where a parent frame executes a | 
 | // remote-to-local navigation on a child frame and immediately removes the same | 
 | // child frame.  This test exercises the path where the detach happens before | 
 | // the provisional local frame is created. | 
 | IN_PROC_BROWSER_TEST_P( | 
 |     SitePerProcessBrowserTest, | 
 |     MAYBE_NavigateProxyAndDetachBeforeProvisionalFrameCreation) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b,b)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   WebContents* contents = shell()->web_contents(); | 
 |   FrameTreeNode* root = | 
 |       static_cast<WebContentsImpl*>(contents)->GetPrimaryFrameTree().root(); | 
 |   EXPECT_EQ(2U, root->child_count()); | 
 |  | 
 |   // Navigate the first child frame to 'about:blank' (which is a | 
 |   // remote-to-local transition), and then detach it. | 
 |   FrameDeletedObserver observer(root->child_at(0)->current_frame_host()); | 
 |   std::string script = | 
 |       "var f = document.querySelector('iframe');" | 
 |       "f.contentWindow.location.href = 'about:blank';" | 
 |       "setTimeout(function() { document.body.removeChild(f); }, 0);"; | 
 |   EXPECT_TRUE(ExecJs(root, script)); | 
 |   observer.Wait(); | 
 |   EXPECT_EQ(1U, root->child_count()); | 
 |  | 
 |   // Make sure the main frame renderer does not crash and ignores the | 
 |   // navigation to the frame that's already been deleted. | 
 |   EXPECT_EQ(1, EvalJs(root, "frames.length")); | 
 | } | 
 |  | 
 | // Test for a variation of https://crbug.com/526304, where a child frame does a | 
 | // remote-to-local navigation, and the parent frame removes that child frame | 
 | // after the provisional local frame is created and starts to navigate, but | 
 | // before it commits. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        NavigateProxyAndDetachBeforeCommit) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b,b)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   WebContents* contents = shell()->web_contents(); | 
 |   FrameTreeNode* root = | 
 |       static_cast<WebContentsImpl*>(contents)->GetPrimaryFrameTree().root(); | 
 |   EXPECT_EQ(2U, root->child_count()); | 
 |   FrameTreeNode* child = root->child_at(0); | 
 |  | 
 |   // Start a remote-to-local navigation for the child, but don't wait for | 
 |   // commit. | 
 |   GURL same_site_url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   NavigationController::LoadURLParams params(same_site_url); | 
 |   params.transition_type = ui::PAGE_TRANSITION_LINK; | 
 |   params.frame_tree_node_id = child->frame_tree_node_id(); | 
 |   child->navigator().controller().LoadURLWithParams(params); | 
 |  | 
 |   // Tell parent to remove the first child.  This should happen after the | 
 |   // previous navigation starts but before it commits. | 
 |   FrameDeletedObserver observer(child->current_frame_host()); | 
 |   EXPECT_TRUE(ExecJs( | 
 |       root, "document.body.removeChild(document.querySelector('iframe'));")); | 
 |   observer.Wait(); | 
 |   EXPECT_EQ(1U, root->child_count()); | 
 |  | 
 |   // Make sure the a.com renderer does not crash. | 
 |   EXPECT_EQ(1, EvalJs(root, "frames.length;")); | 
 | } | 
 |  | 
 | // Similar to NavigateProxyAndDetachBeforeCommit, but uses a synchronous | 
 | // navigation to about:blank and the parent removes the child frame in a load | 
 | // event handler for the subframe. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, NavigateAboutBlankAndDetach) { | 
 |   GURL main_url( | 
 |       embedded_test_server()->GetURL("a.com", "/remove_frame_on_load.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   WebContents* contents = shell()->web_contents(); | 
 |   FrameTreeNode* root = | 
 |       static_cast<WebContentsImpl*>(contents)->GetPrimaryFrameTree().root(); | 
 |   EXPECT_EQ(1U, root->child_count()); | 
 |   FrameTreeNode* child = root->child_at(0); | 
 |   EXPECT_NE(shell()->web_contents()->GetSiteInstance(), | 
 |             child->current_frame_host()->GetSiteInstance()); | 
 |  | 
 |   // Navigate the child frame to "about:blank" from the parent document and | 
 |   // wait for it to be removed. | 
 |   FrameDeletedObserver observer(child->current_frame_host()); | 
 |   EXPECT_TRUE( | 
 |       ExecJs(root, base::StringPrintf("f.src = '%s'", url::kAboutBlankURL))); | 
 |   observer.Wait(); | 
 |  | 
 |   // Make sure the a.com renderer does not crash and the frame is removed. | 
 |   EXPECT_EQ(0, EvalJs(root, "frames.length;")); | 
 | } | 
 |  | 
 | // This test ensures that the RenderFrame isn't leaked in the renderer process | 
 | // if a pending cross-process navigation is cancelled. The test works by trying | 
 | // to create a new RenderFrame with the same routing id. If there is an | 
 | // entry with the same routing ID, a CHECK is hit and the process crashes. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        SubframePendingAndBackToSameSiteInstance) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   // Capture the FrameTreeNode this test will be navigating. | 
 |   FrameTreeNode* node = | 
 |       web_contents()->GetPrimaryFrameTree().root()->child_at(0); | 
 |   EXPECT_TRUE(node); | 
 |   EXPECT_NE(node->current_frame_host()->GetSiteInstance(), | 
 |             node->parent()->GetSiteInstance()); | 
 |  | 
 |   // Navigate to the site of the parent, but to a page that will not commit. | 
 |   GURL same_site_url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   TestNavigationManager stalled_navigation(web_contents(), same_site_url); | 
 |   { | 
 |     NavigationController::LoadURLParams params(same_site_url); | 
 |     params.transition_type = ui::PAGE_TRANSITION_LINK; | 
 |     params.frame_tree_node_id = node->frame_tree_node_id(); | 
 |     node->navigator().controller().LoadURLWithParams(params); | 
 |     EXPECT_TRUE(stalled_navigation.WaitForResponse()); | 
 |   } | 
 |  | 
 |   // Grab the routing id of the pending RenderFrameHost and set up a process | 
 |   // observer to ensure there is no crash when a new RenderFrame creation is | 
 |   // attempted. | 
 |   RenderProcessHost* process = | 
 |       node->render_manager()->speculative_frame_host()->GetProcess(); | 
 |   AgentSchedulingGroupHost* agent_scheduling_group = | 
 |       AgentSchedulingGroupHost::GetOrCreate(*node->render_manager() | 
 |                                                  ->speculative_frame_host() | 
 |                                                  ->GetSiteInstance() | 
 |                                                  ->group(), | 
 |                                             *process); | 
 |   RenderProcessHostWatcher watcher( | 
 |       process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); | 
 |   int frame_routing_id = | 
 |       node->render_manager()->speculative_frame_host()->GetRoutingID(); | 
 |   blink::LocalFrameToken frame_token = | 
 |       node->render_manager()->speculative_frame_host()->GetFrameToken(); | 
 |   blink::RemoteFrameToken previous_frame_token = | 
 |       node->render_manager()->GetProxyToParent()->GetFrameToken(); | 
 |  | 
 |   // Now go to c.com so the navigation to a.com is cancelled and send an IPC | 
 |   // to create a new RenderFrame with the routing id of the previously pending | 
 |   // one. | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer( | 
 |       node, embedded_test_server()->GetURL("c.com", "/title2.html"))); | 
 |   { | 
 |     mojo::PendingAssociatedRemote<mojom::Frame> pending_frame; | 
 |  | 
 |     mojom::CreateFrameParamsPtr params = mojom::CreateFrameParams::New(); | 
 |     params->routing_id = frame_routing_id; | 
 |     params->frame = pending_frame.InitWithNewEndpointAndPassReceiver(); | 
 |     std::ignore = params->interface_broker.InitWithNewPipeAndPassReceiver(); | 
 |     std::ignore = params->associated_interface_provider_remote | 
 |                       .InitWithNewEndpointAndPassReceiver(); | 
 |     params->previous_frame_token = previous_frame_token; | 
 |     params->opener_frame_token = std::nullopt; | 
 |     params->parent_frame_token = | 
 |         shell()->web_contents()->GetPrimaryMainFrame()->GetFrameToken(); | 
 |     params->frame_owner_properties = blink::mojom::FrameOwnerProperties::New(); | 
 |     params->frame_token = frame_token; | 
 |     params->devtools_frame_token = base::UnguessableToken::Create(); | 
 |     params->document_token = blink::DocumentToken(); | 
 |     params->policy_container = CreateStubPolicyContainer(); | 
 |     params->replication_state = blink::mojom::FrameReplicationState::New(); | 
 |     agent_scheduling_group->CreateFrame(std::move(params)); | 
 |   } | 
 |  | 
 |   // Disable the BackForwardCache to ensure the old process is going to be | 
 |   // released. | 
 |   DisableBackForwardCacheForTesting(web_contents(), | 
 |                                     BackForwardCache::TEST_REQUIRES_NO_CACHING); | 
 |  | 
 |   // The test must wait for the process to exit, but if there is no leak, the | 
 |   // RenderFrame will be properly created and there will be no crash. | 
 |   // Therefore, navigate the main frame to completely different site, which | 
 |   // will cause the original process to exit cleanly. | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("d.com", "/title3.html"))); | 
 |   watcher.Wait(); | 
 |   EXPECT_TRUE(watcher.did_exit_normally()); | 
 | } | 
 |  | 
 | // This test ensures that the RenderFrame isn't leaked in the renderer process | 
 | // when a remote parent detaches a child frame. The test works by trying | 
 | // to create a new RenderFrame with the same routing id. If there is an | 
 | // entry with the same routing ID, a CHECK is hit and the process crashes. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, ParentDetachRemoteChild) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b,b)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   WebContentsImpl* contents = web_contents(); | 
 |   EXPECT_EQ(2U, contents->GetPrimaryFrameTree().root()->child_count()); | 
 |  | 
 |   // Capture the FrameTreeNode this test will be navigating. | 
 |   FrameTreeNode* node = contents->GetPrimaryFrameTree().root()->child_at(0); | 
 |   EXPECT_TRUE(node); | 
 |   EXPECT_NE(node->current_frame_host()->GetSiteInstance(), | 
 |             node->parent()->GetSiteInstance()); | 
 |  | 
 |   // Grab the routing id of the first child RenderFrameHost and set up a process | 
 |   // observer to ensure there is no crash when a new RenderFrame creation is | 
 |   // attempted. | 
 |   RenderProcessHost* process = node->current_frame_host()->GetProcess(); | 
 |   AgentSchedulingGroupHost* agent_scheduling_group = | 
 |       AgentSchedulingGroupHost::GetOrCreate( | 
 |           *node->current_frame_host()->GetSiteInstance()->group(), *process); | 
 |   RenderProcessHostWatcher watcher( | 
 |       process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); | 
 |   int frame_routing_id = node->current_frame_host()->GetRoutingID(); | 
 |   blink::LocalFrameToken frame_token = | 
 |       node->current_frame_host()->GetFrameToken(); | 
 |   int widget_routing_id = | 
 |       node->current_frame_host()->GetRenderWidgetHost()->GetRoutingID(); | 
 |   std::optional<blink::FrameToken> parent_frame_token = | 
 |       node->parent() | 
 |           ->frame_tree_node() | 
 |           ->render_manager() | 
 |           ->GetFrameTokenForSiteInstanceGroup( | 
 |               node->current_frame_host()->GetSiteInstance()->group()); | 
 |  | 
 |   // Have the parent frame remove the child frame from its DOM. This should | 
 |   // result in the child RenderFrame being deleted in the remote process. | 
 |   EXPECT_TRUE(ExecJs(contents, | 
 |                      "document.body.removeChild(" | 
 |                      "document.querySelectorAll('iframe')[0])")); | 
 |   EXPECT_EQ(1U, contents->GetPrimaryFrameTree().root()->child_count()); | 
 |  | 
 |   { | 
 |     mojo::PendingAssociatedRemote<mojom::Frame> pending_frame; | 
 |     mojo::PendingAssociatedRemote<blink::mojom::FrameWidget> blink_frame_widget; | 
 |     mojo::PendingAssociatedRemote<blink::mojom::Widget> blink_widget; | 
 |  | 
 |     mojom::CreateFrameParamsPtr params = mojom::CreateFrameParams::New(); | 
 |     params->routing_id = frame_routing_id; | 
 |     params->frame = pending_frame.InitWithNewEndpointAndPassReceiver(); | 
 |     std::ignore = params->interface_broker.InitWithNewPipeAndPassReceiver(); | 
 |     std::ignore = params->associated_interface_provider_remote | 
 |                       .InitWithNewEndpointAndPassReceiver(); | 
 |     params->previous_frame_token = std::nullopt; | 
 |     params->opener_frame_token = std::nullopt; | 
 |     params->parent_frame_token = parent_frame_token; | 
 |     params->previous_sibling_frame_token = std::nullopt; | 
 |     params->frame_owner_properties = blink::mojom::FrameOwnerProperties::New(); | 
 |     params->widget_params = mojom::CreateFrameWidgetParams::New(); | 
 |     params->widget_params->routing_id = widget_routing_id; | 
 |     params->widget_params->frame_widget = | 
 |         blink_frame_widget.InitWithNewEndpointAndPassReceiver(); | 
 |     params->widget_params->widget = | 
 |         blink_widget.InitWithNewEndpointAndPassReceiver(); | 
 |     std::ignore = params->widget_params->frame_widget_host | 
 |                       .InitWithNewEndpointAndPassReceiver(); | 
 |     std::ignore = | 
 |         params->widget_params->widget_host.InitWithNewEndpointAndPassReceiver(); | 
 |     params->widget_params->visual_properties.screen_infos = | 
 |         display::ScreenInfos(display::ScreenInfo()); | 
 |     params->replication_state = blink::mojom::FrameReplicationState::New(); | 
 |     params->replication_state->name = "name"; | 
 |     params->replication_state->unique_name = "name"; | 
 |     params->frame_token = frame_token; | 
 |     params->devtools_frame_token = base::UnguessableToken::Create(); | 
 |     params->document_token = blink::DocumentToken(); | 
 |     params->policy_container = CreateStubPolicyContainer(); | 
 |     agent_scheduling_group->CreateFrame(std::move(params)); | 
 |   } | 
 |  | 
 |   // The test must wait for the process to exit, but if there is no leak, the | 
 |   // RenderFrame will be properly created and there will be no crash. | 
 |   // Therefore, navigate the remaining subframe to completely different site, | 
 |   // which will cause the original process to exit cleanly. | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer( | 
 |       contents->GetPrimaryFrameTree().root()->child_at(0), | 
 |       embedded_test_server()->GetURL("d.com", "/title3.html"))); | 
 |   watcher.Wait(); | 
 |   EXPECT_TRUE(watcher.did_exit_normally()); | 
 | } | 
 |  | 
 | // Verify that sandbox flags inheritance works across multiple levels of | 
 | // frames.  See https://crbug.com/576845. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, SandboxFlagsInheritance) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(a)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   // It is safe to obtain the root frame tree node here, as it doesn't change. | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // Set sandbox flags for child frame. | 
 |   EXPECT_TRUE(ExecJs( | 
 |       root, "document.querySelector('iframe').sandbox = 'allow-scripts';")); | 
 |  | 
 |   // Calculate expected flags.  Note that "allow-scripts" resets both | 
 |   // network::mojom::WebSandboxFlags::Scripts and | 
 |   // network::mojom::WebSandboxFlags::AutomaticFeatures bits per | 
 |   // blink::parseSandboxPolicy(). | 
 |   network::mojom::WebSandboxFlags expected_flags = | 
 |       network::mojom::WebSandboxFlags::kAll & | 
 |       ~network::mojom::WebSandboxFlags::kScripts & | 
 |       ~network::mojom::WebSandboxFlags::kAutomaticFeatures; | 
 |   EXPECT_EQ(expected_flags, | 
 |             root->child_at(0)->pending_frame_policy().sandbox_flags); | 
 |   EXPECT_EQ(network::mojom::WebSandboxFlags::kNone, | 
 |             root->child_at(0)->effective_frame_policy().sandbox_flags); | 
 |  | 
 |   // Navigate child frame so that the sandbox flags take effect.  Use a page | 
 |   // with three levels of frames and make sure all frames properly inherit | 
 |   // sandbox flags. | 
 |   GURL frame_url(embedded_test_server()->GetURL( | 
 |       "b.com", "/cross_site_iframe_factory.html?b(c(d))")); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(0), frame_url)); | 
 |  | 
 |   // Wait for subframes to load as well. | 
 |   ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); | 
 |  | 
 |   // Check each new frame's sandbox flags on the browser process side. | 
 |   FrameTreeNode* b_child = root->child_at(0); | 
 |   FrameTreeNode* c_child = b_child->child_at(0); | 
 |   FrameTreeNode* d_child = c_child->child_at(0); | 
 |   EXPECT_EQ(expected_flags, b_child->effective_frame_policy().sandbox_flags); | 
 |   EXPECT_EQ(expected_flags, c_child->effective_frame_policy().sandbox_flags); | 
 |   EXPECT_EQ(expected_flags, d_child->effective_frame_policy().sandbox_flags); | 
 |  | 
 |   // Check whether each frame is sandboxed on the renderer side, by seeing if | 
 |   // each frame's origin is unique ("null"). | 
 |   EXPECT_EQ("null", GetOriginFromRenderer(b_child)); | 
 |   EXPECT_EQ("null", GetOriginFromRenderer(c_child)); | 
 |   EXPECT_EQ("null", GetOriginFromRenderer(d_child)); | 
 | } | 
 |  | 
 | // Check that sandbox flags are not inherited before they take effect.  Create | 
 | // a child frame, update its sandbox flags but don't navigate the frame, and | 
 | // ensure that a new cross-site grandchild frame doesn't inherit the new flags | 
 | // (which shouldn't have taken effect). | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        SandboxFlagsNotInheritedBeforeNavigation) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(a)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   // It is safe to obtain the root frame tree node here, as it doesn't change. | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // Set sandbox flags for child frame. | 
 |   EXPECT_TRUE(ExecJs( | 
 |       root, "document.querySelector('iframe').sandbox = 'allow-scripts';")); | 
 |  | 
 |   // These flags should be pending but not take effect, since there's been no | 
 |   // navigation. | 
 |   network::mojom::WebSandboxFlags expected_flags = | 
 |       network::mojom::WebSandboxFlags::kAll & | 
 |       ~network::mojom::WebSandboxFlags::kScripts & | 
 |       ~network::mojom::WebSandboxFlags::kAutomaticFeatures; | 
 |   FrameTreeNode* child = root->child_at(0); | 
 |   EXPECT_EQ(expected_flags, child->pending_frame_policy().sandbox_flags); | 
 |   EXPECT_EQ(network::mojom::WebSandboxFlags::kNone, | 
 |             child->effective_frame_policy().sandbox_flags); | 
 |  | 
 |   // Add a new grandchild frame and navigate it cross-site. | 
 |   RenderFrameHostCreatedObserver frame_observer(shell()->web_contents(), 1); | 
 |   EXPECT_TRUE(ExecJs( | 
 |       child, "document.body.appendChild(document.createElement('iframe'));")); | 
 |   frame_observer.Wait(); | 
 |  | 
 |   FrameTreeNode* grandchild = child->child_at(0); | 
 |   GURL frame_url(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |   TestFrameNavigationObserver navigation_observer(grandchild); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(grandchild, frame_url)); | 
 |   navigation_observer.Wait(); | 
 |  | 
 |   // Since the update flags haven't yet taken effect in its parent, this | 
 |   // grandchild frame should not be sandboxed. | 
 |   EXPECT_EQ(network::mojom::WebSandboxFlags::kNone, | 
 |             grandchild->pending_frame_policy().sandbox_flags); | 
 |   EXPECT_EQ(network::mojom::WebSandboxFlags::kNone, | 
 |             grandchild->effective_frame_policy().sandbox_flags); | 
 |  | 
 |   // Check that the grandchild frame isn't sandboxed on the renderer side.  If | 
 |   // sandboxed, its origin would be unique ("null"). | 
 |   EXPECT_EQ(GetExpectedOrigin("b.com"), GetOriginFromRenderer(grandchild)); | 
 | } | 
 |  | 
 | // Verify that popups opened from sandboxed frames inherit sandbox flags from | 
 | // their opener, and that they keep these inherited flags after being navigated | 
 | // cross-site.  See https://crbug.com/483584. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        NewPopupInheritsSandboxFlagsFromOpener) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(a)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   // It is safe to obtain the root frame tree node here, as it doesn't change. | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // Set sandbox flags for child frame. | 
 |   EXPECT_TRUE(ExecJs(root, | 
 |                      "document.querySelector('iframe').sandbox = " | 
 |                      "    'allow-scripts allow-popups';")); | 
 |  | 
 |   // Calculate expected flags.  Note that "allow-scripts" resets both | 
 |   // network::mojom::WebSandboxFlags::Scripts and | 
 |   // network::mojom::WebSandboxFlags::AutomaticFeatures bits per | 
 |   // blink::parseSandboxPolicy(). | 
 |   network::mojom::WebSandboxFlags expected_flags = | 
 |       network::mojom::WebSandboxFlags::kAll & | 
 |       ~network::mojom::WebSandboxFlags::kAutomaticFeatures & | 
 |       ~network::mojom::WebSandboxFlags::kPopups & | 
 |       ~network::mojom::WebSandboxFlags::kScripts & | 
 |       ~network::mojom::WebSandboxFlags::kTopNavigationToCustomProtocols; | 
 |   EXPECT_EQ(expected_flags, | 
 |             root->child_at(0)->pending_frame_policy().sandbox_flags); | 
 |  | 
 |   // Navigate child frame cross-site.  The sandbox flags should take effect. | 
 |   GURL frame_url(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |   TestFrameNavigationObserver frame_observer(root->child_at(0)); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(0), frame_url)); | 
 |   frame_observer.Wait(); | 
 |   EXPECT_EQ(expected_flags, | 
 |             root->child_at(0)->effective_frame_policy().sandbox_flags); | 
 |  | 
 |   // Verify that they've also taken effect on the renderer side.  The sandboxed | 
 |   // frame's origin should be opaque. | 
 |   EXPECT_EQ("null", GetOriginFromRenderer(root->child_at(0))); | 
 |   const url::SchemeHostPort tuple_b(frame_url); | 
 |   const url::Origin sandbox_origin_b = root->child_at(0)->current_origin(); | 
 |   EXPECT_TRUE(sandbox_origin_b.opaque()); | 
 |   EXPECT_EQ(tuple_b, sandbox_origin_b.GetTupleOrPrecursorTupleIfOpaque()); | 
 |  | 
 |   // Open a popup named "foo" from the sandboxed child frame. | 
 |   Shell* foo_shell = | 
 |       OpenPopup(root->child_at(0), GURL(url::kAboutBlankURL), "foo"); | 
 |   EXPECT_TRUE(foo_shell); | 
 |  | 
 |   FrameTreeNode* foo_root = | 
 |       static_cast<WebContentsImpl*>(foo_shell->web_contents()) | 
 |           ->GetPrimaryFrameTree() | 
 |           .root(); | 
 |  | 
 |   // Check that the sandbox flags for new popup are correct in the browser | 
 |   // process. | 
 |   EXPECT_EQ(expected_flags, foo_root->effective_frame_policy().sandbox_flags); | 
 |  | 
 |   // The popup's origin should be opaque, since it's sandboxed, but cross-origin | 
 |   // from its opener. | 
 |   EXPECT_EQ("null", GetOriginFromRenderer(foo_root)); | 
 |   url::Origin sandbox_origin_b2 = foo_root->current_origin(); | 
 |   EXPECT_NE(sandbox_origin_b2, sandbox_origin_b); | 
 |   EXPECT_TRUE(sandbox_origin_b2.opaque()); | 
 |   EXPECT_EQ(tuple_b, sandbox_origin_b2.GetTupleOrPrecursorTupleIfOpaque()); | 
 |  | 
 |   // Navigate the popup cross-site.  This should be placed in an opaque origin | 
 |   // derived from c.com, and retain the inherited sandbox flags. | 
 |   GURL c_url(embedded_test_server()->GetURL("c.com", "/title1.html")); | 
 |   const url::SchemeHostPort tuple_c(c_url); | 
 |   { | 
 |     TestFrameNavigationObserver popup_observer(foo_root); | 
 |     EXPECT_TRUE(ExecJs(foo_root, JsReplace("location.href = $1", c_url))); | 
 |     popup_observer.Wait(); | 
 |     EXPECT_EQ(c_url, foo_shell->web_contents()->GetLastCommittedURL()); | 
 |   } | 
 |  | 
 |   // Confirm that the popup is still sandboxed, both on browser and renderer | 
 |   // sides. | 
 |   EXPECT_EQ(expected_flags, foo_root->effective_frame_policy().sandbox_flags); | 
 |   EXPECT_EQ("null", GetOriginFromRenderer(foo_root)); | 
 |   const url::Origin sandbox_origin_c = foo_root->current_origin(); | 
 |   EXPECT_NE(sandbox_origin_b, sandbox_origin_c); | 
 |   EXPECT_TRUE(sandbox_origin_c.opaque()); | 
 |   EXPECT_EQ(tuple_c, sandbox_origin_c.GetTupleOrPrecursorTupleIfOpaque()); | 
 |  | 
 |   // Navigate the popup back to b.com.  The popup should perform a | 
 |   // remote-to-local navigation in the b.com process, and keep an opaque | 
 |   // origin and the inherited sandbox flags. | 
 |   { | 
 |     TestFrameNavigationObserver popup_observer(foo_root); | 
 |     EXPECT_TRUE(ExecJs(foo_root, JsReplace("location.href = $1", frame_url))); | 
 |     popup_observer.Wait(); | 
 |     EXPECT_EQ(frame_url, foo_shell->web_contents()->GetLastCommittedURL()); | 
 |   } | 
 |  | 
 |   // Confirm that the popup is still sandboxed, both on browser and renderer | 
 |   // sides. This navigation should result in a new opaque origin derived | 
 |   // from b.com. | 
 |   EXPECT_EQ(expected_flags, foo_root->effective_frame_policy().sandbox_flags); | 
 |   EXPECT_EQ("null", GetOriginFromRenderer(foo_root)); | 
 |   url::Origin sandbox_origin_b3 = foo_root->current_origin(); | 
 |   EXPECT_TRUE(sandbox_origin_b3.opaque()); | 
 |   EXPECT_EQ(tuple_b, sandbox_origin_b3.GetTupleOrPrecursorTupleIfOpaque()); | 
 |   EXPECT_NE(sandbox_origin_b, sandbox_origin_b3); | 
 |   EXPECT_NE(sandbox_origin_b2, sandbox_origin_b3); | 
 | } | 
 |  | 
 | // Verify that popups opened from frames sandboxed with the | 
 | // "allow-popups-to-escape-sandbox" directive do *not* inherit sandbox flags | 
 | // from their opener. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        OpenUnsandboxedPopupFromSandboxedFrame) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(a)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   // It is safe to obtain the root frame tree node here, as it doesn't change. | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // Set sandbox flags for child frame, specifying that popups opened from it | 
 |   // should not be sandboxed. | 
 |   EXPECT_TRUE(ExecJs( | 
 |       root, | 
 |       "document.querySelector('iframe').sandbox = " | 
 |       "    'allow-scripts allow-popups allow-popups-to-escape-sandbox';")); | 
 |  | 
 |   // Set expected flags for the child frame.  Note that "allow-scripts" resets | 
 |   // both network::mojom::WebSandboxFlags::Scripts and | 
 |   // network::mojom::WebSandboxFlags::AutomaticFeatures bits per | 
 |   // blink::parseSandboxPolicy(). | 
 |   network::mojom::WebSandboxFlags expected_flags = | 
 |       network::mojom::WebSandboxFlags::kAll & | 
 |       ~network::mojom::WebSandboxFlags::kScripts & | 
 |       ~network::mojom::WebSandboxFlags::kAutomaticFeatures & | 
 |       ~network::mojom::WebSandboxFlags::kPopups & | 
 |       ~network::mojom::WebSandboxFlags::kTopNavigationToCustomProtocols & | 
 |       ~network::mojom::WebSandboxFlags::kPropagatesToAuxiliaryBrowsingContexts; | 
 |   EXPECT_EQ(expected_flags, | 
 |             root->child_at(0)->pending_frame_policy().sandbox_flags); | 
 |  | 
 |   // Navigate child frame cross-site.  The sandbox flags should take effect. | 
 |   GURL frame_url(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |   TestFrameNavigationObserver frame_observer(root->child_at(0)); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(0), frame_url)); | 
 |   frame_observer.Wait(); | 
 |   EXPECT_EQ(expected_flags, | 
 |             root->child_at(0)->effective_frame_policy().sandbox_flags); | 
 |  | 
 |   // Open a cross-site popup named "foo" from the child frame. | 
 |   GURL b_url(embedded_test_server()->GetURL("c.com", "/title1.html")); | 
 |   Shell* foo_shell = OpenPopup(root->child_at(0), b_url, "foo"); | 
 |   EXPECT_TRUE(foo_shell); | 
 |  | 
 |   FrameTreeNode* foo_root = | 
 |       static_cast<WebContentsImpl*>(foo_shell->web_contents()) | 
 |           ->GetPrimaryFrameTree() | 
 |           .root(); | 
 |  | 
 |   // Check that the sandbox flags for new popup are correct in the browser | 
 |   // process.  They should not have been inherited. | 
 |   EXPECT_EQ(network::mojom::WebSandboxFlags::kNone, | 
 |             foo_root->effective_frame_policy().sandbox_flags); | 
 |   // Check that the sandbox flags for the popup document are correct in the | 
 |   // browser process: None are set from the frame, none are set from the | 
 |   // navigation. | 
 |   EXPECT_EQ(network::mojom::WebSandboxFlags::kNone, | 
 |             foo_root->current_frame_host()->active_sandbox_flags()); | 
 |  | 
 |   // The popup's origin should match |b_url|, since it's not sandboxed. | 
 |   EXPECT_EQ(url::Origin::Create(b_url).Serialize(), | 
 |             EvalJs(foo_root, "self.origin;")); | 
 | } | 
 |  | 
 | // Verify that popup frames opened from sandboxed documents with the | 
 | // "allow-popups-to-escape-sandbox" directive do *not* inherit sandbox flags AND | 
 | // that local scheme documents do *not* inherit flags from the opener/initiator. | 
 | IN_PROC_BROWSER_TEST_P( | 
 |     SitePerProcessBrowserTest, | 
 |     OpenSandboxedDocumentInUnsandboxedPopupFromSandboxedFrame) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(a)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   // It is safe to obtain the root frame tree node here, as it doesn't change. | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // Set sandbox flags for child frame, specifying that popups opened from it | 
 |   // should not be sandboxed. | 
 |   EXPECT_TRUE(ExecJs( | 
 |       root, | 
 |       "document.querySelector('iframe').sandbox = " | 
 |       "    'allow-scripts allow-popups allow-popups-to-escape-sandbox';")); | 
 |  | 
 |   // Set expected flags for the child frame.  Note that "allow-scripts" resets | 
 |   // both network::mojom::WebSandboxFlags::Scripts and | 
 |   // network::mojom::WebSandboxFlags::AutomaticFeatures bits per | 
 |   // blink::parseSandboxPolicy(). | 
 |   network::mojom::WebSandboxFlags expected_flags = | 
 |       network::mojom::WebSandboxFlags::kAll & | 
 |       ~network::mojom::WebSandboxFlags::kScripts & | 
 |       ~network::mojom::WebSandboxFlags::kAutomaticFeatures & | 
 |       ~network::mojom::WebSandboxFlags::kPopups & | 
 |       ~network::mojom::WebSandboxFlags::kTopNavigationToCustomProtocols & | 
 |       ~network::mojom::WebSandboxFlags::kPropagatesToAuxiliaryBrowsingContexts; | 
 |   EXPECT_EQ(expected_flags, | 
 |             root->child_at(0)->pending_frame_policy().sandbox_flags); | 
 |  | 
 |   // Navigate child frame cross-site.  The sandbox flags should take effect. | 
 |   GURL frame_url(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |   TestFrameNavigationObserver frame_observer(root->child_at(0)); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(0), frame_url)); | 
 |   frame_observer.Wait(); | 
 |   EXPECT_EQ(expected_flags, | 
 |             root->child_at(0)->effective_frame_policy().sandbox_flags); | 
 |  | 
 |   // Open a popup named "foo" from the child frame on about:blank. | 
 |   GURL foo_url("about:blank"); | 
 |   Shell* foo_shell = OpenPopup(root->child_at(0), foo_url, "foo"); | 
 |   EXPECT_TRUE(foo_shell); | 
 |  | 
 |   FrameTreeNode* foo_root = | 
 |       static_cast<WebContentsImpl*>(foo_shell->web_contents()) | 
 |           ->GetPrimaryFrameTree() | 
 |           .root(); | 
 |  | 
 |   // Check that the sandbox flags for new popup frame are correct in the browser | 
 |   // process. They should not have been inherited. | 
 |   EXPECT_EQ(network::mojom::WebSandboxFlags::kNone, | 
 |             foo_root->effective_frame_policy().sandbox_flags); | 
 |   // Check that the sandbox flags for the popup document are correct in the | 
 |   // browser process. They should not have been inherited (for about:blank). | 
 |   EXPECT_EQ(network::mojom::WebSandboxFlags::kNone, | 
 |             foo_root->current_frame_host()->active_sandbox_flags()); | 
 | } | 
 |  | 
 | // Verify that popup frames opened from sandboxed documents with the | 
 | // "allow-popups-to-escape-sandbox" directive do *not* inherit sandbox flags AND | 
 | // that local scheme documents do inherit CSP sandbox flags from the | 
 | // opener/initiator. | 
 | IN_PROC_BROWSER_TEST_P( | 
 |     SitePerProcessBrowserTest, | 
 |     OpenSandboxedDocumentInUnsandboxedPopupFromCSPSandboxedDocument) { | 
 |   GURL main_url = embedded_test_server()->GetURL( | 
 |       "a.test", | 
 |       "/set-header?" | 
 |       "Content-Security-Policy: sandbox " | 
 |       "allow-scripts allow-popups allow-popups-to-escape-sandbox"); | 
 |  | 
 |   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()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // Set expected flags for the child frame.  Note that "allow-scripts" resets | 
 |   // both network::mojom::WebSandboxFlags::Scripts and | 
 |   // network::mojom::WebSandboxFlags::AutomaticFeatures bits per | 
 |   // blink::parseSandboxPolicy(). | 
 |   network::mojom::WebSandboxFlags expected_flags = | 
 |       network::mojom::WebSandboxFlags::kAll & | 
 |       ~network::mojom::WebSandboxFlags::kScripts & | 
 |       ~network::mojom::WebSandboxFlags::kAutomaticFeatures & | 
 |       ~network::mojom::WebSandboxFlags::kPopups & | 
 |       ~network::mojom::WebSandboxFlags::kTopNavigationToCustomProtocols & | 
 |       ~network::mojom::WebSandboxFlags::kPropagatesToAuxiliaryBrowsingContexts; | 
 |  | 
 |   EXPECT_EQ(expected_flags, root->current_frame_host()->active_sandbox_flags()); | 
 |  | 
 |   // Open a popup named "foo" from the child frame on about:blank. | 
 |   GURL foo_url("about:blank"); | 
 |   Shell* foo_shell = OpenPopup(root, foo_url, "foo"); | 
 |   EXPECT_TRUE(foo_shell); | 
 |  | 
 |   FrameTreeNode* foo_root = | 
 |       static_cast<WebContentsImpl*>(foo_shell->web_contents()) | 
 |           ->GetPrimaryFrameTree() | 
 |           .root(); | 
 |  | 
 |   // Check that the sandbox flags for new popup frame are correct in the browser | 
 |   // process. They should not have been inherited. | 
 |   EXPECT_EQ(network::mojom::WebSandboxFlags::kNone, | 
 |             foo_root->effective_frame_policy().sandbox_flags); | 
 |   // Check that the sandbox flags for the popup document are correct in the | 
 |   // browser process. They should have been inherited. | 
 |   EXPECT_EQ(expected_flags, | 
 |             foo_root->current_frame_host()->active_sandbox_flags()); | 
 | } | 
 |  | 
 | // Test that subresources with certificate errors get reported to the | 
 | // browser. That is, if https://example.test frames https://a.com which | 
 | // loads an image with certificate errors, the browser should be | 
 | // notified about the subresource with certificate errors and downgrade | 
 | // the UI appropriately. | 
 | // TODO(crbug.com/40705650): Flaky. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessIgnoreCertErrorsBrowserTest, | 
 |                        DISABLED_SubresourceWithCertificateErrors) { | 
 |   net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS); | 
 |   https_server.ServeFilesFromSourceDirectory(GetTestDataFilePath()); | 
 |   SetupCrossSiteRedirector(&https_server); | 
 |   ASSERT_TRUE(https_server.Start()); | 
 |  | 
 |   GURL url(https_server.GetURL( | 
 |       "example.test", | 
 |       "/mixed-content/non-redundant-cert-error-in-iframe.html")); | 
 |  | 
 |   // The update of the security state can happen asynchronously after the | 
 |   // navigation finished, see https://crbug.com/1105145. | 
 |   VisibleSecurityStateObserver displayed_content_with_cert_errors_observer( | 
 |       shell()->web_contents(), | 
 |       base::BindRepeating([](WebContents* web_contents) { | 
 |         NavigationEntry* entry = | 
 |             web_contents->GetController().GetLastCommittedEntry(); | 
 |         // The image that the iframe loaded had certificate errors also, so | 
 |         // the page should be marked as having displayed subresources with | 
 |         // cert errors. | 
 |         return entry && (entry->GetSSL().content_status & | 
 |                          SSLStatus::DISPLAYED_CONTENT_WITH_CERT_ERRORS) != 0; | 
 |       })); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url)); | 
 |   displayed_content_with_cert_errors_observer.Wait(); | 
 |  | 
 |   NavigationEntry* entry = | 
 |       shell()->web_contents()->GetController().GetLastCommittedEntry(); | 
 |   ASSERT_TRUE(entry); | 
 |  | 
 |   // The main page was loaded with certificate errors. | 
 |   EXPECT_TRUE(net::IsCertStatusError(entry->GetSSL().cert_status)); | 
 | } | 
 |  | 
 | // Test setting a cross-origin iframe to display: none. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, CrossSiteIframeDisplayNone) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   RenderWidgetHost* root_render_widget_host = | 
 |       root->current_frame_host()->GetRenderWidgetHost(); | 
 |  | 
 |   // Set the iframe to display: none. | 
 |   EXPECT_TRUE(ExecJs( | 
 |       shell(), "document.querySelector('iframe').style.display = 'none'")); | 
 |  | 
 |   // Waits until pending frames are done. | 
 |   std::unique_ptr<MainThreadFrameObserver> observer( | 
 |       new MainThreadFrameObserver(root_render_widget_host)); | 
 |   observer->Wait(); | 
 |  | 
 |   // Force the renderer to generate a new frame. | 
 |   EXPECT_TRUE(ExecJs(shell(), "document.body.style.background = 'black'")); | 
 |  | 
 |   // Waits for the next frame. | 
 |   observer->Wait(); | 
 | } | 
 |  | 
 | // Test that a cross-origin iframe can be blocked by X-Frame-Options and CSP | 
 | // frame-ancestors. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        CrossSiteIframeBlockedByXFrameOptionsOrCSP) { | 
 |   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()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // Add a load event handler for the iframe element. | 
 |   EXPECT_TRUE(ExecJs(shell(), | 
 |                      "document.querySelector('iframe').onload = " | 
 |                      "    function() { document.title = 'loaded'; };")); | 
 |  | 
 |   // The blocked url reported in the console message should only contain the | 
 |   // origin, in order to avoid sensitive data being leaked to the parent frame. | 
 |   // | 
 |   // TODO(crbug.com/40053800): We should not leak any information at all | 
 |   // to the parent frame. Instead, we should send a message directly to Devtools | 
 |   // (without passing through a renderer): that can also contain more | 
 |   // information (like the full blocked url). | 
 |   GURL reported_blocked_url = embedded_test_server()->GetURL("b.com", "/"); | 
 |   const struct { | 
 |     const char* url; | 
 |     bool use_error_page; | 
 |     std::string expected_console_message; | 
 |   } kTestCases[] = { | 
 |       {"/frame-ancestors-none.html", false, | 
 |        "Refused to frame '" + reported_blocked_url.spec() + | 
 |            "' because an ancestor violates the following Content Security " | 
 |            "Policy directive: \"frame-ancestors 'none'\".\n"}, | 
 |       {"/x-frame-options-deny.html", true, | 
 |        "Refused to display '" + reported_blocked_url.spec() + | 
 |            "' in a frame because it set 'X-Frame-Options' to 'deny'."}, | 
 |   }; | 
 |  | 
 |   for (const auto& test : kTestCases) { | 
 |     GURL blocked_url = embedded_test_server()->GetURL("b.com", test.url); | 
 |     EXPECT_TRUE(ExecJs(shell(), "document.title = 'not loaded';")); | 
 |     std::u16string expected_title(u"loaded"); | 
 |     TitleWatcher title_watcher(shell()->web_contents(), expected_title); | 
 |  | 
 |     WebContentsConsoleObserver console_observer(shell()->web_contents()); | 
 |     console_observer.SetPattern("Refused to*"); | 
 |  | 
 |     // Navigate the subframe to a blocked URL. | 
 |     TestNavigationObserver load_observer(shell()->web_contents()); | 
 |     EXPECT_TRUE(ExecJs(shell(), | 
 |                        JsReplace("frames[0].location.href = $1", blocked_url))); | 
 |     load_observer.Wait(); | 
 |  | 
 |     // The blocked frame's origin should become unique. | 
 |     const url::Origin child_origin = | 
 |         root->child_at(0)->current_frame_host()->GetLastCommittedOrigin(); | 
 |     EXPECT_TRUE(child_origin.opaque()); | 
 |     EXPECT_EQ(url::Origin::Create(blocked_url.DeprecatedGetOriginAsURL()) | 
 |                   .GetTupleOrPrecursorTupleIfOpaque(), | 
 |               child_origin.GetTupleOrPrecursorTupleIfOpaque()); | 
 |  | 
 |     // X-Frame-Options and CSP frame-ancestors behave differently. XFO commits | 
 |     // an error page, while CSP commits a "data:," URL. | 
 |     // TODO(crbug.com/41405925): Use an error page for both. | 
 |     EXPECT_FALSE(load_observer.last_navigation_succeeded()); | 
 |     EXPECT_EQ(net::ERR_BLOCKED_BY_RESPONSE, | 
 |               load_observer.last_net_error_code()); | 
 |     EXPECT_EQ(root->child_at(0)->current_frame_host()->GetLastCommittedURL(), | 
 |               blocked_url); | 
 |     EXPECT_EQ("Error", EvalJs(root->child_at(0), "document.title")); | 
 |  | 
 |     // The blocked frame should still fire a load event in its parent's process. | 
 |     EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle()); | 
 |  | 
 |     EXPECT_EQ(console_observer.GetMessageAt(0u), test.expected_console_message); | 
 |  | 
 |     // Check that the current RenderFrameHost has stopped loading. | 
 |     EXPECT_FALSE(root->child_at(0)->current_frame_host()->is_loading()); | 
 |  | 
 |     // Navigate the subframe to another cross-origin page and ensure that this | 
 |     // navigation succeeds.  Use a renderer-initiated navigation to test the | 
 |     // transfer logic, which used to have some issues with this. | 
 |     GURL c_url(embedded_test_server()->GetURL("c.com", "/title1.html")); | 
 |     EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "child-0", c_url)); | 
 |     EXPECT_EQ(c_url, root->child_at(0)->current_url()); | 
 |  | 
 |     // When a page gets blocked due to XFO or CSP, it is sandboxed with the | 
 |     // SandboxOrigin flag (i.e., its origin is set to be unique) to ensure that | 
 |     // the blocked page is seen as cross-origin. However, those flags shouldn't | 
 |     // affect future navigations for a frame. Verify this for the above | 
 |     // navigation. | 
 |     EXPECT_EQ(c_url.DeprecatedGetOriginAsURL().spec(), | 
 |               root->child_at(0)->current_origin().Serialize() + "/"); | 
 |     EXPECT_EQ(network::mojom::WebSandboxFlags::kNone, | 
 |               root->child_at(0)->effective_frame_policy().sandbox_flags); | 
 |   } | 
 | } | 
 |  | 
 | // Test that a cross-origin frame's navigation can be blocked by CSP frame-src. | 
 | // In this version of a test, CSP comes from HTTP headers. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        CrossSiteIframeBlockedByParentCSPFromHeaders) { | 
 |   GURL main_url( | 
 |       embedded_test_server()->GetURL("a.com", "/frame-src-self-and-b.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // Sanity-check that the test page has the expected shape for testing. | 
 |   GURL old_subframe_url( | 
 |       embedded_test_server()->GetURL("b.com", "/title2.html")); | 
 |   EXPECT_FALSE(root->child_at(0)->HasSameOrigin(*root)); | 
 |   EXPECT_EQ(old_subframe_url, root->child_at(0)->current_url()); | 
 |   const std::vector<network::mojom::ContentSecurityPolicyPtr>& root_csp = | 
 |       root->current_frame_host() | 
 |           ->policy_container_host() | 
 |           ->policies() | 
 |           .content_security_policies; | 
 |   EXPECT_EQ(1u, root_csp.size()); | 
 |   EXPECT_EQ("frame-src 'self' http://b.com:*", | 
 |             root_csp[0]->header->header_value); | 
 |  | 
 |   // Monitor subframe's load events via main frame's title. | 
 |   EXPECT_TRUE(ExecJs(shell(), | 
 |                      "document.querySelector('iframe').onload = " | 
 |                      "    function() { document.title = 'loaded'; };")); | 
 |   EXPECT_TRUE(ExecJs(shell(), "document.title = 'not loaded';")); | 
 |   std::u16string expected_title(u"loaded"); | 
 |   TitleWatcher title_watcher(shell()->web_contents(), expected_title); | 
 |  | 
 |   // Try to navigate the subframe to a blocked URL. | 
 |   TestNavigationObserver load_observer(shell()->web_contents()); | 
 |   GURL blocked_url = embedded_test_server()->GetURL("c.com", "/title3.html"); | 
 |   EXPECT_TRUE(ExecJs(root->child_at(0), | 
 |                      JsReplace("window.location.href = $1", blocked_url))); | 
 |  | 
 |   // The blocked frame should still fire a load event in its parent's process. | 
 |   EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle()); | 
 |  | 
 |   // Check that the current RenderFrameHost has stopped loading. | 
 |   if (root->child_at(0)->current_frame_host()->is_loading()) | 
 |     load_observer.Wait(); | 
 |  | 
 |   // The last successful url shouldn't be the blocked url. | 
 |   EXPECT_NE(blocked_url, | 
 |             root->child_at(0)->current_frame_host()->last_successful_url()); | 
 |  | 
 |   // The blocked frame should go to an error page. Errors currently commit | 
 |   // with the URL of the blocked page. | 
 |   EXPECT_EQ(blocked_url, root->child_at(0)->current_url()); | 
 |  | 
 |   // The page should get the title of an error page (i.e "Error") and not the | 
 |   // title of the blocked page. | 
 |   EXPECT_EQ("Error", EvalJs(root->child_at(0), "document.title")); | 
 |  | 
 |   // Navigate to a URL without CSP. | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("a.com", "/title1.html"))); | 
 | } | 
 |  | 
 | // Test that a cross-origin frame's navigation can be blocked by CSP frame-src. | 
 | // In this version of a test, CSP comes from a <meta> element added after the | 
 | // page has already loaded. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        CrossSiteIframeBlockedByParentCSPFromMeta) { | 
 |   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()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // Navigate the subframe to a location we will disallow in the future. | 
 |   GURL old_subframe_url( | 
 |       embedded_test_server()->GetURL("b.com", "/title2.html")); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(0), old_subframe_url)); | 
 |  | 
 |   // Add frame-src CSP via a new <meta> element. | 
 |   EXPECT_TRUE( | 
 |       ExecJs(shell(), | 
 |              "var meta = document.createElement('meta');" | 
 |              "meta.httpEquiv = 'Content-Security-Policy';" | 
 |              "meta.content = 'frame-src https://a.com:*';" | 
 |              "document.getElementsByTagName('head')[0].appendChild(meta);")); | 
 |  | 
 |   // Sanity-check that the test page has the expected shape for testing. | 
 |   // (the CSP should not have an effect on the already loaded frames). | 
 |   EXPECT_FALSE(root->child_at(0)->HasSameOrigin(*root)); | 
 |   EXPECT_EQ(old_subframe_url, root->child_at(0)->current_url()); | 
 |   const std::vector<network::mojom::ContentSecurityPolicyPtr>& root_csp = | 
 |       root->current_frame_host() | 
 |           ->policy_container_host() | 
 |           ->policies() | 
 |           .content_security_policies; | 
 |   EXPECT_EQ(1u, root_csp.size()); | 
 |   EXPECT_EQ("frame-src https://a.com:*", root_csp[0]->header->header_value); | 
 |  | 
 |   // Monitor subframe's load events via main frame's title. | 
 |   EXPECT_TRUE(ExecJs(shell(), | 
 |                      "document.querySelector('iframe').onload = " | 
 |                      "    function() { document.title = 'loaded'; };")); | 
 |   EXPECT_TRUE(ExecJs(shell(), "document.title = 'not loaded';")); | 
 |   std::u16string expected_title(u"loaded"); | 
 |   TitleWatcher title_watcher(shell()->web_contents(), expected_title); | 
 |  | 
 |   // Try to navigate the subframe to a blocked URL. | 
 |   TestNavigationObserver load_observer2(shell()->web_contents()); | 
 |   GURL blocked_url = embedded_test_server()->GetURL("c.com", "/title3.html"); | 
 |   EXPECT_TRUE(ExecJs(root->child_at(0), | 
 |                      JsReplace("window.location.href = $1;", blocked_url))); | 
 |  | 
 |   // The blocked frame should still fire a load event in its parent's process. | 
 |   EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle()); | 
 |  | 
 |   // Check that the current RenderFrameHost has stopped loading. | 
 |   if (root->child_at(0)->current_frame_host()->is_loading()) | 
 |     load_observer2.Wait(); | 
 |  | 
 |   // The last successful url shouldn't be the blocked url. | 
 |   EXPECT_NE(blocked_url, | 
 |             root->child_at(0)->current_frame_host()->last_successful_url()); | 
 |  | 
 |   // The blocked frame should go to an error page. Errors currently commit | 
 |   // with the URL of the blocked page. | 
 |   EXPECT_EQ(blocked_url, root->child_at(0)->current_url()); | 
 |  | 
 |   // The page should get the title of an error page (i.e "Error") and not the | 
 |   // title of the blocked page. | 
 |   EXPECT_EQ("Error", EvalJs(root->child_at(0), "document.title")); | 
 | } | 
 |  | 
 | // Test that a cross-origin frame's navigation can be blocked by CSP frame-src. | 
 | // In this version of a test, CSP is inherited by srcdoc iframe from a parent | 
 | // that declared CSP via HTTP headers.  Cross-origin frame navigating to a | 
 | // blocked location is a child of the srcdoc iframe. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        CrossSiteIframeBlockedByCSPInheritedBySrcDocParent) { | 
 |   GURL main_url( | 
 |       embedded_test_server()->GetURL("a.com", "/frame-src-self-and-b.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   FrameTreeNode* srcdoc_frame = root->child_at(1); | 
 |   EXPECT_TRUE(srcdoc_frame != nullptr); | 
 |   FrameTreeNode* navigating_frame = srcdoc_frame->child_at(0); | 
 |   EXPECT_TRUE(navigating_frame != nullptr); | 
 |  | 
 |   // Sanity-check that the test page has the expected shape for testing. | 
 |   // (the CSP should not have an effect on the already loaded frames). | 
 |   GURL old_subframe_url( | 
 |       embedded_test_server()->GetURL("b.com", "/title2.html")); | 
 |   EXPECT_TRUE(srcdoc_frame->HasSameOrigin(*root)); | 
 |   EXPECT_FALSE(srcdoc_frame->HasSameOrigin(*navigating_frame)); | 
 |   EXPECT_EQ(old_subframe_url, navigating_frame->current_url()); | 
 |   const std::vector<network::mojom::ContentSecurityPolicyPtr>& srcdoc_csp = | 
 |       srcdoc_frame->current_frame_host() | 
 |           ->policy_container_host() | 
 |           ->policies() | 
 |           .content_security_policies; | 
 |   EXPECT_EQ(1u, srcdoc_csp.size()); | 
 |   EXPECT_EQ("frame-src 'self' http://b.com:*", | 
 |             srcdoc_csp[0]->header->header_value); | 
 |  | 
 |   // Monitor navigating_frame's load events via srcdoc_frame posting | 
 |   // a message to the parent frame. | 
 |   EXPECT_TRUE(ExecJs(root, | 
 |                      "window.addEventListener('message', function(event) {" | 
 |                      "  document.title = event.data;" | 
 |                      "});")); | 
 |   EXPECT_TRUE( | 
 |       ExecJs(srcdoc_frame, | 
 |              "document.querySelector('iframe').onload = " | 
 |              "    function() { window.top.postMessage('loaded', '*'); };")); | 
 |   EXPECT_TRUE(ExecJs(shell(), "document.title = 'not loaded';")); | 
 |   std::u16string expected_title(u"loaded"); | 
 |   TitleWatcher title_watcher(shell()->web_contents(), expected_title); | 
 |  | 
 |   // Try to navigate the subframe to a blocked URL. | 
 |   TestNavigationObserver load_observer2(shell()->web_contents()); | 
 |   GURL blocked_url = embedded_test_server()->GetURL("c.com", "/title3.html"); | 
 |   EXPECT_TRUE(ExecJs(navigating_frame, | 
 |                      JsReplace("window.location.href = $1;", blocked_url))); | 
 |  | 
 |   // The blocked frame should still fire a load event in its parent's process. | 
 |   EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle()); | 
 |  | 
 |   // Check that the current RenderFrameHost has stopped loading. | 
 |   if (navigating_frame->current_frame_host()->is_loading()) | 
 |     load_observer2.Wait(); | 
 |  | 
 |   // The last successful url shouldn't be the blocked url. | 
 |   EXPECT_NE(blocked_url, | 
 |             navigating_frame->current_frame_host()->last_successful_url()); | 
 |  | 
 |   // The blocked frame should go to an error page. Errors currently commit | 
 |   // with the URL of the blocked page. | 
 |   EXPECT_EQ(blocked_url, navigating_frame->current_url()); | 
 |  | 
 |   // The page should get the title of an error page (i.e "Error") and not the | 
 |   // title of the blocked page. | 
 |   EXPECT_EQ("Error", EvalJs(navigating_frame, "document.title")); | 
 |  | 
 |   // Navigate the subframe to a URL without CSP. | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer( | 
 |       srcdoc_frame, embedded_test_server()->GetURL("a.com", "/title1.html"))); | 
 |  | 
 |   // Verify that the frame's CSP got correctly reset to an empty set. | 
 |   EXPECT_EQ(0u, srcdoc_frame->current_frame_host() | 
 |                     ->policy_container_host() | 
 |                     ->policies() | 
 |                     .content_security_policies.size()); | 
 | } | 
 |  | 
 | // Tests that the state of the RenderViewHost is properly reset when the main | 
 | // frame is navigated to the same SiteInstance as one of its child frames. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        NavigateMainFrameToChildSite) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   SimulateEndOfPaintHoldingOnPrimaryMainFrame(web_contents()); | 
 |  | 
 |   WebContentsImpl* contents = web_contents(); | 
 |   FrameTreeNode* root = contents->GetPrimaryFrameTree().root(); | 
 |   EXPECT_EQ(1U, root->child_count()); | 
 |  | 
 |   // The test expect the BrowsingInstance to be kept across cross-site main | 
 |   // frame navigations. ProactivelySwapBrowsingInstance will provide a new one. | 
 |   // To prevent this, a popup is opened. | 
 |   if (CanCrossSiteNavigationsProactivelySwapBrowsingInstances()) { | 
 |     GURL popup_url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |     EXPECT_TRUE(OpenPopup(root, popup_url, "foo")); | 
 |   } | 
 |  | 
 |   // Ensure the RenderViewHost for the SiteInstance of the child is considered | 
 |   // inactive. | 
 |   RenderViewHostImpl* rvh = contents->GetPrimaryFrameTree() | 
 |                                 .GetRenderViewHost(root->child_at(0) | 
 |                                                        ->current_frame_host() | 
 |                                                        ->GetSiteInstance() | 
 |                                                        ->group()) | 
 |                                 .get(); | 
 |   EXPECT_FALSE(rvh->is_active()); | 
 |  | 
 |   // Have the child frame navigate its parent to its SiteInstance. | 
 |   GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |   auto script = JsReplace("parent.location = $1", b_url); | 
 |  | 
 |   // Ensure the child has received a user gesture, so that it has permission | 
 |   // to framebust. | 
 |   SimulateMouseClick( | 
 |       root->child_at(0)->current_frame_host()->GetRenderWidgetHost(), 1, 1); | 
 |   TestFrameNavigationObserver frame_observer(root); | 
 |   EXPECT_TRUE(ExecJs(root->child_at(0), script)); | 
 |   frame_observer.Wait(); | 
 |   EXPECT_EQ(b_url, root->current_url()); | 
 |  | 
 |   // Verify that the same RenderViewHost is preserved and that it is now active. | 
 |   EXPECT_EQ(rvh, contents->GetPrimaryFrameTree().GetRenderViewHost( | 
 |                      root->current_frame_host()->GetSiteInstance()->group())); | 
 |   EXPECT_TRUE(rvh->is_active()); | 
 | } | 
 |  | 
 | // Test for https://crbug.com/568836.  From an A-embed-B page, navigate the | 
 | // subframe from B to A.  This cleans up the process for B, but the test delays | 
 | // the browser side from killing the B process right away.  This allows the | 
 | // B process to process the subframe's detached event and the disconnect | 
 | // of the blink::WebView's blink::mojom::PageBroadcast mojo channel. In the bug, | 
 | // the latter crashed while detaching the subframe's LocalFrame (triggered as | 
 | // part of closing the `blink::WebView`), because this tried to access the | 
 | // subframe's WebFrameWidget (from RenderFrameImpl::didChangeSelection), which | 
 | // had already been cleared by the former. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        CloseSubframeWidgetAndViewOnProcessExit) { | 
 |   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()) | 
 |                             ->GetPrimaryFrameTree() | 
 |                             .root(); | 
 |  | 
 |   // "Select all" in the subframe.  The bug only happens if there's a selection | 
 |   // change, which triggers the path through didChangeSelection. | 
 |   root->child_at(0) | 
 |       ->current_frame_host() | 
 |       ->GetRenderWidgetHost() | 
 |       ->GetFrameWidgetInputHandler() | 
 |       ->SelectAll(); | 
 |  | 
 |   // Prevent b.com process from terminating right away once the subframe | 
 |   // navigates away from b.com below.  This is necessary so that the renderer | 
 |   // process has time to process the closings of RenderWidget and | 
 |   // `blink::WebView`, which is where the original bug was triggered. | 
 |   // Incrementing the worker ref count will cause | 
 |   // RenderProcessHostImpl::Cleanup to forego process termination. | 
 |   RenderProcessHostImpl* subframe_process = static_cast<RenderProcessHostImpl*>( | 
 |       root->child_at(0)->current_frame_host()->GetProcess()); | 
 |   subframe_process->IncrementWorkerRefCount(); | 
 |  | 
 |   // Navigate the subframe away from b.com.  Since this is the last active | 
 |   // frame in the b.com process, this causes the RenderWidget and | 
 |   // `blink::WebView` to be closed. | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer( | 
 |       root->child_at(0), | 
 |       embedded_test_server()->GetURL("a.com", "/title1.html"))); | 
 |  | 
 |   // Release the process. | 
 |   RenderProcessHostWatcher process_shutdown_observer( | 
 |       subframe_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); | 
 |   subframe_process->DecrementWorkerRefCount(); | 
 |   process_shutdown_observer.Wait(); | 
 | } | 
 |  | 
 | // Tests that an input event targeted to a out-of-process iframe correctly | 
 | // triggers a user interaction notification for WebContentsObservers. | 
 | // This is used for browser features such as download request limiting and | 
 | // launching multiple external protocol handlers, which can block repeated | 
 | // actions from a page when a user is not interacting with the page. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        UserInteractionForChildFrameTest) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   SimulateEndOfPaintHoldingOnPrimaryMainFrame(web_contents()); | 
 |  | 
 |   UserInteractionObserver observer(web_contents()); | 
 |  | 
 |   // Target an event to the child frame's RenderWidgetHostView. | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   SimulateMouseClick( | 
 |       root->child_at(0)->current_frame_host()->GetRenderWidgetHost(), 5, 5); | 
 |  | 
 |   EXPECT_TRUE(observer.WasUserInteractionReceived()); | 
 |  | 
 |   // Target an event to the main frame. | 
 |   observer.Reset(); | 
 |   SimulateMouseClick(root->current_frame_host()->GetRenderWidgetHost(), 1, 1); | 
 |  | 
 |   EXPECT_TRUE(observer.WasUserInteractionReceived()); | 
 | } | 
 |  | 
 | // Ensures that navigating to data: URLs present in session history will | 
 | // correctly commit the navigation in the same process as the one used for the | 
 | // original navigation. See https://crbug.com/606996. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        NavigateSubframeToDataUrlInSessionHistory) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b,b)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   EXPECT_EQ(2U, root->child_count()); | 
 |   EXPECT_EQ( | 
 |       " Site A ------------ proxies for B\n" | 
 |       "   |--Site B ------- proxies for A\n" | 
 |       "   +--Site B ------- proxies for A\n" | 
 |       "Where A = http://a.com/\n" | 
 |       "      B = http://b.com/", | 
 |       DepictFrameTree(root)); | 
 |  | 
 |   TestNavigationObserver observer(shell()->web_contents()); | 
 |   FrameTreeNode* child = root->child_at(0); | 
 |  | 
 |   // Navigate iframe to a data URL, which will commit in a new SiteInstance. | 
 |   GURL data_url("data:text/html,dataurl"); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(child, data_url)); | 
 |   EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |   EXPECT_EQ(data_url, observer.last_navigation_url()); | 
 |   scoped_refptr<SiteInstanceImpl> orig_site_instance = | 
 |       child->current_frame_host()->GetSiteInstance(); | 
 |   EXPECT_NE(root->current_frame_host()->GetSiteInstance(), orig_site_instance); | 
 |  | 
 |   // Navigate it to another cross-site url. | 
 |   GURL cross_site_url(embedded_test_server()->GetURL("c.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(child, cross_site_url)); | 
 |   EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |   EXPECT_EQ(cross_site_url, observer.last_navigation_url()); | 
 |   EXPECT_EQ(3, web_contents()->GetController().GetEntryCount()); | 
 |   EXPECT_NE(orig_site_instance, child->current_frame_host()->GetSiteInstance()); | 
 |  | 
 |   // Go back and ensure the data: URL committed in the same SiteInstance as the | 
 |   // original navigation. | 
 |   EXPECT_TRUE(web_contents()->GetController().CanGoBack()); | 
 |   TestFrameNavigationObserver frame_observer(child); | 
 |   web_contents()->GetController().GoBack(); | 
 |   frame_observer.WaitForCommit(); | 
 |   EXPECT_EQ(orig_site_instance, child->current_frame_host()->GetSiteInstance()); | 
 | } | 
 |  | 
 | // The site URL for a data: URL is the scheme + the serialized nonce from the | 
 | // origin. This means that two data: URLs with the same body will have different | 
 | // site URLs. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, DataUrlsHaveUniqueSiteURLs) { | 
 |   // Force process reuse for same-site URLs, to test whether identical data: | 
 |   // URLs share a process with each other. | 
 |   RenderProcessHost::SetMaxRendererProcessCount(1); | 
 |  | 
 |   // Load a main frame data: URL. | 
 |   GURL data_url("data:text/html,dataurl"); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), data_url)); | 
 |  | 
 |   // Open another tab, then load the same data: URL in that tab. We need to | 
 |   // first navigate the new tab to a different page, a_url. | 
 |   // Shell::CreateNewWindow opens a new tab to about:blank, then loads the URL | 
 |   // passed in. Since the about:blank is in a new tab, it gets a new process, | 
 |   // and the passed-in URL keeps using that about:blank process. By navigating | 
 |   // from a_url to the data: URL, we exercise the flow that will reuse the | 
 |   // existing data: URL process, if possible. | 
 |   GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   ShellAddedObserver new_shell_observer; | 
 |   Shell* new_shell = | 
 |       Shell::CreateNewWindow(static_cast<NavigationControllerImpl&>( | 
 |                                  shell()->web_contents()->GetController()) | 
 |                                  .GetBrowserContext(), | 
 |                              a_url, nullptr, gfx::Size()); | 
 |   auto* new_contents = static_cast<WebContentsImpl*>(new_shell->web_contents()); | 
 |   EXPECT_TRUE(WaitForLoadStop(new_contents)); | 
 |   EXPECT_TRUE(NavigateToURL(new_shell, data_url)); | 
 |  | 
 |   auto* main_frame = shell()->web_contents()->GetPrimaryMainFrame(); | 
 |   auto* new_frame = new_shell->web_contents()->GetPrimaryMainFrame(); | 
 |   GURL main_url = main_frame->GetSiteInstance()->GetSiteURL(); | 
 |   GURL new_url = new_frame->GetSiteInstance()->GetSiteURL(); | 
 |   EXPECT_NE(new_frame->GetSiteInstance(), main_frame->GetSiteInstance()); | 
 |  | 
 |   // The site URL is the data scheme followed by a serialized nonce, which is | 
 |   // unique for every data: URL instance. | 
 |   EXPECT_NE(main_url, new_url); | 
 |   EXPECT_TRUE(main_url.SchemeIs(url::kDataScheme)); | 
 |   EXPECT_EQ(new_url.GetContent().length(), | 
 |             base::UnguessableToken::Create().ToString().length()); | 
 |   EXPECT_NE(new_frame->GetProcess(), main_frame->GetProcess()); | 
 | } | 
 |  | 
 | // Ensures that subframes navigated to data: URLs start in a process based on | 
 | // their creator, but end up in unique processes after a restore (since | 
 | // SiteInstance relationships are not preserved on restore, until | 
 | // https://crbug.com/14987 is fixed).  This is better than restoring into the | 
 | // parent process, per https://crbug.com/863069. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        SubframeDataUrlsAfterRestore) { | 
 |   // We must use a page that has iframes in the HTML here, unlike | 
 |   // cross_site_iframe_factory.html which loads them dynamically.  In the latter | 
 |   // case, Chrome will not restore subframe URLs from history, which is needed | 
 |   // for this test. | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/frame_tree/page_with_two_iframes.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   EXPECT_EQ(2U, root->child_count()); | 
 |   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://bar.com/\n" | 
 |       "      C = http://baz.com/", | 
 |       DepictFrameTree(root)); | 
 |  | 
 |   FrameTreeNode* child_0 = root->child_at(0); | 
 |   FrameTreeNode* child_1 = root->child_at(1); | 
 |   scoped_refptr<SiteInstanceImpl> child_site_instance_0 = | 
 |       child_0->current_frame_host()->GetSiteInstance(); | 
 |   scoped_refptr<SiteInstanceImpl> child_site_instance_1 = | 
 |       child_1->current_frame_host()->GetSiteInstance(); | 
 |  | 
 |   // Navigate the iframes to data URLs via renderer initiated navigations, which | 
 |   // will commit in the existing SiteInstanceGroups. | 
 |   TestNavigationObserver observer(shell()->web_contents()); | 
 |   GURL data_url_0("data:text/html,dataurl_0"); | 
 |   { | 
 |     TestFrameNavigationObserver commit_observer(child_0); | 
 |     EXPECT_TRUE(ExecJs(child_0, JsReplace("location.href = $1", data_url_0))); | 
 |     commit_observer.WaitForCommit(); | 
 |   } | 
 |   EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |   EXPECT_EQ(data_url_0, observer.last_navigation_url()); | 
 |  | 
 |   if (ShouldCreateSiteInstanceForDataUrls()) { | 
 |     EXPECT_NE(child_site_instance_0, | 
 |               child_0->current_frame_host()->GetSiteInstance()); | 
 |     EXPECT_EQ(child_site_instance_0->group(), | 
 |               child_0->current_frame_host()->GetSiteInstance()->group()); | 
 |   } else { | 
 |     EXPECT_EQ(child_site_instance_0, | 
 |               child_0->current_frame_host()->GetSiteInstance()); | 
 |   } | 
 |  | 
 |   GURL data_url_1("data:text/html,dataurl_1"); | 
 |   { | 
 |     TestFrameNavigationObserver commit_observer(child_1); | 
 |     EXPECT_TRUE(ExecJs(child_1, JsReplace("location.href = $1", data_url_1))); | 
 |     commit_observer.WaitForCommit(); | 
 |   } | 
 |   EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |   EXPECT_EQ(data_url_1, observer.last_navigation_url()); | 
 |  | 
 |   if (ShouldCreateSiteInstanceForDataUrls()) { | 
 |     EXPECT_NE(child_site_instance_1, | 
 |               child_1->current_frame_host()->GetSiteInstance()); | 
 |     EXPECT_EQ(child_site_instance_1->group(), | 
 |               child_1->current_frame_host()->GetSiteInstance()->group()); | 
 |   } else { | 
 |     EXPECT_EQ(child_site_instance_1, | 
 |               child_1->current_frame_host()->GetSiteInstance()); | 
 |   } | 
 |  | 
 |   // Grab the NavigationEntry and clone its PageState into a new entry for | 
 |   // restoring into a new tab. | 
 |   NavigationControllerImpl& controller = static_cast<NavigationControllerImpl&>( | 
 |       shell()->web_contents()->GetController()); | 
 |   NavigationEntryImpl* entry = controller.GetLastCommittedEntry(); | 
 |   std::unique_ptr<NavigationEntryImpl> restored_entry = | 
 |       NavigationEntryImpl::FromNavigationEntry( | 
 |           NavigationController::CreateNavigationEntry( | 
 |               main_url, Referrer(), /* initiator_origin= */ std::nullopt, | 
 |               /* initiator_base_url= */ std::nullopt, | 
 |               ui::PAGE_TRANSITION_RELOAD, false, std::string(), | 
 |               controller.GetBrowserContext(), | 
 |               nullptr /* blob_url_loader_factory */)); | 
 |   EXPECT_EQ(0U, restored_entry->root_node()->children.size()); | 
 |   NavigationEntryRestoreContextImpl context; | 
 |   restored_entry->SetPageState(entry->GetPageState(), &context); | 
 |   ASSERT_EQ(2U, restored_entry->root_node()->children.size()); | 
 |  | 
 |   // Restore the NavigationEntry into a new tab and check that the data URLs are | 
 |   // not loaded into the parent's SiteInstance. | 
 |   std::vector<std::unique_ptr<NavigationEntry>> entries; | 
 |   entries.push_back(std::move(restored_entry)); | 
 |   Shell* new_shell = Shell::CreateNewWindow(controller.GetBrowserContext(), | 
 |                                             GURL(), nullptr, gfx::Size()); | 
 |   FrameTreeNode* new_root = | 
 |       static_cast<WebContentsImpl*>(new_shell->web_contents()) | 
 |           ->GetPrimaryFrameTree() | 
 |           .root(); | 
 |   NavigationControllerImpl& new_controller = | 
 |       static_cast<NavigationControllerImpl&>( | 
 |           new_shell->web_contents()->GetController()); | 
 |   new_controller.Restore(entries.size() - 1, RestoreType::kRestored, &entries); | 
 |   ASSERT_EQ(0u, entries.size()); | 
 |   { | 
 |     TestNavigationObserver restore_observer(new_shell->web_contents()); | 
 |     new_controller.LoadIfNecessary(); | 
 |     restore_observer.Wait(); | 
 |   } | 
 |   ASSERT_EQ(2U, new_root->child_count()); | 
 |   EXPECT_EQ(main_url, new_root->current_url()); | 
 |   EXPECT_EQ("data", new_root->child_at(0)->current_url().scheme()); | 
 |   EXPECT_EQ("data", new_root->child_at(1)->current_url().scheme()); | 
 |  | 
 |   EXPECT_NE(new_root->current_frame_host()->GetSiteInstance(), | 
 |             new_root->child_at(0)->current_frame_host()->GetSiteInstance()); | 
 |   EXPECT_NE(new_root->current_frame_host()->GetSiteInstance(), | 
 |             new_root->child_at(1)->current_frame_host()->GetSiteInstance()); | 
 |   EXPECT_NE(new_root->child_at(0)->current_frame_host()->GetSiteInstance(), | 
 |             new_root->child_at(1)->current_frame_host()->GetSiteInstance()); | 
 | } | 
 |  | 
 | // Similar to SubframeDataUrlsAfterRestore. Ensures that about:blank frames | 
 | // are not put into their parent process after restore if their initiator origin | 
 | // is different from the parent. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        SubframeBlankUrlsAfterRestore) { | 
 |   // We must use a page that has iframes in the HTML here, unlike | 
 |   // cross_site_iframe_factory.html which loads them dynamically.  In the latter | 
 |   // case, Chrome will not restore subframe URLs from history, which is needed | 
 |   // for this test. | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/frame_tree/page_with_two_iframes.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   EXPECT_EQ(2U, root->child_count()); | 
 |   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://bar.com/\n" | 
 |       "      C = http://baz.com/", | 
 |       DepictFrameTree(root)); | 
 |  | 
 |   FrameTreeNode* child_0 = root->child_at(0); | 
 |   FrameTreeNode* child_1 = root->child_at(1); | 
 |   scoped_refptr<SiteInstanceImpl> child_site_instance_0 = | 
 |       child_0->current_frame_host()->GetSiteInstance(); | 
 |   scoped_refptr<SiteInstanceImpl> child_site_instance_1 = | 
 |       child_1->current_frame_host()->GetSiteInstance(); | 
 |  | 
 |   // Navigate the iframes to about:blank URLs via renderer initiated | 
 |   // navigations, which will commit in the existing SiteInstances. | 
 |   TestNavigationObserver observer(shell()->web_contents()); | 
 |   GURL blank_url("about:blank"); | 
 |   { | 
 |     TestFrameNavigationObserver commit_observer(child_0); | 
 |     EXPECT_TRUE(ExecJs(child_0, JsReplace("location.href = $1", blank_url))); | 
 |     commit_observer.WaitForCommit(); | 
 |   } | 
 |   EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |   EXPECT_EQ(blank_url, observer.last_navigation_url()); | 
 |   EXPECT_EQ(child_site_instance_0, | 
 |             child_0->current_frame_host()->GetSiteInstance()); | 
 |  | 
 |   GURL blank_url_ref("about:blank#1"); | 
 |   { | 
 |     TestFrameNavigationObserver commit_observer(child_1); | 
 |     EXPECT_TRUE( | 
 |         ExecJs(child_1, JsReplace("location.href = $1", blank_url_ref))); | 
 |     commit_observer.WaitForCommit(); | 
 |   } | 
 |   EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |   EXPECT_EQ(blank_url_ref, observer.last_navigation_url()); | 
 |   EXPECT_EQ(child_site_instance_1, | 
 |             child_1->current_frame_host()->GetSiteInstance()); | 
 |  | 
 |   // Grab the NavigationEntry and clone its PageState into a new entry for | 
 |   // restoring into a new tab. | 
 |   NavigationControllerImpl& controller = static_cast<NavigationControllerImpl&>( | 
 |       shell()->web_contents()->GetController()); | 
 |   NavigationEntryImpl* entry = controller.GetLastCommittedEntry(); | 
 |   std::unique_ptr<NavigationEntryImpl> restored_entry = | 
 |       NavigationEntryImpl::FromNavigationEntry( | 
 |           NavigationController::CreateNavigationEntry( | 
 |               main_url, Referrer(), /* initiator_origin= */ std::nullopt, | 
 |               /* initiator_base_url= */ std::nullopt, | 
 |               ui::PAGE_TRANSITION_RELOAD, false, std::string(), | 
 |               controller.GetBrowserContext(), | 
 |               nullptr /* blob_url_loader_factory */)); | 
 |   EXPECT_EQ(0U, restored_entry->root_node()->children.size()); | 
 |   NavigationEntryRestoreContextImpl context; | 
 |   restored_entry->SetPageState(entry->GetPageState(), &context); | 
 |   ASSERT_EQ(2U, restored_entry->root_node()->children.size()); | 
 |  | 
 |   // Restore the NavigationEntry into a new tab and check that the about:blank | 
 |   // URLs are not loaded into the parent's SiteInstance. | 
 |   std::vector<std::unique_ptr<NavigationEntry>> entries; | 
 |   entries.push_back(std::move(restored_entry)); | 
 |   Shell* new_shell = Shell::CreateNewWindow(controller.GetBrowserContext(), | 
 |                                             GURL(), nullptr, gfx::Size()); | 
 |   FrameTreeNode* new_root = | 
 |       static_cast<WebContentsImpl*>(new_shell->web_contents()) | 
 |           ->GetPrimaryFrameTree() | 
 |           .root(); | 
 |   NavigationControllerImpl& new_controller = | 
 |       static_cast<NavigationControllerImpl&>( | 
 |           new_shell->web_contents()->GetController()); | 
 |   new_controller.Restore(entries.size() - 1, RestoreType::kRestored, &entries); | 
 |   ASSERT_EQ(0u, entries.size()); | 
 |   { | 
 |     TestNavigationObserver restore_observer(new_shell->web_contents()); | 
 |     new_controller.LoadIfNecessary(); | 
 |     restore_observer.Wait(); | 
 |   } | 
 |   ASSERT_EQ(2U, new_root->child_count()); | 
 |   EXPECT_EQ(main_url, new_root->current_url()); | 
 |   auto* new_child_0 = new_root->child_at(0); | 
 |   auto* new_child_1 = new_root->child_at(1); | 
 |   EXPECT_TRUE(new_child_0->current_url().IsAboutBlank()); | 
 |   EXPECT_TRUE(new_child_1->current_url().IsAboutBlank()); | 
 |  | 
 |   // Restored frames should retain the origin from before restoring. | 
 |   EXPECT_EQ(new_root->current_frame_host()->GetLastCommittedOrigin(), | 
 |             root->current_frame_host()->GetLastCommittedOrigin()); | 
 |   EXPECT_EQ(new_child_0->current_frame_host() | 
 |                 ->GetLastCommittedOrigin() | 
 |                 .GetTupleOrPrecursorTupleIfOpaque(), | 
 |             child_0->current_frame_host() | 
 |                 ->GetLastCommittedOrigin() | 
 |                 .GetTupleOrPrecursorTupleIfOpaque()); | 
 |   EXPECT_EQ(new_child_1->current_frame_host() | 
 |                 ->GetLastCommittedOrigin() | 
 |                 .GetTupleOrPrecursorTupleIfOpaque(), | 
 |             child_1->current_frame_host() | 
 |                 ->GetLastCommittedOrigin() | 
 |                 .GetTupleOrPrecursorTupleIfOpaque()); | 
 |   EXPECT_NE(child_0->current_frame_host() | 
 |                 ->GetLastCommittedOrigin() | 
 |                 .GetTupleOrPrecursorTupleIfOpaque(), | 
 |             child_1->current_frame_host() | 
 |                 ->GetLastCommittedOrigin() | 
 |                 .GetTupleOrPrecursorTupleIfOpaque()); | 
 |  | 
 |   // Origin for child frames should match the navigation initiators. | 
 |   EXPECT_EQ( | 
 |       new_root->current_frame_host()->GetLastCommittedOrigin().Serialize(), | 
 |       GetOriginFromRenderer(new_root)); | 
 |   EXPECT_EQ(GetExpectedOrigin("bar.com"), GetOriginFromRenderer(new_child_0)); | 
 |   EXPECT_EQ(GetExpectedOrigin("baz.com"), GetOriginFromRenderer(new_child_1)); | 
 |  | 
 |   // Since the origin for the frames are different, they all end up in different | 
 |   // SiteInstances. | 
 |   EXPECT_NE(new_root->current_frame_host()->GetSiteInstance(), | 
 |             new_child_0->current_frame_host()->GetSiteInstance()); | 
 |   EXPECT_NE(new_root->current_frame_host()->GetSiteInstance(), | 
 |             new_child_1->current_frame_host()->GetSiteInstance()); | 
 |   EXPECT_NE(new_child_0->current_frame_host()->GetSiteInstance(), | 
 |             new_child_1->current_frame_host()->GetSiteInstance()); | 
 | } | 
 |  | 
 | // Similar to SubframeBlankUrlsAfterRestore, but ensures that about:srcdoc ends | 
 | // up in its parent's process after restore, since that's where its content | 
 | // comes from. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        SubframeSrcdocUrlAfterRestore) { | 
 |   // Load a page that uses iframe srcdoc. | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/frame_tree/page_with_srcdoc_frame.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   EXPECT_EQ(1U, root->child_count()); | 
 |   FrameTreeNode* child = root->child_at(0); | 
 |   scoped_refptr<SiteInstanceImpl> child_site_instance = | 
 |       child->current_frame_host()->GetSiteInstance(); | 
 |   EXPECT_EQ(child_site_instance, root->current_frame_host()->GetSiteInstance()); | 
 |  | 
 |   // Grab the NavigationEntry and clone its PageState into a new entry for | 
 |   // restoring into a new tab. | 
 |   NavigationControllerImpl& controller = static_cast<NavigationControllerImpl&>( | 
 |       shell()->web_contents()->GetController()); | 
 |   NavigationEntryImpl* entry = controller.GetLastCommittedEntry(); | 
 |   std::unique_ptr<NavigationEntryImpl> restored_entry = | 
 |       NavigationEntryImpl::FromNavigationEntry( | 
 |           NavigationController::CreateNavigationEntry( | 
 |               main_url, Referrer(), /* initiator_origin= */ std::nullopt, | 
 |               /* initiator_base_url= */ std::nullopt, | 
 |               ui::PAGE_TRANSITION_RELOAD, false, std::string(), | 
 |               controller.GetBrowserContext(), | 
 |               nullptr /* blob_url_loader_factory */)); | 
 |   EXPECT_EQ(0U, restored_entry->root_node()->children.size()); | 
 |   NavigationEntryRestoreContextImpl context; | 
 |   restored_entry->SetPageState(entry->GetPageState(), &context); | 
 |   ASSERT_EQ(1U, restored_entry->root_node()->children.size()); | 
 |  | 
 |   // Restore the NavigationEntry into a new tab and check that the srcdoc URLs | 
 |   // are still loaded into the parent's SiteInstance. | 
 |   std::vector<std::unique_ptr<NavigationEntry>> entries; | 
 |   entries.push_back(std::move(restored_entry)); | 
 |   Shell* new_shell = Shell::CreateNewWindow(controller.GetBrowserContext(), | 
 |                                             GURL(), nullptr, gfx::Size()); | 
 |   FrameTreeNode* new_root = | 
 |       static_cast<WebContentsImpl*>(new_shell->web_contents()) | 
 |           ->GetPrimaryFrameTree() | 
 |           .root(); | 
 |   NavigationControllerImpl& new_controller = | 
 |       static_cast<NavigationControllerImpl&>( | 
 |           new_shell->web_contents()->GetController()); | 
 |   new_controller.Restore(entries.size() - 1, RestoreType::kRestored, &entries); | 
 |   ASSERT_EQ(0u, entries.size()); | 
 |   { | 
 |     TestNavigationObserver restore_observer(new_shell->web_contents()); | 
 |     new_controller.LoadIfNecessary(); | 
 |     restore_observer.Wait(); | 
 |   } | 
 |   ASSERT_EQ(1U, new_root->child_count()); | 
 |   EXPECT_EQ(main_url, new_root->current_url()); | 
 |   EXPECT_TRUE(new_root->child_at(0)->current_url().IsAboutSrcdoc()); | 
 |   // Not only should the srcdoc inherit its base url from its initiator, but it | 
 |   // should also be properly restored from the session history. | 
 |   EXPECT_EQ( | 
 |       main_url, | 
 |       GURL(EvalJs(new_root->child_at(0), "document.baseURI").ExtractString())); | 
 |  | 
 |   EXPECT_EQ(new_root->current_frame_host()->GetSiteInstance(), | 
 |             new_root->child_at(0)->current_frame_host()->GetSiteInstance()); | 
 | } | 
 |  | 
 | // Ensures that navigating to about:blank URLs present in session history will | 
 | // correctly commit the navigation in the same process as the one used for | 
 | // the original navigation. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        NavigateSubframeToAboutBlankInSessionHistory) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b,b)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   EXPECT_EQ(2U, root->child_count()); | 
 |   EXPECT_EQ( | 
 |       " Site A ------------ proxies for B\n" | 
 |       "   |--Site B ------- proxies for A\n" | 
 |       "   +--Site B ------- proxies for A\n" | 
 |       "Where A = http://a.com/\n" | 
 |       "      B = http://b.com/", | 
 |       DepictFrameTree(root)); | 
 |  | 
 |   TestNavigationObserver observer(shell()->web_contents()); | 
 |   FrameTreeNode* child = root->child_at(0); | 
 |  | 
 |   // Navigate iframe to about:blank, which will commit in a new SiteInstance. | 
 |   GURL about_blank_url("about:blank"); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(child, about_blank_url)); | 
 |   EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |   EXPECT_EQ(about_blank_url, observer.last_navigation_url()); | 
 |   scoped_refptr<SiteInstanceImpl> orig_site_instance = | 
 |     child->current_frame_host()->GetSiteInstance(); | 
 |   EXPECT_NE(root->current_frame_host()->GetSiteInstance(), orig_site_instance); | 
 |  | 
 |   // Navigate it to another cross-site url. | 
 |   GURL cross_site_url(embedded_test_server()->GetURL("c.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(child, cross_site_url)); | 
 |   EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |   EXPECT_EQ(cross_site_url, observer.last_navigation_url()); | 
 |   EXPECT_EQ(3, web_contents()->GetController().GetEntryCount()); | 
 |   EXPECT_NE(orig_site_instance, child->current_frame_host()->GetSiteInstance()); | 
 |  | 
 |   // Go back and ensure the about:blank URL committed in the same SiteInstance | 
 |   // as the original navigation. | 
 |   EXPECT_TRUE(web_contents()->GetController().CanGoBack()); | 
 |   TestFrameNavigationObserver frame_observer(child); | 
 |   web_contents()->GetController().GoBack(); | 
 |   frame_observer.WaitForCommit(); | 
 |   EXPECT_EQ(orig_site_instance, child->current_frame_host()->GetSiteInstance()); | 
 | } | 
 |  | 
 | // Intercepts calls to LocalMainFrame's ShowCreatedWindow mojo method, and | 
 | // invokes the provided callback. | 
 | class ShowCreatedWindowInterceptor | 
 |     : public blink::mojom::LocalMainFrameHostInterceptorForTesting { | 
 |  public: | 
 |   // The caller has to guarantee that `render_frame_host` lives at least as long | 
 |   // as ShowCreatedWindowInterceptor. | 
 |   ShowCreatedWindowInterceptor( | 
 |       RenderFrameHostImpl* render_frame_host, | 
 |       base::OnceCallback<void(int32_t pending_widget_routing_id)> test_callback) | 
 |       : render_frame_host_(render_frame_host), | 
 |         test_callback_(std::move(test_callback)), | 
 |         swapped_impl_( | 
 |             render_frame_host_->local_main_frame_host_receiver_for_testing(), | 
 |             this) {} | 
 |  | 
 |   ~ShowCreatedWindowInterceptor() override = default; | 
 |  | 
 |   blink::mojom::LocalMainFrameHost* GetForwardingInterface() override { | 
 |     return swapped_impl_.old_impl(); | 
 |   } | 
 |  | 
 |   void ShowCreatedWindow(const blink::LocalFrameToken& opener_frame_token, | 
 |                          WindowOpenDisposition disposition, | 
 |                          blink::mojom::WindowFeaturesPtr window_features, | 
 |                          bool user_gesture, | 
 |                          ShowCreatedWindowCallback callback) override { | 
 |     show_callback_ = std::move(callback); | 
 |     opener_frame_token_ = opener_frame_token; | 
 |     user_gesture_ = user_gesture; | 
 |     window_features_ = std::move(window_features); | 
 |     disposition_ = disposition; | 
 |     std::move(test_callback_) | 
 |         .Run(render_frame_host_->GetRenderWidgetHost()->GetRoutingID()); | 
 |   } | 
 |  | 
 |   void ResumeShowCreatedWindow() { | 
 |     GetForwardingInterface()->ShowCreatedWindow( | 
 |         opener_frame_token_, disposition_, std::move(window_features_), | 
 |         user_gesture_, std::move(show_callback_)); | 
 |   } | 
 |  | 
 |  private: | 
 |   raw_ptr<RenderFrameHostImpl> render_frame_host_; | 
 |   base::OnceCallback<void(int32_t pending_widget_routing_id)> test_callback_; | 
 |   ShowCreatedWindowCallback show_callback_; | 
 |   blink::LocalFrameToken opener_frame_token_; | 
 |   blink::mojom::WindowFeaturesPtr window_features_; | 
 |   bool user_gesture_ = false; | 
 |   WindowOpenDisposition disposition_; | 
 |   mojo::test::ScopedSwapImplForTesting<blink::mojom::LocalMainFrameHost> | 
 |       swapped_impl_; | 
 | }; | 
 |  | 
 | // Listens for the source WebContents opening the new WebContents then attaches | 
 | // a show listener to the widget. | 
 | class NewWindowCreatedObserver : public WebContentsObserver { | 
 |  public: | 
 |   NewWindowCreatedObserver( | 
 |       WebContents* web_contents, | 
 |       base::OnceCallback<void(int32_t pending_widget_routing_id)> test_callback) | 
 |       : WebContentsObserver(web_contents), | 
 |         test_callback_(std::move(test_callback)) {} | 
 |  | 
 |   // WebContentsObserver overrides. | 
 |   void DidOpenRequestedURL(WebContents* new_contents, | 
 |                            RenderFrameHost* source_render_frame_host, | 
 |                            const GURL& url, | 
 |                            const Referrer& referrer, | 
 |                            WindowOpenDisposition disposition, | 
 |                            ui::PageTransition transition, | 
 |                            bool started_from_context_menu, | 
 |                            bool renderer_initiated) override { | 
 |     show_interceptor_ = std::make_unique<ShowCreatedWindowInterceptor>( | 
 |         static_cast<RenderFrameHostImpl*>(new_contents->GetPrimaryMainFrame()), | 
 |         std::move(test_callback_)); | 
 |  | 
 |     // Stop observing now. | 
 |     Observe(nullptr); | 
 |   } | 
 |  | 
 |   void ResumeShowCreatedWindow() { | 
 |     show_interceptor_->ResumeShowCreatedWindow(); | 
 |   } | 
 |  | 
 |  private: | 
 |   std::unique_ptr<ShowCreatedWindowInterceptor> show_interceptor_; | 
 |   base::OnceCallback<void(int32_t pending_widget_routing_id)> test_callback_; | 
 | }; | 
 |  | 
 | // Test for https://crbug.com/612276.  Simultaneously open two new windows from | 
 | // two subframes in different processes, where each subframe process's next | 
 | // routing ID is the same.  Make sure that both windows are created properly. | 
 | // | 
 | // Each new window requires two IPCs to first create it (handled by | 
 | // CreateNewWindow) and then show it (ShowCreatedWindow).  In the bug, both | 
 | // CreateNewWindow calls arrived before the ShowCreatedWindow calls, resulting | 
 | // in the two pending windows colliding in the pending WebContents map, which | 
 | // used to be keyed only by routing_id. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        TwoSubframesCreatePopupsSimultaneously) { | 
 |   // This test covers a scenario which can only happen when creating and showing | 
 |   // a new window is split between to IPC's and some conflicting update happens | 
 |   // between them. kCombineNewWindowIPCs eliminates this possibility by | 
 |   // combining the function of the two IPC's into one. | 
 |   if (base::FeatureList::IsEnabled(blink::features::kCombineNewWindowIPCs)) { | 
 |     return; | 
 |   } | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b,c)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   FrameTreeNode* child1 = root->child_at(0); | 
 |   FrameTreeNode* child2 = root->child_at(1); | 
 |   RenderFrameHostImpl* frame1 = child1->current_frame_host(); | 
 |   RenderFrameHostImpl* frame2 = child2->current_frame_host(); | 
 |   RenderProcessHost* process1 = frame1->GetProcess(); | 
 |   RenderProcessHost* process2 = frame2->GetProcess(); | 
 |  | 
 |   // Call window.open simultaneously in both subframes to create two popups. | 
 |   // Wait for and then drop both ShowCreatedWindow messages.  This will ensure | 
 |   // that both CreateNewWindow calls happen before either ShowCreatedWindow | 
 |   // call. | 
 |   base::RunLoop run_loop1; | 
 |   int32_t routing_id1; | 
 |   NewWindowCreatedObserver interceptor1( | 
 |       web_contents(), | 
 |       base::BindLambdaForTesting([&](int32_t pending_widget_routing_id) { | 
 |         routing_id1 = pending_widget_routing_id; | 
 |         run_loop1.Quit(); | 
 |       })); | 
 |   EXPECT_TRUE(ExecJs(child1, "window.open();")); | 
 |   run_loop1.Run(); | 
 |  | 
 |   base::RunLoop run_loop2; | 
 |   int32_t routing_id2; | 
 |   NewWindowCreatedObserver interceptor2( | 
 |       web_contents(), | 
 |       base::BindLambdaForTesting([&](int32_t pending_widget_routing_id) { | 
 |         routing_id2 = pending_widget_routing_id; | 
 |         run_loop2.Quit(); | 
 |       })); | 
 |  | 
 |   EXPECT_TRUE(ExecJs(child2, "window.open();")); | 
 |   run_loop2.Run(); | 
 |  | 
 |   // At this point, we should have two pending WebContents. | 
 |   EXPECT_TRUE(base::Contains( | 
 |       web_contents()->pending_contents_, | 
 |       GlobalRoutingID(process1->GetDeprecatedID(), routing_id1))); | 
 |   EXPECT_TRUE(base::Contains( | 
 |       web_contents()->pending_contents_, | 
 |       GlobalRoutingID(process2->GetDeprecatedID(), routing_id2))); | 
 |  | 
 |   // Both subframes were set up in the same way, so the next routing ID for the | 
 |   // new popup windows should match up (this led to the collision in the | 
 |   // pending contents map in the original bug). | 
 |   EXPECT_EQ(routing_id1, routing_id2); | 
 |  | 
 |   // Now, resuming processing the show messages. | 
 |   interceptor1.ResumeShowCreatedWindow(); | 
 |   interceptor2.ResumeShowCreatedWindow(); | 
 |  | 
 |   // Verify that both shells were properly created. | 
 |   EXPECT_EQ(3u, Shell::windows().size()); | 
 | } | 
 |  | 
 | // Intercepts calls to PopupWidgetHost's RequestClosePopup mojo method, and | 
 | // discards it. The caller has to guarantee that `render_widget_host` lives at | 
 | // least as long as RequestCloseWidgetInterceptor. | 
 | class RequestCloseWidgetInterceptor | 
 |     : public blink::mojom::PopupWidgetHostInterceptorForTesting { | 
 |  public: | 
 |   explicit RequestCloseWidgetInterceptor( | 
 |       RenderWidgetHostImpl* render_widget_host) | 
 |       : swapped_impl_( | 
 |             render_widget_host->popup_widget_host_receiver_for_testing(), | 
 |             this) {} | 
 |  | 
 |   ~RequestCloseWidgetInterceptor() override = default; | 
 |  | 
 |   blink::mojom::PopupWidgetHost* GetForwardingInterface() override { | 
 |     return swapped_impl_.old_impl(); | 
 |   } | 
 |  | 
 |   void RequestClosePopup() override {} | 
 |  | 
 |  private: | 
 |   mojo::test::ScopedSwapImplForTesting<blink::mojom::PopupWidgetHost> | 
 |       swapped_impl_; | 
 | }; | 
 |  | 
 | // Intercepts calls to PopupWidgetHost's ShowPopup mojo method, and | 
 | // invokes the provided callback. The caller has to guarantee that | 
 | // `render_widget_host` lives at least as long as | 
 | // ShowCreatedPopupWidgetInterceptor. | 
 | class ShowCreatedPopupWidgetInterceptor | 
 |     : public blink::mojom::PopupWidgetHostInterceptorForTesting { | 
 |  public: | 
 |   ShowCreatedPopupWidgetInterceptor( | 
 |       RenderWidgetHostImpl* render_widget_host, | 
 |       base::OnceCallback<void(int32_t pending_widget_routing_id)> test_callback) | 
 |       : render_widget_host_(render_widget_host), | 
 |         test_callback_(std::move(test_callback)), | 
 |         swapped_impl_( | 
 |             render_widget_host_->popup_widget_host_receiver_for_testing(), | 
 |             this) {} | 
 |  | 
 |   ~ShowCreatedPopupWidgetInterceptor() override = default; | 
 |  | 
 |   blink::mojom::PopupWidgetHost* GetForwardingInterface() override { | 
 |     return swapped_impl_.old_impl(); | 
 |   } | 
 |  | 
 |   void ShowPopup(const gfx::Rect& initial_rect, | 
 |                  const gfx::Rect& initial_anchor_rect, | 
 |                  ShowPopupCallback callback) override { | 
 |     show_callback_ = std::move(callback); | 
 |     initial_rect_ = initial_rect; | 
 |     std::move(test_callback_).Run(render_widget_host_->GetRoutingID()); | 
 |   } | 
 |  | 
 |   void ResumeShowPopupWidget() { | 
 |     // Let anchor have same origin as bounds, but its width and height should be | 
 |     // 1,1 as RenderWidgetHostViewAura sets OwnedWindowAnchorPosition as | 
 |     // kBottomLeft. Otherwise, the bottom left point of the |initial_rect|'s | 
 |     // size is going to be used as the origin of a popup. | 
 |     gfx::Rect anchor = initial_rect_; | 
 |     anchor.set_size({1, 1}); | 
 |     GetForwardingInterface()->ShowPopup(initial_rect_, anchor, | 
 |                                         std::move(show_callback_)); | 
 |   } | 
 |  | 
 |  private: | 
 |   raw_ptr<RenderWidgetHostImpl> render_widget_host_; | 
 |   base::OnceCallback<void(int32_t pending_widget_routing_id)> test_callback_; | 
 |   ShowPopupCallback show_callback_; | 
 |   gfx::Rect initial_rect_; | 
 |   mojo::test::ScopedSwapImplForTesting<blink::mojom::PopupWidgetHost> | 
 |       swapped_impl_; | 
 | }; | 
 |  | 
 | // Listens for the source RenderFrameHost opening the new popup widget then | 
 | // attaches a show listener to the widget. | 
 | class NewPopupWidgetCreatedObserver { | 
 |  public: | 
 |   NewPopupWidgetCreatedObserver( | 
 |       RenderFrameHostImpl* frame_host, | 
 |       base::OnceCallback<void(int32_t pending_widget_routing_id)> test_callback) | 
 |       : create_new_popup_widget_interceptor_( | 
 |             frame_host, | 
 |             base::BindOnce(&NewPopupWidgetCreatedObserver::DidCreatePopupWidget, | 
 |                            base::Unretained(this))), | 
 |         test_callback_(std::move(test_callback)) {} | 
 |  | 
 |   void ResumeShowPopupWidget() { show_interceptor_->ResumeShowPopupWidget(); } | 
 |  | 
 |  private: | 
 |   void DidCreatePopupWidget(RenderWidgetHost* widget) { | 
 |     show_interceptor_ = std::make_unique<ShowCreatedPopupWidgetInterceptor>( | 
 |         static_cast<RenderWidgetHostImpl*>(widget), std::move(test_callback_)); | 
 |   } | 
 |  | 
 |   CreateNewPopupWidgetInterceptor create_new_popup_widget_interceptor_; | 
 |   std::unique_ptr<ShowCreatedPopupWidgetInterceptor> show_interceptor_; | 
 |   base::OnceCallback<void(int32_t pending_widget_routing_id)> test_callback_; | 
 | }; | 
 |  | 
 | // Test for https://crbug.com/612276.  Similar to | 
 | // TwoSubframesOpenWindowsSimultaneously, but use popup menu widgets instead of | 
 | // windows. | 
 | // | 
 | // The plumbing that this test is verifying is not utilized on Mac/Android, | 
 | // where popup menus don't create a popup RenderWidget, but rather they trigger | 
 | // a FrameHostMsg_ShowPopup to ask the browser to build and display the actual | 
 | // popup using native controls. | 
 | #if !BUILDFLAG(IS_MAC) && !BUILDFLAG(IS_ANDROID) | 
 | // Disable the test due to flaky: https://crbug.com/1126165 | 
 | #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) | 
 | #define MAYBE_TwoSubframesCreatePopupMenuWidgetsSimultaneously \ | 
 |   DISABLED_TwoSubframesCreatePopupMenuWidgetsSimultaneously | 
 | #else | 
 | #define MAYBE_TwoSubframesCreatePopupMenuWidgetsSimultaneously \ | 
 |   TwoSubframesCreatePopupMenuWidgetsSimultaneously | 
 | #endif | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        MAYBE_TwoSubframesCreatePopupMenuWidgetsSimultaneously) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b,c)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   FrameTreeNode* child1 = root->child_at(0); | 
 |   FrameTreeNode* child2 = root->child_at(1); | 
 |   RenderProcessHost* process1 = child1->current_frame_host()->GetProcess(); | 
 |   RenderProcessHost* process2 = child2->current_frame_host()->GetProcess(); | 
 |  | 
 |   // Navigate both subframes to a page with a <select> element. | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer( | 
 |       child1, embedded_test_server()->GetURL( | 
 |                   "b.com", "/site_isolation/page-with-select.html"))); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer( | 
 |       child2, embedded_test_server()->GetURL( | 
 |                   "c.com", "/site_isolation/page-with-select.html"))); | 
 |  | 
 |   // Open both <select> menus by focusing each item and sending a space key | 
 |   // at the focused node. This creates a popup widget in both processes. | 
 |   // Wait for and then drop the ViewHostMsg_ShowWidget messages, so that both | 
 |   // widgets are left in pending-but-not-shown state. | 
 |   input::NativeWebKeyboardEvent event( | 
 |       blink::WebKeyboardEvent::Type::kChar, blink::WebInputEvent::kNoModifiers, | 
 |       blink::WebInputEvent::GetStaticTimeStampForTests()); | 
 |   event.text[0] = ' '; | 
 |  | 
 |   base::RunLoop run_loop1; | 
 |   int32_t routing_id1; | 
 |   NewPopupWidgetCreatedObserver interceptor1( | 
 |       child1->current_frame_host(), | 
 |       base::BindLambdaForTesting([&](int32_t pending_widget_routing_id) { | 
 |         routing_id1 = pending_widget_routing_id; | 
 |         run_loop1.Quit(); | 
 |       })); | 
 |   EXPECT_TRUE(ExecJs(child1, "focusSelectMenu();")); | 
 |   child1->current_frame_host()->GetRenderWidgetHost()->ForwardKeyboardEvent( | 
 |       event); | 
 |   run_loop1.Run(); | 
 |  | 
 |   auto first_popup_global_id = | 
 |       GlobalRoutingID(process1->GetDeprecatedID(), routing_id1); | 
 |   // Add an interceptor for first popup widget so it doesn't get closed | 
 |   // immediately while the other one is being opened. | 
 |   EXPECT_TRUE( | 
 |       base::Contains(web_contents()->pending_widgets_, first_popup_global_id)); | 
 |  | 
 |   RequestCloseWidgetInterceptor child1_popup_widget_interceptor( | 
 |       static_cast<RenderWidgetHostImpl*>( | 
 |           web_contents()->pending_widgets_[first_popup_global_id])); | 
 |  | 
 |   base::RunLoop run_loop2; | 
 |   int32_t routing_id2; | 
 |   NewPopupWidgetCreatedObserver interceptor2( | 
 |       child2->current_frame_host(), | 
 |       base::BindLambdaForTesting([&](int32_t pending_widget_routing_id) { | 
 |         routing_id2 = pending_widget_routing_id; | 
 |         run_loop2.Quit(); | 
 |       })); | 
 |   EXPECT_TRUE(ExecJs(child2, "focusSelectMenu();")); | 
 |   child2->current_frame_host()->GetRenderWidgetHost()->ForwardKeyboardEvent( | 
 |       event); | 
 |   run_loop2.Run(); | 
 |  | 
 |   // At this point, we should have two pending widgets. | 
 |   EXPECT_TRUE( | 
 |       base::Contains(web_contents()->pending_widgets_, first_popup_global_id)); | 
 |   EXPECT_TRUE(base::Contains( | 
 |       web_contents()->pending_widgets_, | 
 |       GlobalRoutingID(process2->GetDeprecatedID(), routing_id2))); | 
 |  | 
 |   // Both subframes were set up in the same way, so the next routing ID for the | 
 |   // new popup widgets should match up (this led to the collision in the | 
 |   // pending widgets map in the original bug). | 
 |   EXPECT_EQ(routing_id1, routing_id2); | 
 |  | 
 |   // Now simulate both widgets being shown. | 
 |   interceptor1.ResumeShowPopupWidget(); | 
 |   interceptor2.ResumeShowPopupWidget(); | 
 |   EXPECT_FALSE(base::Contains( | 
 |       web_contents()->pending_widgets_, | 
 |       GlobalRoutingID(process1->GetDeprecatedID(), routing_id1))); | 
 |   EXPECT_FALSE(base::Contains( | 
 |       web_contents()->pending_widgets_, | 
 |       GlobalRoutingID(process2->GetDeprecatedID(), routing_id2))); | 
 |  | 
 |   // There are posted tasks that must be run before the test shuts down, lest | 
 |   // they access deleted state. | 
 |   RunPostedTasks(); | 
 | } | 
 | #endif | 
 |  | 
 | // Test for https://crbug.com/615575. It ensures that file chooser triggered | 
 | // by a document in an out-of-process subframe works properly. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, FileChooserInSubframe) { | 
 |   EXPECT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b)"))); | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   GURL url(embedded_test_server()->GetURL("b.com", "/file_input.html")); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(0), url)); | 
 |  | 
 |   // Use FileChooserDelegate to avoid showing the actual dialog and to respond | 
 |   // back to the renderer process with predefined file. | 
 |   base::RunLoop run_loop; | 
 |   base::FilePath file; | 
 |   EXPECT_TRUE(base::PathService::Get(base::DIR_TEMP, &file)); | 
 |   file = file.AppendASCII("bar"); | 
 |   std::unique_ptr<FileChooserDelegate> delegate( | 
 |       new FileChooserDelegate(file, run_loop.QuitClosure())); | 
 |   shell()->web_contents()->SetDelegate(delegate.get()); | 
 |   EXPECT_TRUE(ExecJs(root->child_at(0), | 
 |                      "document.getElementById('fileinput').click();")); | 
 |   run_loop.Run(); | 
 |  | 
 |   // Also, extract the file from the renderer process to ensure that the | 
 |   // response made it over successfully and the proper filename is set. | 
 |   EXPECT_EQ("bar", | 
 |             EvalJs(root->child_at(0), | 
 |                    "document.getElementById('fileinput').files[0].name;")); | 
 | } | 
 |  | 
 | // Test that the pending RenderFrameHost is canceled and destroyed when its | 
 | // process dies. Previously, reusing a top-level pending RFH which | 
 | // is not live was hitting a CHECK in CreateRenderView due to having neither a | 
 | // main frame routing ID nor a proxy routing ID.  See https://crbug.com/627400 | 
 | // for more details. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        PendingRFHIsCanceledWhenItsProcessDies) { | 
 |   GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // Open a popup at b.com. | 
 |   GURL popup_url(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |   Shell* popup_shell = OpenPopup(root, popup_url, "foo"); | 
 |   EXPECT_TRUE(popup_shell); | 
 |  | 
 |   // The RenderViewHost for b.com in the main tab should not be active. | 
 |   SiteInstanceGroup* b_group = | 
 |       static_cast<SiteInstanceImpl*>( | 
 |           popup_shell->web_contents()->GetSiteInstance()) | 
 |           ->group(); | 
 |   RenderViewHostImpl* rvh = | 
 |       web_contents()->GetPrimaryFrameTree().GetRenderViewHost(b_group).get(); | 
 |   EXPECT_FALSE(rvh->is_active()); | 
 |  | 
 |   // Navigate main tab to a b.com URL that will not commit. | 
 |   GURL stall_url(embedded_test_server()->GetURL("b.com", "/title2.html")); | 
 |   TestNavigationManager delayer(shell()->web_contents(), stall_url); | 
 |   EXPECT_TRUE(ExecJs(shell(), JsReplace("location = $1", stall_url))); | 
 |   delayer.WaitForSpeculativeRenderFrameHostCreation(); | 
 |  | 
 |   // The pending RFH should be in the same process as the popup. | 
 |   RenderFrameHostImpl* pending_rfh = | 
 |       root->render_manager()->speculative_frame_host(); | 
 |   RenderProcessHost* pending_process = pending_rfh->GetProcess(); | 
 |   EXPECT_EQ(pending_process, | 
 |             popup_shell->web_contents()->GetPrimaryMainFrame()->GetProcess()); | 
 |  | 
 |   // Kill the b.com process, currently in use by the pending RenderFrameHost | 
 |   // and the popup. | 
 |   RenderProcessHostWatcher crash_observer( | 
 |       pending_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); | 
 |   EXPECT_TRUE(pending_process->Shutdown(0)); | 
 |   crash_observer.Wait(); | 
 |  | 
 |   // The pending RFH should have been canceled and destroyed, so that it won't | 
 |   // be reused while it's not live in the next navigation. | 
 |   EXPECT_FALSE(root->render_manager()->speculative_frame_host()); | 
 |  | 
 |   // Navigate main tab to b.com again.  This should not crash. | 
 |   GURL b_url(embedded_test_server()->GetURL("b.com", "/title3.html")); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(shell(), b_url)); | 
 |  | 
 |   // The b.com RVH in the main tab should become active. | 
 |   EXPECT_TRUE(rvh->is_active()); | 
 | } | 
 |  | 
 | // Test that killing a pending RenderFrameHost's process doesn't leave its | 
 | // RenderViewHost confused whether it's active or not for future navigations | 
 | // that try to reuse it.  See https://crbug.com/627893 for more details. | 
 | // Similar to the test above for https://crbug.com/627400, except the popup is | 
 | // navigated after pending RFH's process is killed, rather than the main tab. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        RenderViewHostKeepsSwappedOutStateIfPendingRFHDies) { | 
 |   GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // Open a popup at b.com. | 
 |   GURL popup_url(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |   Shell* popup_shell = OpenPopup(root, popup_url, "foo"); | 
 |   EXPECT_TRUE(popup_shell); | 
 |  | 
 |   // The RenderViewHost for b.com in the main tab should not be active. | 
 |   SiteInstanceGroup* b_group = | 
 |       static_cast<SiteInstanceImpl*>( | 
 |           popup_shell->web_contents()->GetSiteInstance()) | 
 |           ->group(); | 
 |   RenderViewHostImpl* rvh = | 
 |       web_contents()->GetPrimaryFrameTree().GetRenderViewHost(b_group).get(); | 
 |   EXPECT_FALSE(rvh->is_active()); | 
 |  | 
 |   // Navigate main tab to a b.com URL that will not commit. | 
 |   GURL stall_url(embedded_test_server()->GetURL("b.com", "/title2.html")); | 
 |   NavigationHandleObserver handle_observer(shell()->web_contents(), stall_url); | 
 |   TestNavigationManager delayer(shell()->web_contents(), stall_url); | 
 |   EXPECT_TRUE(ExecJs(shell(), JsReplace("location = $1", stall_url))); | 
 |   delayer.WaitForSpeculativeRenderFrameHostCreation(); | 
 |  | 
 |   // Kill the b.com process, currently in use by the pending RenderFrameHost | 
 |   // and the popup. | 
 |   RenderProcessHost* pending_process = | 
 |       popup_shell->web_contents()->GetPrimaryMainFrame()->GetProcess(); | 
 |   RenderProcessHostWatcher crash_observer( | 
 |       pending_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); | 
 |   EXPECT_TRUE(pending_process->Shutdown(0)); | 
 |   crash_observer.Wait(); | 
 |  | 
 |   // Since the navigation above didn't commit, the b.com RenderViewHost in the | 
 |   // main tab should still not be active. | 
 |   EXPECT_FALSE(rvh->is_active()); | 
 |   EXPECT_EQ(net::ERR_ABORTED, handle_observer.net_error_code()); | 
 |  | 
 |   // Navigate popup to b.com to recreate the b.com process.  When creating | 
 |   // opener proxies, |rvh| should be reused as a swapped out RVH.  In | 
 |   // https://crbug.com/627893, recreating the opener `blink::WebView` was | 
 |   // hitting a CHECK(params.swapped_out) in the renderer process, since its | 
 |   // RenderViewHost was brought into an active state by the navigation to | 
 |   // |stall_url| above, even though it never committed. | 
 |   GURL b_url(embedded_test_server()->GetURL("b.com", "/title3.html")); | 
 |   EXPECT_TRUE(NavigateToURLInSameBrowsingInstance(popup_shell, b_url)); | 
 |   EXPECT_FALSE(rvh->is_active()); | 
 | } | 
 |  | 
 | // Test that a crashed subframe can be successfully navigated to the site it | 
 | // was on before crashing.  See https://crbug.com/634368. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        NavigateCrashedSubframeToSameSite) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   FrameTreeNode* child = root->child_at(0); | 
 |  | 
 |   // Set up a postMessage handler in the main frame for later use. | 
 |   EXPECT_TRUE(ExecJs( | 
 |       root->current_frame_host(), | 
 |       "window.addEventListener('message'," | 
 |       "                        function(e) { document.title = e.data; });")); | 
 |  | 
 |   // Crash the subframe process. | 
 |   RenderProcessHost* child_process = child->current_frame_host()->GetProcess(); | 
 |   RenderProcessHostWatcher crash_observer( | 
 |       child_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); | 
 |   child_process->Shutdown(0); | 
 |   crash_observer.Wait(); | 
 |   EXPECT_FALSE(child->current_frame_host()->IsRenderFrameLive()); | 
 |  | 
 |   // When the subframe dies, its RenderWidgetHostView should be cleared and | 
 |   // reset in the CrossProcessFrameConnector. | 
 |   EXPECT_FALSE(child->current_frame_host()->GetView()); | 
 |   RenderFrameProxyHost* proxy_to_parent = | 
 |       child->render_manager()->GetProxyToParent(); | 
 |   EXPECT_FALSE( | 
 |       proxy_to_parent->cross_process_frame_connector()->get_view_for_testing()); | 
 |  | 
 |   // Navigate the subframe to the same site it was on before crashing.  This | 
 |   // should reuse the subframe's current RenderFrameHost and reinitialize the | 
 |   // RenderFrame in a new process. | 
 |   NavigateFrameToURL(child, | 
 |                      embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |   EXPECT_TRUE(child->current_frame_host()->IsRenderFrameLive()); | 
 |  | 
 |   // The RenderWidgetHostView for the child should be recreated and set to be | 
 |   // used in the CrossProcessFrameConnector.  Without this, the frame won't be | 
 |   // rendered properly. | 
 |   EXPECT_TRUE(child->current_frame_host()->GetView()); | 
 |   EXPECT_EQ( | 
 |       child->current_frame_host()->GetView(), | 
 |       proxy_to_parent->cross_process_frame_connector()->get_view_for_testing()); | 
 |  | 
 |   // Make sure that the child frame has submitted a compositor frame | 
 |   RenderFrameSubmissionObserver frame_observer(child); | 
 |   frame_observer.WaitForMetadataChange(); | 
 |  | 
 |   // Send a postMessage from the child to its parent.  This verifies that the | 
 |   // parent's proxy in the child's SiteInstance was also restored. | 
 |   std::u16string expected_title(u"I am alive!"); | 
 |   TitleWatcher title_watcher(shell()->web_contents(), expected_title); | 
 |   EXPECT_TRUE(ExecJs(child->current_frame_host(), | 
 |                      "parent.postMessage('I am alive!', '*');")); | 
 |   EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle()); | 
 | } | 
 |  | 
 | // Test that session history length and offset are replicated to all renderer | 
 | // processes in a FrameTree.  This allows each renderer to see correct values | 
 | // for history.length, and to check the offset validity properly for | 
 | // navigations initiated via history.go(). See https:/crbug.com/501116. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, SessionHistoryReplication) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(a,a)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   FrameTreeNode* child1 = root->child_at(0); | 
 |   FrameTreeNode* child2 = root->child_at(1); | 
 |   GURL child_first_url(child1->current_url()); | 
 |   EXPECT_EQ(child1->current_url(), child2->current_url()); | 
 |  | 
 |   // Helper to retrieve the history length from a given frame. | 
 |   auto history_length = [](FrameTreeNode* ftn) { | 
 |     return EvalJs(ftn->current_frame_host(), "history.length;"); | 
 |   }; | 
 |  | 
 |   // All frames should see a history length of 1 to start with. | 
 |   EXPECT_EQ(1, history_length(root)); | 
 |   EXPECT_EQ(1, history_length(child1)); | 
 |   EXPECT_EQ(1, history_length(child2)); | 
 |  | 
 |   // Navigate first child cross-site.  This increases history length to 2. | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer( | 
 |       child1, embedded_test_server()->GetURL("b.com", "/title1.html"))); | 
 |   EXPECT_EQ(2, history_length(root)); | 
 |   EXPECT_EQ(2, history_length(child1)); | 
 |   EXPECT_EQ(2, history_length(child2)); | 
 |  | 
 |   // Navigate second child same-site. | 
 |   GURL child2_last_url(embedded_test_server()->GetURL("a.com", "/title2.html")); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(child2, child2_last_url)); | 
 |   EXPECT_EQ(3, history_length(root)); | 
 |   EXPECT_EQ(3, history_length(child1)); | 
 |   EXPECT_EQ(3, history_length(child2)); | 
 |  | 
 |   // Navigate first child same-site to another b.com URL. | 
 |   GURL child1_last_url(embedded_test_server()->GetURL("b.com", "/title3.html")); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(child1, child1_last_url)); | 
 |   EXPECT_EQ(4, history_length(root)); | 
 |   EXPECT_EQ(4, history_length(child1)); | 
 |   EXPECT_EQ(4, history_length(child2)); | 
 |  | 
 |   // Go back three entries using the history API from the main frame. This | 
 |   // checks that both history length and offset are not stale in a.com, as | 
 |   // otherwise this navigation might be dropped by Blink. | 
 |   EXPECT_TRUE(ExecJs(root, "history.go(-3);")); | 
 |   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); | 
 |   EXPECT_EQ(main_url, root->current_url()); | 
 |   EXPECT_EQ(child_first_url, child1->current_url()); | 
 |   EXPECT_EQ(child_first_url, child2->current_url()); | 
 |  | 
 |   // Now go forward three entries from the child1 frame and check that the | 
 |   // history length and offset are not stale in b.com. | 
 |   EXPECT_TRUE(ExecJs(child1, "history.go(3);")); | 
 |   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); | 
 |   EXPECT_EQ(main_url, root->current_url()); | 
 |   EXPECT_EQ(child1_last_url, child1->current_url()); | 
 |   EXPECT_EQ(child2_last_url, child2->current_url()); | 
 | } | 
 |  | 
 | // Intercepts calls to LocalFrameHost::DispatchLoad method(), and discards them. | 
 | class DispatchLoadInterceptor | 
 |     : public blink::mojom::LocalFrameHostInterceptorForTesting { | 
 |  public: | 
 |   explicit DispatchLoadInterceptor(RenderFrameHostImpl* render_frame_host) | 
 |       : swapped_impl_( | 
 |             render_frame_host->local_frame_host_receiver_for_testing(), | 
 |             this) {} | 
 |  | 
 |   ~DispatchLoadInterceptor() override = default; | 
 |  | 
 |   LocalFrameHost* GetForwardingInterface() override { | 
 |     return swapped_impl_.old_impl(); | 
 |   } | 
 |  | 
 |   // Discard incoming calls to LocalFrameHost::DispatchLoad(). | 
 |   void DispatchLoad() override {} | 
 |  | 
 |  private: | 
 |   mojo::test::ScopedSwapImplForTesting<blink::mojom::LocalFrameHost> | 
 |       swapped_impl_; | 
 | }; | 
 |  | 
 | // Test that the renderer isn't killed when a frame generates a load event just | 
 | // after becoming pending deletion.  See https://crbug.com/636513. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        LoadEventForwardingWhilePendingDeletion) { | 
 |   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()->GetPrimaryFrameTree().root(); | 
 |   FrameTreeNode* child = root->child_at(0); | 
 |  | 
 |   // Open a popup in the b.com process for later use. | 
 |   GURL popup_url(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |   Shell* popup_shell = OpenPopup(root, popup_url, "foo"); | 
 |   EXPECT_TRUE(popup_shell); | 
 |  | 
 |   // Navigate subframe to b.com.  Wait for commit but not full load. | 
 |   GURL b_url(embedded_test_server()->GetURL("b.com", "/title2.html")); | 
 |   { | 
 |     TestFrameNavigationObserver commit_observer(child); | 
 |     EXPECT_TRUE(ExecJs(child, JsReplace("location.href = $1", b_url))); | 
 |     commit_observer.WaitForCommit(); | 
 |   } | 
 |   RenderFrameHostImpl* child_rfh = child->current_frame_host(); | 
 |   child_rfh->DisableUnloadTimerForTesting(); | 
 |  | 
 |   // At this point, the subframe should have a proxy in its parent's | 
 |   // SiteInstance, a.com. | 
 |   EXPECT_TRUE(child->render_manager()->GetProxyToParent()); | 
 |  | 
 |   { | 
 |     // Intercept calls to the LocalFrameHost::DispatchLoad() method. | 
 |     DispatchLoadInterceptor interceptor(child_rfh); | 
 |  | 
 |     // Now, go back to a.com in the subframe and wait for commit. | 
 |     { | 
 |       TestFrameNavigationObserver commit_observer(child); | 
 |       web_contents()->GetController().GoBack(); | 
 |       commit_observer.WaitForCommit(); | 
 |     } | 
 |  | 
 |     // At this point, the subframe's old RFH for b.com should be pending | 
 |     // deletion, and the subframe's proxy in a.com should've been cleared. | 
 |     EXPECT_TRUE(child_rfh->IsPendingDeletion()); | 
 |     EXPECT_FALSE(child->render_manager()->GetProxyToParent()); | 
 |  | 
 |     // Simulate that the load event is dispatched from |child_rfh| just after | 
 |     // it's become pending deletion. | 
 |     child_rfh->DispatchLoad(); | 
 |   } | 
 |  | 
 |   // In the bug, DispatchLoad killed the b.com renderer.  Ensure that this is | 
 |   // not the case. Note that the process kill doesn't happen immediately, so | 
 |   // IsRenderFrameLive() can't be checked here (yet).  Instead, check that | 
 |   // JavaScript can still execute in b.com using the popup. | 
 |   EXPECT_TRUE(ExecJs(popup_shell->web_contents(), "true")); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        RFHTransfersWhilePendingDeletion) { | 
 |   GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) | 
 |                             ->GetPrimaryFrameTree() | 
 |                             .root(); | 
 |  | 
 |   // Start a cross-process navigation and wait until the response is received. | 
 |   GURL cross_site_url_1 = | 
 |       embedded_test_server()->GetURL("b.com", "/title1.html"); | 
 |   TestNavigationManager cross_site_manager(shell()->web_contents(), | 
 |                                            cross_site_url_1); | 
 |   shell()->web_contents()->GetController().LoadURL( | 
 |       cross_site_url_1, Referrer(), ui::PAGE_TRANSITION_LINK, std::string()); | 
 |   EXPECT_TRUE(cross_site_manager.WaitForResponse()); | 
 |  | 
 |   // Start a renderer-initiated navigation to a cross-process url and make sure | 
 |   // the navigation will be blocked before being transferred. | 
 |   GURL cross_site_url_2 = | 
 |       embedded_test_server()->GetURL("c.com", "/title1.html"); | 
 |   TestNavigationManager transfer_manager(shell()->web_contents(), | 
 |                                          cross_site_url_2); | 
 |   EXPECT_TRUE(ExecJs(root, JsReplace("location.href = $1", cross_site_url_2))); | 
 |   EXPECT_TRUE(transfer_manager.WaitForResponse()); | 
 |  | 
 |   // Now have the cross-process navigation commit and mark the current RFH as | 
 |   // pending deletion. | 
 |   ASSERT_TRUE(cross_site_manager.WaitForNavigationFinished()); | 
 |  | 
 |   // Resume the navigation in the previous RFH that has just been marked as | 
 |   // pending deletion. We should not crash. | 
 |   ASSERT_TRUE(transfer_manager.WaitForNavigationFinished()); | 
 | } | 
 |  | 
 | class NavigationHandleWatcher : public WebContentsObserver { | 
 |  public: | 
 |   explicit NavigationHandleWatcher(WebContents* web_contents) | 
 |       : WebContentsObserver(web_contents) {} | 
 |   void DidStartNavigation(NavigationHandle* navigation_handle) override { | 
 |     DCHECK_EQ(GURL("http://b.com/"), | 
 |               navigation_handle->GetStartingSiteInstance()->GetSiteURL()); | 
 |   } | 
 | }; | 
 |  | 
 | // Verifies that the SiteInstance of a NavigationHandle correctly identifies the | 
 | // RenderFrameHost that started the navigation (and not the destination RFH). | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        NavigationHandleSiteInstance) { | 
 |   // Navigate to a page with a cross-site iframe. | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   // Navigate the iframe cross-site. | 
 |   NavigationHandleWatcher watcher(shell()->web_contents()); | 
 |   TestNavigationObserver load_observer(shell()->web_contents()); | 
 |   GURL frame_url = embedded_test_server()->GetURL("c.com", "/title1.html"); | 
 |   EXPECT_TRUE(ExecJs(shell()->web_contents(), | 
 |                      JsReplace("window.frames[0].location = $1", frame_url))); | 
 |   load_observer.Wait(); | 
 | } | 
 |  | 
 | // Test that when canceling a pending RenderFrameHost in the middle of a | 
 | // redirect, and then killing the corresponding `blink::WebView`'s renderer | 
 | // process, the RenderViewHost isn't reused in an improper state later. | 
 | // Previously this led to a crash in CreateRenderView when recreating the | 
 | // `blink::WebView` due to a stale main frame routing ID.  See | 
 | // https://crbug.com/627400. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        ReuseNonLiveRenderViewHostAfterCancelPending) { | 
 |   GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   GURL b_url(embedded_test_server()->GetURL("b.com", "/title2.html")); | 
 |   GURL c_url(embedded_test_server()->GetURL("c.com", "/title3.html")); | 
 |  | 
 |   EXPECT_TRUE(NavigateToURL(shell(), a_url)); | 
 |  | 
 |   // Open a popup and navigate it to b.com. | 
 |   Shell* popup = OpenPopup(shell(), a_url, "popup"); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(popup, b_url)); | 
 |  | 
 |   // Open a second popup and navigate it to b.com, which redirects to c.com. | 
 |   // The navigation to b.com will create a pending RenderFrameHost, which will | 
 |   // be canceled during the redirect to c.com.  Note that | 
 |   // NavigateToURLFromRenderer will return false because the committed URL | 
 |   // won't match the requested URL due to the redirect. | 
 |   Shell* popup2 = OpenPopup(shell(), a_url, "popup2"); | 
 |   TestNavigationObserver observer(popup2->web_contents()); | 
 |   GURL redirect_url(embedded_test_server()->GetURL( | 
 |       "b.com", "/server-redirect?" + c_url.spec())); | 
 |   EXPECT_FALSE(NavigateToURLFromRenderer(popup2, redirect_url)); | 
 |   EXPECT_EQ(c_url, observer.last_navigation_url()); | 
 |   EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |  | 
 |   // Kill the b.com process (which currently hosts a `blink::RemoteFrame` that | 
 |   // replaced the pending RenderFrame in |popup2|, as well as the RenderFrame | 
 |   // for |popup|). | 
 |   RenderProcessHost* b_process = | 
 |       popup->web_contents()->GetPrimaryMainFrame()->GetProcess(); | 
 |   RenderProcessHostWatcher crash_observer( | 
 |       b_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); | 
 |   b_process->Shutdown(0); | 
 |   crash_observer.Wait(); | 
 |  | 
 |   // Navigate the second popup to b.com.  This used to crash when creating the | 
 |   // `blink::WebView`, because it reused the RenderViewHost created by the | 
 |   // canceled navigation to b.com, and that RenderViewHost had a stale main | 
 |   // frame routing ID and active state. | 
 |   EXPECT_TRUE(NavigateToURLInSameBrowsingInstance(popup2, b_url)); | 
 | } | 
 |  | 
 | // Check that after a pending RFH is canceled and replaced with a proxy (which | 
 | // reuses the canceled RFH's RenderViewHost), navigating to a main frame in the | 
 | // same site as the canceled RFH doesn't lead to a renderer crash.  The steps | 
 | // here are similar to ReuseNonLiveRenderViewHostAfterCancelPending, but don't | 
 | // involve crashing the renderer. See https://crbug.com/651980. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        RecreateMainFrameAfterCancelPending) { | 
 |   GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   GURL b_url(embedded_test_server()->GetURL("b.com", "/title2.html")); | 
 |   GURL c_url(embedded_test_server()->GetURL("c.com", "/title3.html")); | 
 |  | 
 |   EXPECT_TRUE(NavigateToURL(shell(), a_url)); | 
 |  | 
 |   // Open a popup and navigate it to b.com. | 
 |   Shell* popup = OpenPopup(shell(), a_url, "popup"); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(popup, b_url)); | 
 |  | 
 |   // Open a second popup and navigate it to b.com, which redirects to c.com. | 
 |   // The navigation to b.com will create a pending RenderFrameHost, which will | 
 |   // be canceled during the redirect to c.com.  Note that NavigateToURL will | 
 |   // return false because the committed URL won't match the requested URL due | 
 |   // to the redirect. | 
 |   Shell* popup2 = OpenPopup(shell(), a_url, "popup2"); | 
 |   TestNavigationObserver observer(popup2->web_contents()); | 
 |   GURL redirect_url(embedded_test_server()->GetURL( | 
 |       "b.com", "/server-redirect?" + c_url.spec())); | 
 |   EXPECT_FALSE(NavigateToURLFromRenderer(popup2, redirect_url)); | 
 |   EXPECT_EQ(c_url, observer.last_navigation_url()); | 
 |   EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |  | 
 |   // Navigate the second popup to b.com.  This used to crash the b.com renderer | 
 |   // because it failed to delete the canceled RFH's RenderFrame, so this caused | 
 |   // it to try to create a frame widget which already existed. | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(popup2, b_url)); | 
 | } | 
 |  | 
 | // Check that when a pending RFH is canceled and a proxy needs to be created in | 
 | // its place, the proxy is properly initialized on the renderer side.  See | 
 | // https://crbug.com/653746. | 
 | // The test disables the delay of creating the speculative RFH since it requires | 
 | // the created RFH to be cancelld because of the cross-origin redirect. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTestWithoutSpeculativeRFHDelay, | 
 |                        CommunicateWithProxyAfterCancelPending) { | 
 |   GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   GURL b_url(embedded_test_server()->GetURL("b.com", "/title2.html")); | 
 |   GURL c_url(embedded_test_server()->GetURL("c.com", "/title3.html")); | 
 |  | 
 |   EXPECT_TRUE(NavigateToURL(shell(), a_url)); | 
 |  | 
 |   // Open a popup and navigate it to b.com. | 
 |   Shell* popup = OpenPopup(shell(), a_url, "popup"); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(popup, b_url)); | 
 |  | 
 |   // Open a second popup and navigate it to b.com, which redirects to c.com. | 
 |   // The navigation to b.com will create a pending RenderFrameHost, which will | 
 |   // be canceled during the redirect to c.com.  Note that NavigateToURL will | 
 |   // return false because the committed URL won't match the requested URL due | 
 |   // to the redirect. | 
 |   Shell* popup2 = OpenPopup(shell(), a_url, "popup2"); | 
 |   TestNavigationObserver observer(popup2->web_contents()); | 
 |   GURL redirect_url(embedded_test_server()->GetURL( | 
 |       "b.com", "/server-redirect?" + c_url.spec())); | 
 |   EXPECT_FALSE(NavigateToURLFromRenderer(popup2, redirect_url)); | 
 |   EXPECT_EQ(c_url, observer.last_navigation_url()); | 
 |   EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |  | 
 |   // Because b.com has other active frames (namely, the frame in |popup|), | 
 |   // there should be a proxy created for the canceled RFH, and it should be | 
 |   // live. | 
 |   SiteInstance* b_instance = popup->web_contents()->GetSiteInstance(); | 
 |   FrameTreeNode* popup2_root = | 
 |       static_cast<WebContentsImpl*>(popup2->web_contents()) | 
 |           ->GetPrimaryFrameTree() | 
 |           .root(); | 
 |   RenderFrameProxyHost* proxy = | 
 |       popup2_root->current_frame_host() | 
 |           ->browsing_context_state() | 
 |           ->GetRenderFrameProxyHost( | 
 |               static_cast<SiteInstanceImpl*>(b_instance)->group()); | 
 |   EXPECT_TRUE(proxy); | 
 |   EXPECT_TRUE(proxy->is_render_frame_proxy_live()); | 
 |  | 
 |   // Add a postMessage listener in |popup2| (currently at a c.com URL). | 
 |   EXPECT_TRUE(ExecJs(popup2, | 
 |                      "window.addEventListener('message', function(event) {\n" | 
 |                      "  document.title=event.data;\n" | 
 |                      "});")); | 
 |  | 
 |   // Check that a postMessage can be sent via |proxy| above.  This needs to be | 
 |   // done from the b.com process.  |popup| is currently in b.com, but it can't | 
 |   // reach the window reference for |popup2| due to a security restriction in | 
 |   // Blink. So, navigate the main tab to b.com and then send a postMessage to | 
 |   // |popup2|. This is allowed since the main tab is |popup2|'s opener. | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(shell(), b_url)); | 
 |  | 
 |   std::u16string expected_title(u"foo"); | 
 |   TitleWatcher title_watcher(popup2->web_contents(), expected_title); | 
 |   EXPECT_TRUE( | 
 |       ExecJs(shell(), "window.open('','popup2').postMessage('foo', '*');")); | 
 |   EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle()); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        HeaderPolicyOnXSLTNavigation) { | 
 |   GURL url(embedded_test_server()->GetURL("a.com", "/permissions-policy.xml")); | 
 |  | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url)); | 
 |  | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   EXPECT_EQ(CreateParsedPermissionsPolicyMatchesSelf( | 
 |                 {network::mojom::PermissionsPolicyFeature::kGeolocation}, | 
 |                 url.DeprecatedGetOriginAsURL()), | 
 |             root->current_replication_state().permissions_policy_header); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        TestPolicyReplicationOnSameOriginNavigation) { | 
 |   GURL start_url( | 
 |       embedded_test_server()->GetURL("a.com", "/permissions-policy1.html")); | 
 |   GURL first_nav_url( | 
 |       embedded_test_server()->GetURL("a.com", "/permissions-policy2.html")); | 
 |   GURL second_nav_url(embedded_test_server()->GetURL("a.com", "/title2.html")); | 
 |  | 
 |   EXPECT_TRUE(NavigateToURL(shell(), start_url)); | 
 |  | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   EXPECT_EQ(CreateParsedPermissionsPolicyMatchesSelf( | 
 |                 {network::mojom::PermissionsPolicyFeature::kGeolocation, | 
 |                  network::mojom::PermissionsPolicyFeature::kPayment}, | 
 |                 start_url.DeprecatedGetOriginAsURL()), | 
 |             root->current_replication_state().permissions_policy_header); | 
 |  | 
 |   // When the main frame navigates to a page with a new policy, it should | 
 |   // overwrite the old one. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), first_nav_url)); | 
 |   EXPECT_EQ(CreateParsedPermissionsPolicyMatchesAll( | 
 |                 {network::mojom::PermissionsPolicyFeature::kGeolocation, | 
 |                  network::mojom::PermissionsPolicyFeature::kPayment}), | 
 |             root->current_replication_state().permissions_policy_header); | 
 |  | 
 |   // When the main frame navigates to a page without a policy, the replicated | 
 |   // policy header should be cleared. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), second_nav_url)); | 
 |   EXPECT_TRUE( | 
 |       root->current_replication_state().permissions_policy_header.empty()); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        TestPolicyReplicationOnCrossOriginNavigation) { | 
 |   GURL start_url( | 
 |       embedded_test_server()->GetURL("a.com", "/permissions-policy1.html")); | 
 |   GURL first_nav_url( | 
 |       embedded_test_server()->GetURL("b.com", "/permissions-policy2.html")); | 
 |   GURL second_nav_url(embedded_test_server()->GetURL("c.com", "/title2.html")); | 
 |  | 
 |   EXPECT_TRUE(NavigateToURL(shell(), start_url)); | 
 |  | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   EXPECT_EQ(CreateParsedPermissionsPolicyMatchesSelf( | 
 |                 {network::mojom::PermissionsPolicyFeature::kGeolocation, | 
 |                  network::mojom::PermissionsPolicyFeature::kPayment}, | 
 |                 start_url.DeprecatedGetOriginAsURL()), | 
 |             root->current_replication_state().permissions_policy_header); | 
 |  | 
 |   // When the main frame navigates to a page with a new policy, it should | 
 |   // overwrite the old one. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), first_nav_url)); | 
 |   EXPECT_EQ(CreateParsedPermissionsPolicyMatchesAll( | 
 |                 {network::mojom::PermissionsPolicyFeature::kGeolocation, | 
 |                  network::mojom::PermissionsPolicyFeature::kPayment}), | 
 |             root->current_replication_state().permissions_policy_header); | 
 |  | 
 |   // When the main frame navigates to a page without a policy, the replicated | 
 |   // policy header should be cleared. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), second_nav_url)); | 
 |   EXPECT_TRUE( | 
 |       root->current_replication_state().permissions_policy_header.empty()); | 
 | } | 
 |  | 
 | // Test that the replicated permissions policy header is correct in subframes as | 
 | // they navigate. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        TestPolicyReplicationFromRemoteFrames) { | 
 |   GURL main_url( | 
 |       embedded_test_server()->GetURL("a.com", "/permissions-policy-main.html")); | 
 |   GURL first_nav_url( | 
 |       embedded_test_server()->GetURL("b.com", "/permissions-policy2.html")); | 
 |   GURL second_nav_url(embedded_test_server()->GetURL("c.com", "/title2.html")); | 
 |  | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   EXPECT_EQ(CreateParsedPermissionsPolicy( | 
 |                 {network::mojom::PermissionsPolicyFeature::kGeolocation, | 
 |                  network::mojom::PermissionsPolicyFeature::kPayment}, | 
 |                 {GURL("http://example.com/")}, /*match_all_origins=*/false, | 
 |                 main_url.DeprecatedGetOriginAsURL()), | 
 |             root->current_replication_state().permissions_policy_header); | 
 |   EXPECT_EQ(1UL, root->child_count()); | 
 |   EXPECT_EQ( | 
 |       CreateParsedPermissionsPolicyMatchesSelf( | 
 |           {network::mojom::PermissionsPolicyFeature::kGeolocation, | 
 |            network::mojom::PermissionsPolicyFeature::kPayment}, | 
 |           main_url.DeprecatedGetOriginAsURL()), | 
 |       root->child_at(0)->current_replication_state().permissions_policy_header); | 
 |  | 
 |   // Navigate the iframe cross-site. | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(0), first_nav_url)); | 
 |   EXPECT_EQ( | 
 |       CreateParsedPermissionsPolicyMatchesAll( | 
 |           {network::mojom::PermissionsPolicyFeature::kGeolocation, | 
 |            network::mojom::PermissionsPolicyFeature::kPayment}), | 
 |       root->child_at(0)->current_replication_state().permissions_policy_header); | 
 |  | 
 |   // Navigate the iframe to another location, this one with no policy header | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(0), second_nav_url)); | 
 |   EXPECT_TRUE(root->child_at(0) | 
 |                   ->current_replication_state() | 
 |                   .permissions_policy_header.empty()); | 
 |  | 
 |   // Navigate the iframe back to a page with a policy | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(0), first_nav_url)); | 
 |   EXPECT_EQ( | 
 |       CreateParsedPermissionsPolicyMatchesAll( | 
 |           {network::mojom::PermissionsPolicyFeature::kGeolocation, | 
 |            network::mojom::PermissionsPolicyFeature::kPayment}), | 
 |       root->child_at(0)->current_replication_state().permissions_policy_header); | 
 | } | 
 |  | 
 | // Test that the replicated permissions policy header is correct in remote | 
 | // proxies after the local frame has navigated. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        TestPermissionsPolicyReplicationToProxyOnNavigation) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/frame_tree/page_with_two_frames.html")); | 
 |   GURL first_nav_url( | 
 |       embedded_test_server()->GetURL("a.com", "/permissions-policy3.html")); | 
 |   GURL second_nav_url( | 
 |       embedded_test_server()->GetURL("a.com", "/permissions-policy4.html")); | 
 |  | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   EXPECT_TRUE( | 
 |       root->current_replication_state().permissions_policy_header.empty()); | 
 |   EXPECT_EQ(2UL, root->child_count()); | 
 |   EXPECT_TRUE(root->child_at(1) | 
 |                   ->current_replication_state() | 
 |                   .permissions_policy_header.empty()); | 
 |  | 
 |   // Navigate the iframe to a page with a policy, and a nested cross-site iframe | 
 |   // (to the same site as a root->child_at(1) so that the render process already | 
 |   // exists.) | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(1), first_nav_url)); | 
 |   EXPECT_EQ( | 
 |       CreateParsedPermissionsPolicyMatchesNone( | 
 |           {network::mojom::PermissionsPolicyFeature::kGeolocation, | 
 |            network::mojom::PermissionsPolicyFeature::kPayment}), | 
 |       root->child_at(1)->current_replication_state().permissions_policy_header); | 
 |  | 
 |   EXPECT_EQ(1UL, root->child_at(1)->child_count()); | 
 |  | 
 |   // Ask the deepest iframe to report the enabled state of the geolocation | 
 |   // feature. If its parent frame's policy was replicated correctly to the | 
 |   // proxy, then this will be disabled. Otherwise, it will be enabled by the | 
 |   // "allow" attribute on the parent frame. | 
 |   EXPECT_EQ(false, | 
 |             EvalJs(root->child_at(1)->child_at(0), | 
 |                    "document.featurePolicy.allowsFeature('geolocation')")); | 
 |  | 
 |   // Now navigate the iframe to a page with no header policy, and the same | 
 |   // nested cross-site iframe. The header policy should be cleared in the proxy. | 
 |   // In this case, the frame policy from the parent will allow geolocation to be | 
 |   // delegated. | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(1), second_nav_url)); | 
 |   EXPECT_TRUE(root->child_at(1) | 
 |                   ->current_replication_state() | 
 |                   .permissions_policy_header.empty()); | 
 |   EXPECT_EQ(1UL, root->child_at(1)->child_count()); | 
 |  | 
 |   // Ask the deepest iframe to report the enabled state of the geolocation | 
 |   // feature. If its parent frame's policy was replicated correctly to the | 
 |   // proxy, then this will now be allowed. | 
 |   EXPECT_EQ(true, | 
 |             EvalJs(root->child_at(1)->child_at(0), | 
 |                    "document.featurePolicy.allowsFeature('geolocation')")); | 
 | } | 
 |  | 
 | // Test that the constructed permissions policy is correct in sandboxed | 
 | // frames. Sandboxed frames have an opaque origin, and if the frame policy, | 
 | // which is constructed in the parent frame, cannot send that origin through | 
 | // the browser process to the sandboxed frame, then the sandboxed frame's | 
 | // policy will be incorrect. | 
 | // | 
 | // This is a regression test for https://crbug.com/690520 | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        TestAllowAttributeInSandboxedFrame) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", | 
 |       "/cross_site_iframe_factory.html?" | 
 |       "a(b{allow-geolocation,sandbox-allow-scripts})")); | 
 |   GURL nav_url(embedded_test_server()->GetURL("c.com", "/title1.html")); | 
 |  | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   EXPECT_TRUE( | 
 |       root->current_replication_state().permissions_policy_header.empty()); | 
 |   EXPECT_EQ(1UL, root->child_count()); | 
 |   // Verify that the child frame is sandboxed with an opaque origin. | 
 |   EXPECT_TRUE(root->child_at(0) | 
 |                   ->current_frame_host() | 
 |                   ->GetLastCommittedOrigin() | 
 |                   .opaque()); | 
 |   // And verify that the origin in the replication state is also opaque. | 
 |   EXPECT_TRUE(root->child_at(0)->current_origin().opaque()); | 
 |  | 
 |   // Ask the sandboxed iframe to report the enabled state of the geolocation | 
 |   // feature. If the declared policy was correctly flagged as referring to the | 
 |   // opaque origin, then the policy in the sandboxed renderer will be | 
 |   // constructed correctly, and geolocation will be enabled in the sandbox. | 
 |   // Otherwise, it will be disabled, as geolocation is disabled by default in | 
 |   // cross-origin frames. | 
 |   EXPECT_EQ(true, | 
 |             EvalJs(root->child_at(0), | 
 |                    "document.featurePolicy.allowsFeature('geolocation');")); | 
 |  | 
 |   TestNavigationObserver load_observer(shell()->web_contents()); | 
 |   EXPECT_TRUE(ExecJs(root->child_at(0), | 
 |                      JsReplace("document.location.href=$1", nav_url))); | 
 |   load_observer.Wait(); | 
 |  | 
 |   // Verify that the child frame is sandboxed with an opaque origin. | 
 |   EXPECT_TRUE(root->child_at(0) | 
 |                   ->current_frame_host() | 
 |                   ->GetLastCommittedOrigin() | 
 |                   .opaque()); | 
 |   // And verify that the origin in the replication state is also opaque. | 
 |   EXPECT_TRUE(root->child_at(0)->current_origin().opaque()); | 
 |  | 
 |   EXPECT_EQ(true, | 
 |             EvalJs(root->child_at(0), | 
 |                    "document.featurePolicy.allowsFeature('geolocation');")); | 
 | } | 
 |  | 
 | // Test that the constructed permissions policy is correct in sandboxed | 
 | // frames. Sandboxed frames have an opaque origin, and if the frame policy, | 
 | // which is constructed in the parent frame, cannot send that origin through | 
 | // the browser process to the sandboxed frame, then the sandboxed frame's | 
 | // policy will be incorrect. | 
 | // | 
 | // This is a regression test for https://crbug.com/690520 | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        TestAllowAttributeInOpaqueOriginAfterNavigation) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/page_with_data_iframe_and_allow.html")); | 
 |   GURL nav_url(embedded_test_server()->GetURL("c.com", "/title1.html")); | 
 |  | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   EXPECT_TRUE( | 
 |       root->current_replication_state().permissions_policy_header.empty()); | 
 |   EXPECT_EQ(1UL, root->child_count()); | 
 |   // Verify that the child frame has an opaque origin. | 
 |   EXPECT_TRUE(root->child_at(0) | 
 |                   ->current_frame_host() | 
 |                   ->GetLastCommittedOrigin() | 
 |                   .opaque()); | 
 |   // And verify that the origin in the replication state is also opaque. | 
 |   EXPECT_TRUE(root->child_at(0)->current_origin().opaque()); | 
 |  | 
 |   // Verify that geolocation is enabled in the document. | 
 |   EXPECT_EQ(true, | 
 |             EvalJs(root->child_at(0), | 
 |                    "document.featurePolicy.allowsFeature('geolocation');")); | 
 |  | 
 |   TestNavigationObserver load_observer(shell()->web_contents()); | 
 |   EXPECT_TRUE(ExecJs(root->child_at(0), | 
 |                      JsReplace("document.location.href=$1", nav_url))); | 
 |   load_observer.Wait(); | 
 |  | 
 |   // Verify that the child frame no longer has an opaque origin. | 
 |   EXPECT_FALSE(root->child_at(0) | 
 |                    ->current_frame_host() | 
 |                    ->GetLastCommittedOrigin() | 
 |                    .opaque()); | 
 |   // Verify that the origin in the replication state is also no longer opaque. | 
 |   EXPECT_FALSE(root->child_at(0)->current_origin().opaque()); | 
 |  | 
 |   // Verify that the new document does not have geolocation enabled. | 
 |   EXPECT_EQ(false, | 
 |             EvalJs(root->child_at(0), | 
 |                    "document.featurePolicy.allowsFeature('geolocation');")); | 
 | } | 
 |  | 
 | // Ensure that an iframe that navigates cross-site doesn't use the same process | 
 | // as its parent. Then when its parent navigates it via the "srcdoc" attribute, | 
 | // it must reuse its parent's process. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        IframeSrcdocAfterCrossSiteNavigation) { | 
 |   GURL parent_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b)")); | 
 |   GURL child_url(embedded_test_server()->GetURL( | 
 |       "b.com", "/cross_site_iframe_factory.html?b()")); | 
 |  | 
 |   // #1 Navigate to a page with a cross-site iframe. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), parent_url)); | 
 |  | 
 |   // Ensure that the iframe uses its own process. | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   ASSERT_EQ(1u, root->child_count()); | 
 |   FrameTreeNode* child = root->child_at(0); | 
 |   EXPECT_EQ(parent_url, root->current_url()); | 
 |   EXPECT_EQ(child_url, child->current_url()); | 
 |   EXPECT_NE(root->current_frame_host()->GetSiteInstance(), | 
 |             child->current_frame_host()->GetSiteInstance()); | 
 |   EXPECT_NE(root->current_frame_host()->GetProcess(), | 
 |             child->current_frame_host()->GetProcess()); | 
 |  | 
 |   // #2 Navigate the iframe to its srcdoc attribute. | 
 |   TestNavigationObserver load_observer(shell()->web_contents()); | 
 |   EXPECT_TRUE(ExecJs( | 
 |       root, "document.getElementById('child-0').srcdoc = 'srcdoc content';")); | 
 |   load_observer.Wait(); | 
 |  | 
 |   // Ensure that the iframe reuses its parent's process. | 
 |   EXPECT_TRUE(child->current_url().IsAboutSrcdoc()); | 
 |   EXPECT_EQ(root->current_frame_host()->GetSiteInstance(), | 
 |             child->current_frame_host()->GetSiteInstance()); | 
 |   EXPECT_EQ(root->current_frame_host()->GetProcess(), | 
 |             child->current_frame_host()->GetProcess()); | 
 | } | 
 |  | 
 | // Verify that a remote-to-local navigation in a crashed subframe works.  See | 
 | // https://crbug.com/487872. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        RemoteToLocalNavigationInCrashedSubframe) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   FrameTreeNode* child = root->child_at(0); | 
 |  | 
 |   // Crash the subframe process. | 
 |   RenderProcessHost* child_process = child->current_frame_host()->GetProcess(); | 
 |   { | 
 |     RenderProcessHostWatcher crash_observer( | 
 |         child_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); | 
 |     child_process->Shutdown(0); | 
 |     crash_observer.Wait(); | 
 |   } | 
 |   EXPECT_FALSE(child->current_frame_host()->IsRenderFrameLive()); | 
 |  | 
 |   // Do a remote-to-local navigation of the child frame from the parent frame. | 
 |   TestFrameNavigationObserver frame_observer(child); | 
 |   GURL frame_url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   EXPECT_TRUE(ExecJs( | 
 |       root, JsReplace("document.querySelector('iframe').src = $1", frame_url))); | 
 |   frame_observer.Wait(); | 
 |  | 
 |   EXPECT_TRUE(child->current_frame_host()->IsRenderFrameLive()); | 
 |   EXPECT_FALSE(child->IsLoading()); | 
 |   EXPECT_EQ(child->current_frame_host()->GetSiteInstance(), | 
 |             root->current_frame_host()->GetSiteInstance()); | 
 |  | 
 |   // Ensure the subframe is correctly attached in the frame tree, and that it | 
 |   // has correct content. | 
 |   EXPECT_EQ(1, EvalJs(root, "frames.length;")); | 
 |  | 
 |   EXPECT_EQ("This page has no title.", | 
 |             EvalJs(root, "frames[0].document.body.innerText;")); | 
 | } | 
 |  | 
 | // Tests that trying to open a context menu in the old RFH after commiting a | 
 | // navigation doesn't crash the browser. https://crbug.com/677266. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        ContextMenuAfterCrossProcessNavigation) { | 
 |   // Navigate to a.com. | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("a.com", "/title1.html"))); | 
 |  | 
 |   // Disable the unload ACK and the unload timer. | 
 |   RenderFrameHostImpl* rfh = static_cast<RenderFrameHostImpl*>( | 
 |       shell()->web_contents()->GetPrimaryMainFrame()); | 
 |   auto unload_ack_filter = base::BindRepeating([] { return true; }); | 
 |   rfh->SetUnloadACKCallbackForTesting(unload_ack_filter); | 
 |   rfh->DisableUnloadTimerForTesting(); | 
 |  | 
 |   // Open a popup on a.com to keep the process alive. | 
 |   OpenPopup(shell(), embedded_test_server()->GetURL("a.com", "/title2.html"), | 
 |             "foo"); | 
 |  | 
 |   // Cross-process navigation to b.com. | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("b.com", "/title3.html"))); | 
 |  | 
 |   // Pretend that a.com just requested a context menu. This used to cause a | 
 |   // because the RenderWidgetHostView is destroyed when the frame is unloaded | 
 |   // and added to pending delete list. | 
 |   rfh->ShowContextMenu(mojo::NullAssociatedRemote(), ContextMenuParams()); | 
 | } | 
 |  | 
 | // Test iframe container policy is replicated properly to the browser. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, ContainerPolicy) { | 
 |   GURL url(embedded_test_server()->GetURL("/allowed_frames.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url)); | 
 |  | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   EXPECT_EQ(0UL, root->effective_frame_policy().container_policy.size()); | 
 |   EXPECT_EQ( | 
 |       0UL, root->child_at(0)->effective_frame_policy().container_policy.size()); | 
 |   EXPECT_EQ( | 
 |       0UL, root->child_at(1)->effective_frame_policy().container_policy.size()); | 
 |   EXPECT_EQ( | 
 |       2UL, root->child_at(2)->effective_frame_policy().container_policy.size()); | 
 |   EXPECT_EQ( | 
 |       2UL, root->child_at(3)->effective_frame_policy().container_policy.size()); | 
 | } | 
 |  | 
 | // Test dynamic updates to iframe "allow" attribute are propagated correctly. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, ContainerPolicyDynamic) { | 
 |   GURL main_url(embedded_test_server()->GetURL("/allowed_frames.html")); | 
 |   GURL nav_url( | 
 |       embedded_test_server()->GetURL("b.com", "/permissions-policy2.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   EXPECT_EQ( | 
 |       2UL, root->child_at(2)->effective_frame_policy().container_policy.size()); | 
 |  | 
 |   // Removing the "allow" attribute; pending policy should update, but effective | 
 |   // policy remains unchanged. | 
 |   EXPECT_TRUE(ExecJs( | 
 |       root, "document.getElementById('child-2').setAttribute('allow','')")); | 
 |   EXPECT_EQ( | 
 |       2UL, root->child_at(2)->effective_frame_policy().container_policy.size()); | 
 |   EXPECT_EQ(0UL, | 
 |             root->child_at(2)->pending_frame_policy().container_policy.size()); | 
 |  | 
 |   // Navigate the frame; pending policy should be committed. | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(2), nav_url)); | 
 |   EXPECT_EQ( | 
 |       0UL, root->child_at(2)->effective_frame_policy().container_policy.size()); | 
 | } | 
 |  | 
 | // Check that out-of-process frames correctly calculate the container policy in | 
 | // the renderer when navigating cross-origin. The policy should be unchanged | 
 | // when modified dynamically in the parent frame. When the frame is navigated, | 
 | // the new renderer should have the correct container policy. | 
 | // | 
 | // TODO(iclelland): Once there is a proper JS inspection API from the renderer, | 
 | // use that to check the policy. Until then, we test webkitFullscreenEnabled, | 
 | // which conveniently just returns the result of calling isFeatureEnabled on | 
 | // the fullscreen feature. Since there are no HTTP header policies involved, | 
 | // this verifies the presence of the container policy in the iframe. | 
 | // https://crbug.com/703703 | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        ContainerPolicyCrossOriginNavigation) { | 
 |   WebContentsImpl* contents = web_contents(); | 
 |   FrameTreeNode* root = contents->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // Helper to check if a frame is allowed to go fullscreen on the renderer | 
 |   // side. | 
 |   auto is_fullscreen_allowed = [](FrameTreeNode* ftn) { | 
 |     return EvalJs(ftn, "document.webkitFullscreenEnabled;"); | 
 |   }; | 
 |  | 
 |   // Load a page with an <iframe> without allowFullscreen. | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL( | 
 |                    "a.com", "/cross_site_iframe_factory.html?a(b)"))); | 
 |  | 
 |   // Dynamically enable fullscreen for the subframe and check that the | 
 |   // fullscreen property was updated on the FrameTreeNode. | 
 |   EXPECT_TRUE(ExecJs( | 
 |       root, "document.getElementById('child-0').allowFullscreen='true'")); | 
 |  | 
 |   // No change is expected to the container policy for dynamic modification of | 
 |   // a loaded frame. | 
 |   EXPECT_EQ(false, is_fullscreen_allowed(root->child_at(0))); | 
 |  | 
 |   // Cross-site navigation should update the container policy in the new render | 
 |   // frame. | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer( | 
 |       root->child_at(0), | 
 |       embedded_test_server()->GetURL("c.com", "/title1.html"))); | 
 |   EXPECT_EQ(true, is_fullscreen_allowed(root->child_at(0))); | 
 | } | 
 |  | 
 | // Test that dynamic updates to iframe sandbox attribute correctly set the | 
 | // replicated container policy. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        ContainerPolicySandboxDynamic) { | 
 |   GURL main_url(embedded_test_server()->GetURL("/allowed_frames.html")); | 
 |   GURL nav_url( | 
 |       embedded_test_server()->GetURL("b.com", "/permissions-policy2.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // Validate that the effective container policy contains a single non-unique | 
 |   // origin. | 
 |   const network::ParsedPermissionsPolicy initial_effective_policy = | 
 |       root->child_at(2)->effective_frame_policy().container_policy; | 
 |   EXPECT_EQ(1UL, initial_effective_policy[0].allowed_origins.size()); | 
 |  | 
 |   // Set the "sandbox" attribute; pending policy should update, and should now | 
 |   // be flagged as matching the opaque origin of the frame (without containing | 
 |   // an actual opaque origin, since the parent frame doesn't actually have that | 
 |   // origin yet) but the effective policy should remain unchanged. | 
 |   EXPECT_TRUE(ExecJs( | 
 |       root, "document.getElementById('child-2').setAttribute('sandbox','')")); | 
 |   const network::ParsedPermissionsPolicy updated_effective_policy = | 
 |       root->child_at(2)->effective_frame_policy().container_policy; | 
 |   const network::ParsedPermissionsPolicy updated_pending_policy = | 
 |       root->child_at(2)->pending_frame_policy().container_policy; | 
 |   EXPECT_EQ(1UL, updated_effective_policy[0].allowed_origins.size()); | 
 |   EXPECT_TRUE(updated_pending_policy[0].matches_opaque_src); | 
 |   EXPECT_EQ(0UL, updated_pending_policy[0].allowed_origins.size()); | 
 |  | 
 |   // Navigate the frame; pending policy should now be committed. | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(2), nav_url)); | 
 |   const network::ParsedPermissionsPolicy final_effective_policy = | 
 |       root->child_at(2)->effective_frame_policy().container_policy; | 
 |   EXPECT_TRUE(final_effective_policy[0].matches_opaque_src); | 
 |   EXPECT_EQ(0UL, final_effective_policy[0].allowed_origins.size()); | 
 | } | 
 |  | 
 | // Test that creating a new remote frame at the same origin as its parent | 
 | // results in the correct permissions policy in the RemoteSecurityContext. | 
 | // https://crbug.com/852102 | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        PermissionsPolicyConstructionInExistingProxy) { | 
 |   WebContentsImpl* contents = web_contents(); | 
 |   FrameTreeNode* root = contents->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // Navigate to a page (1) with a cross-origin iframe (2). After load, the | 
 |   // frame tree should look like: | 
 |   // | 
 |   //    a.com(1) | 
 |   //   / | 
 |   // b.com(2) | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL( | 
 |                    "a.com", "/cross_site_iframe_factory.html?a(b)"))); | 
 |  | 
 |   // Programmatically create a new same-origin frame (3) under the root, with a | 
 |   // cross-origin child (4). Since two SiteInstances already exist at this | 
 |   // point, a proxy for frame 3 will be created in the renderer for frames 2 and | 
 |   // 4. The frame tree should look like: | 
 |   // | 
 |   //    a.com(1) | 
 |   //   /      \ | 
 |   // b.com(2) a.com(3) | 
 |   //                \ | 
 |   //                b.com(4) | 
 |   auto create_subframe_script = JsReplace( | 
 |       "var f = document.createElement('iframe'); f.src=$1; " | 
 |       "document.body.appendChild(f);", | 
 |       embedded_test_server()->GetURL( | 
 |           "a.com", "/cross_site_iframe_factory.html?a(b{allow-autoplay})")); | 
 |   EXPECT_TRUE(ExecJs(root, create_subframe_script)); | 
 |   EXPECT_TRUE(WaitForLoadStop(contents)); | 
 |  | 
 |   // Verify the shape of the frame tree | 
 |   EXPECT_EQ(2UL, root->child_count()); | 
 |   EXPECT_EQ(1UL, root->child_at(1)->child_count()); | 
 |  | 
 |   // Ask frame 4 to report the enabled state of the autoplay feature. Frame 3's | 
 |   // policy should allow autoplay if created correctly, as it is same-origin | 
 |   // with the root, where the feature is enabled by default, and therefore | 
 |   // should be able to delegate it to frame 4. | 
 |   // This indirectly tests the replicated policy in frame 3: Because frame 4 is | 
 |   // cross-origin to frame 3, it will use the proxy's replicated policy as the | 
 |   // parent policy; otherwise we would just ask frame 3 to report its own state. | 
 |   EXPECT_EQ(true, EvalJs(root->child_at(1)->child_at(0), | 
 |                          "document.featurePolicy.allowsFeature('autoplay');")); | 
 | } | 
 |  | 
 | // Test harness that allows for "barrier" style delaying of requests matching | 
 | // certain paths. Call SetDelayedRequestsForPath to delay requests, then | 
 | // SetUpEmbeddedTestServer to register handlers and start the server. | 
 | class RequestDelayingSitePerProcessBrowserTest | 
 |     : public SitePerProcessBrowserTest { | 
 |  public: | 
 |   RequestDelayingSitePerProcessBrowserTest() | 
 |       : test_server_(std::make_unique<net::EmbeddedTestServer>()) {} | 
 |  | 
 |   // Must be called after any calls to SetDelayedRequestsForPath. | 
 |   void SetUpEmbeddedTestServer() { | 
 |     SetupCrossSiteRedirector(test_server_.get()); | 
 |     test_server_->RegisterRequestHandler(base::BindRepeating( | 
 |         &RequestDelayingSitePerProcessBrowserTest::HandleMockResource, | 
 |         base::Unretained(this))); | 
 |     ASSERT_TRUE(test_server_->Start()); | 
 |   } | 
 |  | 
 |   // Delays |num_delayed| requests with URLs whose path parts match |path|. When | 
 |   // the |num_delayed| + 1 request matching the path comes in, the rest are | 
 |   // unblocked. | 
 |   // Note: must be called on the UI thread before |test_server_| is started. | 
 |   void SetDelayedRequestsForPath(const std::string& path, int num_delayed) { | 
 |     DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |     DCHECK(!test_server_->Started()); | 
 |     num_remaining_requests_to_delay_for_path_[path] = num_delayed; | 
 |   } | 
 |  | 
 |  private: | 
 |   // Called on the test server's thread. | 
 |   void AddDelayedResponse( | 
 |       base::WeakPtr<net::test_server::HttpResponseDelegate> delegate) { | 
 |     response_closures_.push_back(base::BindOnce( | 
 |         &net::test_server::HttpResponseDelegate::SendHeadersContentAndFinish, | 
 |         delegate, net::HTTP_OK, "OK", base::StringPairs(), "")); | 
 |   } | 
 |  | 
 |   // Custom embedded test server handler. Looks for requests matching | 
 |   // num_remaining_requests_to_delay_for_path_, and delays them if necessary. As | 
 |   // soon as a single request comes in and: | 
 |   // 1) It matches a delayed path | 
 |   // 2) No path has any more requests to delay | 
 |   // Then we release the barrier and finish all delayed requests. | 
 |   std::unique_ptr<net::test_server::HttpResponse> HandleMockResource( | 
 |       const net::test_server::HttpRequest& request) { | 
 |     auto it = | 
 |         num_remaining_requests_to_delay_for_path_.find(request.GetURL().path()); | 
 |     if (it == num_remaining_requests_to_delay_for_path_.end()) | 
 |       return nullptr; | 
 |  | 
 |     // If there are requests to delay for this path, make a delayed request | 
 |     // which will be finished later. Otherwise fall through to the bottom and | 
 |     // send an empty response. | 
 |     if (it->second > 0) { | 
 |       --it->second; | 
 |       return std::make_unique<DelayedResponse>(this); | 
 |     } | 
 |     MaybeStartRequests(); | 
 |     return nullptr; | 
 |   } | 
 |  | 
 |   // If there are no more requests to delay, post a series of tasks finishing | 
 |   // all the delayed tasks. This will be called on the test server's thread. | 
 |   void MaybeStartRequests() { | 
 |     for (auto it : num_remaining_requests_to_delay_for_path_) { | 
 |       if (it.second > 0) | 
 |         return; | 
 |     } | 
 |     for (auto& it : response_closures_) | 
 |       std::move(it).Run(); | 
 |   } | 
 |  | 
 |   // This class passes the delegates needed to respond to a request to the | 
 |   // underlying test fixture. | 
 |   class DelayedResponse : public net::test_server::BasicHttpResponse { | 
 |    public: | 
 |     explicit DelayedResponse( | 
 |         RequestDelayingSitePerProcessBrowserTest* test_harness) | 
 |         : test_harness_(test_harness) {} | 
 |  | 
 |     DelayedResponse(const DelayedResponse&) = delete; | 
 |     DelayedResponse& operator=(const DelayedResponse&) = delete; | 
 |  | 
 |     void SendResponse(base::WeakPtr<net::test_server::HttpResponseDelegate> | 
 |                           delegate) override { | 
 |       test_harness_->AddDelayedResponse(delegate); | 
 |     } | 
 |  | 
 |    private: | 
 |     raw_ptr<RequestDelayingSitePerProcessBrowserTest> test_harness_; | 
 |   }; | 
 |  | 
 |   // Set of delegates to call which will complete delayed requests. May only be | 
 |   // modified on the test_server_'s thread. | 
 |   std::vector<base::OnceClosure> response_closures_; | 
 |  | 
 |   // Map from URL paths to the number of requests to delay for that particular | 
 |   // path. Initialized on the UI thread but modified and read on the test | 
 |   // server's thread after the |test_server_| is started. | 
 |   std::map<std::string, int> num_remaining_requests_to_delay_for_path_; | 
 |  | 
 |   // Don't use embedded_test_server() because this one requires custom | 
 |   // initialization. | 
 |   std::unique_ptr<net::EmbeddedTestServer> test_server_; | 
 | }; | 
 |  | 
 | // Regression tests for https://crbug.com/678206, where the request throttling | 
 | // in ResourceScheduler was not updated for OOPIFs. This resulted in a single | 
 | // hung delayable request (e.g. video) starving all other delayable requests. | 
 | // The tests work by delaying n requests in a cross-domain iframe. Once the n + | 
 | // 1st request goes through to the network stack (ensuring it was not starved), | 
 | // the delayed request completed. | 
 | // | 
 | // If the logic is not correct, these tests will time out, as the n + 1st | 
 | // request will never start. | 
 | IN_PROC_BROWSER_TEST_P(RequestDelayingSitePerProcessBrowserTest, | 
 |                        DelayableSubframeRequestsOneFrame) { | 
 |   std::string path = "/mock-video.mp4"; | 
 |   SetDelayedRequestsForPath(path, 2); | 
 |   SetUpEmbeddedTestServer(); | 
 |   GURL url(embedded_test_server()->GetURL( | 
 |       "a.com", base::StringPrintf("/site_isolation/" | 
 |                                   "subframes_with_resources.html?urls=%s&" | 
 |                                   "numSubresources=3", | 
 |                                   path.c_str()))); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url)); | 
 |   EXPECT_EQ(true, EvalJs(shell(), "createFrames()")); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_P(RequestDelayingSitePerProcessBrowserTest, | 
 |                        DelayableSubframeRequestsTwoFrames) { | 
 |   std::string path0 = "/mock-video0.mp4"; | 
 |   std::string path1 = "/mock-video1.mp4"; | 
 |   SetDelayedRequestsForPath(path0, 2); | 
 |   SetDelayedRequestsForPath(path1, 2); | 
 |   SetUpEmbeddedTestServer(); | 
 |   GURL url(embedded_test_server()->GetURL( | 
 |       "a.com", base::StringPrintf("/site_isolation/" | 
 |                                   "subframes_with_resources.html?urls=%s,%s&" | 
 |                                   "numSubresources=3", | 
 |                                   path0.c_str(), path1.c_str()))); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url)); | 
 |   EXPECT_EQ(true, EvalJs(shell(), "createFrames()")); | 
 | } | 
 |  | 
 | #if BUILDFLAG(IS_ANDROID) | 
 | class TextSelectionObserver : public TextInputManager::Observer { | 
 |  public: | 
 |   explicit TextSelectionObserver(TextInputManager* text_input_manager) | 
 |       : text_input_manager_(text_input_manager) { | 
 |     text_input_manager->AddObserver(this); | 
 |   } | 
 |  | 
 |   TextSelectionObserver(const TextSelectionObserver&) = delete; | 
 |   TextSelectionObserver& operator=(const TextSelectionObserver&) = delete; | 
 |  | 
 |   ~TextSelectionObserver() { text_input_manager_->RemoveObserver(this); } | 
 |  | 
 |   void WaitForSelectedText(const std::string& expected_text) { | 
 |     if (last_selected_text_ == expected_text) | 
 |       return; | 
 |     expected_text_ = expected_text; | 
 |     loop_runner_ = new MessageLoopRunner(); | 
 |     loop_runner_->Run(); | 
 |   } | 
 |  | 
 |  private: | 
 |   void OnTextSelectionChanged(TextInputManager* text_input_manager, | 
 |                               RenderWidgetHostViewBase* updated_view) override { | 
 |     last_selected_text_ = base::UTF16ToUTF8( | 
 |         text_input_manager->GetTextSelection(updated_view)->selected_text()); | 
 |     if (last_selected_text_ == expected_text_ && loop_runner_) | 
 |       loop_runner_->Quit(); | 
 |   } | 
 |  | 
 |   const raw_ptr<TextInputManager> text_input_manager_; | 
 |   std::string last_selected_text_; | 
 |   std::string expected_text_; | 
 |   scoped_refptr<MessageLoopRunner> loop_runner_; | 
 | }; | 
 |  | 
 | class SitePerProcessAndroidImeTest : public SitePerProcessBrowserTest { | 
 |  public: | 
 |   SitePerProcessAndroidImeTest() : SitePerProcessBrowserTest() {} | 
 |  | 
 |   SitePerProcessAndroidImeTest(const SitePerProcessAndroidImeTest&) = delete; | 
 |   SitePerProcessAndroidImeTest& operator=(const SitePerProcessAndroidImeTest&) = | 
 |       delete; | 
 |  | 
 |   ~SitePerProcessAndroidImeTest() override {} | 
 |  | 
 |  protected: | 
 |   ImeAdapterAndroid* ime_adapter() { | 
 |     return static_cast<RenderWidgetHostViewAndroid*>( | 
 |                web_contents()->GetRenderWidgetHostView()) | 
 |         ->ime_adapter_for_testing(); | 
 |   } | 
 |  | 
 |   void FocusInputInFrame(RenderFrameHostImpl* frame) { | 
 |     ASSERT_TRUE(ExecJs(frame, "window.focus(); input.focus();")); | 
 |   } | 
 |  | 
 |   // Creates a page with multiple (nested) OOPIFs and populates all of them | 
 |   // with an <input> element along with the required handlers for the test. | 
 |   void LoadPage() { | 
 |     ASSERT_TRUE(NavigateToURL( | 
 |         shell(), | 
 |         GURL(embedded_test_server()->GetURL( | 
 |             "a.com", "/cross_site_iframe_factory.html?a(b,c(a(b)))")))); | 
 |     FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |     frames_.push_back(root->current_frame_host()); | 
 |     frames_.push_back(root->child_at(0)->current_frame_host()); | 
 |     frames_.push_back(root->child_at(1)->current_frame_host()); | 
 |     frames_.push_back(root->child_at(1)->child_at(0)->current_frame_host()); | 
 |     frames_.push_back( | 
 |         root->child_at(1)->child_at(0)->child_at(0)->current_frame_host()); | 
 |  | 
 |     // Adds an <input> to frame and sets up a handler for |window.oninput|. When | 
 |     // the input event is fired (by changing the value of <input> element), the | 
 |     // handler will select all the text so that the corresponding text selection | 
 |     // update on the browser side notifies the test about input insertion. | 
 |     std::string add_input_script = | 
 |         "var input = document.createElement('input');" | 
 |         "document.body.appendChild(input);" | 
 |         "window.oninput = function() {" | 
 |         "  input.select();" | 
 |         "};"; | 
 |  | 
 |     for (content::RenderFrameHostImpl* frame : frames_) { | 
 |       ASSERT_TRUE(ExecJs(frame, add_input_script)); | 
 |     } | 
 |   } | 
 |  | 
 |   // This methods tries to commit |text| by simulating a native call from Java. | 
 |   void CommitText(const char* text) { | 
 |     JNIEnv* env = base::android::AttachCurrentThread(); | 
 |  | 
 |     // A valid caller is needed for ImeAdapterAndroid::GetUnderlinesFromSpans. | 
 |     base::android::ScopedJavaLocalRef<jobject> caller = | 
 |         ime_adapter()->java_ime_adapter_for_testing(env); | 
 |  | 
 |     // Input string from Java side. | 
 |     base::android::ScopedJavaLocalRef<jstring> jtext = | 
 |         base::android::ConvertUTF8ToJavaString(env, text); | 
 |  | 
 |     // Simulating a native call from Java side. | 
 |     ime_adapter()->CommitText(env, caller, jtext, jtext, 0); | 
 |   } | 
 |  | 
 |   std::vector<raw_ptr<RenderFrameHostImpl, VectorExperimental>> frames_; | 
 | }; | 
 |  | 
 | // This test verifies that committing text will be applied on the focused | 
 | // RenderWidgetHost. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessAndroidImeTest, | 
 |                        CommitTextForFocusedWidget) { | 
 |   LoadPage(); | 
 |   TextSelectionObserver selection_observer( | 
 |       web_contents()->GetTextInputManager()); | 
 |   for (size_t index = 0; index < frames_.size(); ++index) { | 
 |     std::string text = base::StringPrintf("text%zu", index); | 
 |     FocusInputInFrame(frames_[index]); | 
 |     CommitText(text.c_str()); | 
 |     selection_observer.WaitForSelectedText(text); | 
 |   } | 
 | } | 
 | #endif  // BUILDFLAG(IS_ANDROID) | 
 |  | 
 | // Test that an OOPIF at b.com can navigate to a cross-site a.com URL that | 
 | // transfers back to b.com.  See https://crbug.com/681077#c10 and | 
 | // https://crbug.com/660407. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        SubframeTransfersToCurrentRFH) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b)")); | 
 |   ASSERT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) | 
 |                             ->GetPrimaryFrameTree() | 
 |                             .root(); | 
 |   scoped_refptr<SiteInstanceImpl> b_site_instance = | 
 |       root->child_at(0)->current_frame_host()->GetSiteInstance(); | 
 |  | 
 |   // Navigate subframe to a URL that will redirect from a.com back to b.com. | 
 |   // This navigation shouldn't time out.  Also ensure that the pending RFH | 
 |   // that was created for a.com is destroyed. | 
 |   GURL frame_url( | 
 |       embedded_test_server()->GetURL("a.com", "/cross-site/b.com/title2.html")); | 
 |   NavigateIframeToURL(shell()->web_contents(), "child-0", frame_url); | 
 |   EXPECT_FALSE(root->child_at(0)->render_manager()->speculative_frame_host()); | 
 |   GURL redirected_url(embedded_test_server()->GetURL("b.com", "/title2.html")); | 
 |   EXPECT_EQ(root->child_at(0)->current_url(), redirected_url); | 
 |   EXPECT_EQ(b_site_instance, | 
 |             root->child_at(0)->current_frame_host()->GetSiteInstance()); | 
 |  | 
 |   // Try the same navigation, but use the browser-initiated path. | 
 |   NavigateFrameToURL(root->child_at(0), frame_url); | 
 |   EXPECT_FALSE(root->child_at(0)->render_manager()->speculative_frame_host()); | 
 |   EXPECT_EQ(root->child_at(0)->current_url(), redirected_url); | 
 |   EXPECT_EQ(b_site_instance, | 
 |             root->child_at(0)->current_frame_host()->GetSiteInstance()); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        FrameSwapPreservesUniqueName) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(a)")); | 
 |   ASSERT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   // Navigate the subframe cross-site… | 
 |   { | 
 |     GURL url(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |     EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "child-0", url)); | 
 |   } | 
 |   // and then same-site… | 
 |   { | 
 |     GURL url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |     EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "child-0", url)); | 
 |   } | 
 |   // and cross-site once more. | 
 |   { | 
 |     GURL url(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |     EXPECT_TRUE(NavigateIframeToURL(shell()->web_contents(), "child-0", url)); | 
 |   } | 
 |  | 
 |   // Inspect the navigation entries and make sure that the navigation target | 
 |   // remained constant across frame swaps. | 
 |   auto& controller = static_cast<NavigationControllerImpl&>( | 
 |       shell()->web_contents()->GetController()); | 
 |   EXPECT_EQ(4, controller.GetEntryCount()); | 
 |  | 
 |   std::set<std::string> names; | 
 |   for (int i = 0; i < controller.GetEntryCount(); ++i) { | 
 |     NavigationEntryImpl::TreeNode* root = | 
 |         controller.GetEntryAtIndex(i)->root_node(); | 
 |     ASSERT_EQ(1U, root->children.size()); | 
 |     names.insert(root->children[0]->frame_entry->frame_unique_name()); | 
 |   } | 
 |  | 
 |   // More than one entry in the set means that the subframe frame navigation | 
 |   // entries didn't have a consistent unique name. This will break history | 
 |   // navigations =( | 
 |   EXPECT_THAT(names, SizeIs(1)) << "Mismatched names for subframe!"; | 
 | } | 
 |  | 
 | // Tests that POST body is not lost when it targets a OOPIF. | 
 | // See https://crbug.com/710937. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, PostTargetSubFrame) { | 
 |   // Navigate to a page with an OOPIF. | 
 |   GURL main_url( | 
 |       embedded_test_server()->GetURL("/frame_tree/page_with_one_frame.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |   FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) | 
 |                             ->GetPrimaryFrameTree() | 
 |                             .root(); | 
 |  | 
 |   // The main frame and the subframe live on different processes. | 
 |   EXPECT_EQ(1u, root->child_count()); | 
 |   EXPECT_NE(root->current_frame_host()->GetSiteInstance(), | 
 |             root->child_at(0)->current_frame_host()->GetSiteInstance()); | 
 |  | 
 |   // Make a form submission from the main frame and target the OOPIF. | 
 |   GURL form_url(embedded_test_server()->GetURL("/echoall")); | 
 |   TestNavigationObserver form_post_observer(shell()->web_contents(), 1); | 
 |   EXPECT_TRUE(ExecJs(shell()->web_contents(), JsReplace(R"( | 
 |     var form = document.createElement('form'); | 
 |  | 
 |     // POST form submission to /echoall. | 
 |     form.setAttribute("method", "POST"); | 
 |     form.setAttribute("action", $1); | 
 |  | 
 |     // Target the OOPIF. | 
 |     form.setAttribute("target", "child-name-0"); | 
 |  | 
 |     // Add some POST data: "my_token=my_value"; | 
 |     var input = document.createElement("input"); | 
 |     input.setAttribute("type", "hidden"); | 
 |     input.setAttribute("name", "my_token"); | 
 |     input.setAttribute("value", "my_value"); | 
 |     form.appendChild(input); | 
 |  | 
 |     // Submit the form. | 
 |     document.body.appendChild(form); | 
 |     form.submit(); | 
 |   )", | 
 |                                                         form_url))); | 
 |   form_post_observer.Wait(); | 
 |  | 
 |   NavigationEntryImpl* entry = static_cast<NavigationEntryImpl*>( | 
 |       shell()->web_contents()->GetController().GetLastCommittedEntry()); | 
 |   // TODO(arthursonzogni): This is wrong. The last committed entry was | 
 |   // renderer-initiated. See https://crbug.com/722251. | 
 |   EXPECT_FALSE(entry->is_renderer_initiated()); | 
 |  | 
 |   // Verify that POST body was correctly passed to the server and ended up in | 
 |   // the body of the page. | 
 |   EXPECT_EQ("my_token=my_value\n", | 
 |             EvalJs(root->child_at(0), | 
 |                    "document.getElementsByTagName('pre')[0].innerText;")); | 
 | } | 
 |  | 
 | // Tests that POST method and body is not lost when an OOPIF submits a form | 
 | // that targets the main frame.  See https://crbug.com/806215. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        PostTargetsMainFrameFromOOPIF) { | 
 |   // Navigate to a page with an OOPIF. | 
 |   GURL main_url( | 
 |       embedded_test_server()->GetURL("/frame_tree/page_with_one_frame.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // The main frame and the subframe live on different processes. | 
 |   EXPECT_EQ(1u, root->child_count()); | 
 |   EXPECT_NE(root->current_frame_host()->GetSiteInstance(), | 
 |             root->child_at(0)->current_frame_host()->GetSiteInstance()); | 
 |  | 
 |   // Make a form submission from the subframe and target its parent frame. | 
 |   GURL form_url(embedded_test_server()->GetURL("/echoall")); | 
 |   TestNavigationObserver form_post_observer(web_contents()); | 
 |   EXPECT_TRUE( | 
 |       ExecJs(root->child_at(0)->current_frame_host(), JsReplace(R"( | 
 |     var form = document.createElement('form'); | 
 |  | 
 |     // POST form submission to /echoall. | 
 |     form.setAttribute("method", "POST"); | 
 |     form.setAttribute("action", $1); | 
 |  | 
 |     // Target the parent. | 
 |     form.setAttribute("target", "_parent"); | 
 |  | 
 |     // Add some POST data: "my_token=my_value"; | 
 |     var input = document.createElement("input"); | 
 |     input.setAttribute("type", "hidden"); | 
 |     input.setAttribute("name", "my_token"); | 
 |     input.setAttribute("value", "my_value"); | 
 |     form.appendChild(input); | 
 |  | 
 |     // Submit the form. | 
 |     document.body.appendChild(form); | 
 |     form.submit(); | 
 |   )", | 
 |                                                                 form_url))); | 
 |   form_post_observer.Wait(); | 
 |  | 
 |   // Verify that the FrameNavigationEntry's method is POST. | 
 |   NavigationEntryImpl* entry = static_cast<NavigationEntryImpl*>( | 
 |       web_contents()->GetController().GetLastCommittedEntry()); | 
 |   EXPECT_EQ("POST", entry->root_node()->frame_entry->method()); | 
 |  | 
 |   // Verify that POST body was correctly passed to the server and ended up in | 
 |   // the body of the page. | 
 |   EXPECT_EQ("my_token=my_value\n", | 
 |             EvalJs(root, "document.getElementsByTagName('pre')[0].innerText")); | 
 |  | 
 |   // Reload the main frame and ensure the POST body is preserved.  This checks | 
 |   // that the POST body was saved in the FrameNavigationEntry. | 
 |   web_contents()->GetController().Reload(ReloadType::NORMAL, | 
 |                                          false /* check_for_repost */); | 
 |   EXPECT_TRUE(WaitForLoadStop(web_contents())); | 
 |   EXPECT_EQ("my_token=my_value\n", | 
 |             EvalJs(root, "document.getElementsByTagName('pre')[0].innerText")); | 
 | } | 
 |  | 
 | // Verify that a remote-to-local main frame navigation doesn't overwrite | 
 | // the previous history entry.  See https://crbug.com/725716. | 
 | IN_PROC_BROWSER_TEST_P( | 
 |     SitePerProcessBrowserTest, | 
 |     DISABLED_CrossProcessMainFrameNavigationDoesNotOverwriteHistory) { | 
 |   GURL foo_url(embedded_test_server()->GetURL("foo.com", "/title1.html")); | 
 |   GURL bar_url(embedded_test_server()->GetURL("bar.com", "/title2.html")); | 
 |  | 
 |   EXPECT_TRUE(NavigateToURL(shell(), foo_url)); | 
 |  | 
 |   // Open a same-site popup to keep the www.foo.com process alive. | 
 |   OpenPopup(shell(), GURL(url::kAboutBlankURL), "foo"); | 
 |  | 
 |   // Navigate foo -> bar -> foo. | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(shell(), bar_url)); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(shell(), foo_url)); | 
 |  | 
 |   // There should be three history entries. | 
 |   EXPECT_EQ(3, web_contents()->GetController().GetEntryCount()); | 
 |  | 
 |   // Go back: this should go to bar.com. | 
 |   { | 
 |     TestNavigationObserver back_observer(web_contents()); | 
 |     web_contents()->GetController().GoBack(); | 
 |     back_observer.Wait(); | 
 |   } | 
 |   EXPECT_EQ(bar_url, | 
 |             web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL()); | 
 |  | 
 |   // Go back again.  This should go to foo.com. | 
 |   { | 
 |     TestNavigationObserver back_observer(web_contents()); | 
 |     web_contents()->GetController().GoBack(); | 
 |     back_observer.Wait(); | 
 |   } | 
 |   EXPECT_EQ(foo_url, | 
 |             web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL()); | 
 | } | 
 |  | 
 | // The test is flaky on Linux, Chrome OS, etc; cf https://crbug.com/1170583. | 
 | #if BUILDFLAG(IS_POSIX) | 
 | #define MAYBE_CrossProcessInertSubframe DISABLED_CrossProcessInertSubframe | 
 | #else | 
 | #define MAYBE_CrossProcessInertSubframe CrossProcessInertSubframe | 
 | #endif | 
 | // Tests that when an out-of-process iframe becomes inert due to a modal | 
 | // <dialog> element, the contents of the iframe can still take focus. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        MAYBE_CrossProcessInertSubframe) { | 
 |   // This uses a(b,b) instead of a(b) to preserve the b.com process even when | 
 |   // the first subframe is navigated away from it. | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b,b)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) | 
 |                             ->GetPrimaryFrameTree() | 
 |                             .root(); | 
 |   ASSERT_EQ(2U, root->child_count()); | 
 |  | 
 |   FrameTreeNode* iframe_node = root->child_at(0); | 
 |  | 
 |   EXPECT_TRUE(ExecJs( | 
 |       iframe_node, | 
 |       "document.head.innerHTML = '';" | 
 |       "document.body.innerHTML = '<input id=\"text1\"> <input id=\"text2\">';" | 
 |       "text1.focus();")); | 
 |  | 
 |   // Add a <dialog> to the root frame and call showModal on it. | 
 |   EXPECT_TRUE(ExecJs(root, | 
 |                      "let dialog = " | 
 |                      "document.body.appendChild(document.createElement('" | 
 |                      "dialog'));" | 
 |                      "dialog.innerHTML = 'Modal dialog <input>';" | 
 |                      "dialog.showModal();")); | 
 |  | 
 |   // Yield the UI thread to ensure that the real SetIsInert message | 
 |   // handler runs, in order to guarantee that the update arrives at the | 
 |   // renderer process before the script below. | 
 |   base::RunLoop().RunUntilIdle(); | 
 |  | 
 |   RenderFrameProxyHost* root_proxy = | 
 |       iframe_node->render_manager()->GetProxyToParent(); | 
 |   EXPECT_TRUE(root_proxy->IsInertForTesting()); | 
 |  | 
 |   std::string focused_element; | 
 |  | 
 |   // Attempt to change focus in the inert subframe. This should work. | 
 |   // The setTimeout ensures that the inert bit can propagate before the | 
 |   // test JS code runs. | 
 |   EXPECT_EQ("text2", EvalJs(iframe_node, | 
 |                             "new Promise(resolve => {" | 
 |                             "  window.setTimeout(() => {" | 
 |                             "    text2.focus();" | 
 |                             "    resolve(document.activeElement.id);" | 
 |                             "  }, 0);" | 
 |                             "});")); | 
 |  | 
 |   // Navigate the child frame to another site, so that it moves into a new | 
 |   // process. | 
 |   GURL site_url(embedded_test_server()->GetURL("c.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(iframe_node, site_url)); | 
 |  | 
 |   // NavigateToURLFromRenderer returns when the navigation commits, at which | 
 |   // point frame state has to be re-sent to the new frame. Yield the thread to | 
 |   // prevent races with the inertness update. | 
 |   base::RunLoop().RunUntilIdle(); | 
 |  | 
 |   EXPECT_TRUE(ExecJs( | 
 |       iframe_node, | 
 |       "document.head.innerHTML = '';" | 
 |       "document.body.innerHTML = '<input id=\"text1\"> <input id=\"text2\">';" | 
 |       "text1.focus();")); | 
 |  | 
 |   // Verify we can still set focus after the navigation. | 
 |   EXPECT_EQ("text2", EvalJs(iframe_node, | 
 |                             "text2.focus();" | 
 |                             "document.activeElement.id;")); | 
 |  | 
 |   // Navigate the subframe back into its parent process to verify that the | 
 |   // new local frame remains non-inert. | 
 |   GURL same_site_url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(iframe_node, same_site_url)); | 
 |  | 
 |   EXPECT_TRUE(ExecJs( | 
 |       iframe_node, | 
 |       "document.head.innerHTML = '';" | 
 |       "document.body.innerHTML = '<input id=\"text1\"> <input id=\"text2\">';" | 
 |       "text1.focus();")); | 
 |  | 
 |   // Verify we can still set focus after the navigation. | 
 |   EXPECT_EQ("text2", EvalJs(iframe_node, | 
 |                             "text2.focus();" | 
 |                             "document.activeElement.id;")); | 
 | } | 
 |  | 
 | // Tests that IsInert frame flag is correctly updated and propagated. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        CrossProcessIsInertPropagation) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b(c))")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   FrameTreeNode* frame_a = | 
 |       static_cast<WebContentsImpl*>(shell()->web_contents()) | 
 |           ->GetPrimaryFrameTree() | 
 |           .root(); | 
 |   ASSERT_EQ(1U, frame_a->child_count()); | 
 |   FrameTreeNode* frame_b = frame_a->child_at(0); | 
 |   ASSERT_EQ(1U, frame_b->child_count()); | 
 |   FrameTreeNode* frame_c = frame_b->child_at(0); | 
 |   RenderFrameProxyHost* proxy_b = frame_b->render_manager()->GetProxyToParent(); | 
 |   RenderFrameProxyHost* proxy_c = frame_c->render_manager()->GetProxyToParent(); | 
 |  | 
 |   auto waitForInertPropagated = [&]() { | 
 |     // Force layout. This recomputes the element styles so that the <iframe> | 
 |     // gets the updated ComputedStyle::IsInert() flag. This triggers an update | 
 |     // of the associated RenderFrameProxyHost::IsInertForTesting(). | 
 |     for (FrameTreeNode* frame : {frame_a, frame_b, frame_c}) | 
 |       ExecuteScriptAsync(frame, "document.body.offsetLeft"); | 
 |  | 
 |     // Propagating the inert flag requires sending messages in between the | 
 |     // browser and the renderers. Since they are using the same mojo interfaces | 
 |     // as ExecJs, waiting for an browser<->renderer roundtrip using ExecJs | 
 |     // should be enough to guarantee it has been propagate. | 
 |     for (FrameTreeNode* frame : {frame_a, frame_b, frame_c}) | 
 |       EXPECT_TRUE(ExecJs(frame, "'Done'")); | 
 |   }; | 
 |  | 
 |   waitForInertPropagated(); | 
 |   EXPECT_FALSE(proxy_b->IsInertForTesting()); | 
 |   EXPECT_FALSE(proxy_c->IsInertForTesting()); | 
 |  | 
 |   // Make b inert, this should also make c inert. | 
 |   EXPECT_TRUE(ExecJs(frame_a, "document.body.inert = true;")); | 
 |   waitForInertPropagated(); | 
 |   EXPECT_TRUE(proxy_b->IsInertForTesting()); | 
 |   EXPECT_TRUE(proxy_c->IsInertForTesting()); | 
 |  | 
 |   // Make b non-inert, this should also make c non-inert. | 
 |   EXPECT_TRUE(ExecJs(frame_a, "document.body.inert = false;")); | 
 |   waitForInertPropagated(); | 
 |   EXPECT_FALSE(proxy_b->IsInertForTesting()); | 
 |   EXPECT_FALSE(proxy_c->IsInertForTesting()); | 
 |  | 
 |   // Make c inert. | 
 |   EXPECT_TRUE(ExecJs(frame_b, "document.body.inert = true;")); | 
 |   waitForInertPropagated(); | 
 |   EXPECT_FALSE(proxy_b->IsInertForTesting()); | 
 |   EXPECT_TRUE(proxy_c->IsInertForTesting()); | 
 |  | 
 |   // Make b inert, c should continue being inert. | 
 |   EXPECT_TRUE(ExecJs(frame_a, "document.body.inert = true;")); | 
 |   waitForInertPropagated(); | 
 |   EXPECT_TRUE(proxy_b->IsInertForTesting()); | 
 |   EXPECT_TRUE(proxy_c->IsInertForTesting()); | 
 |  | 
 |   // Try to make c non-inert, it should still be inert due to b. | 
 |   EXPECT_TRUE(ExecJs(frame_b, "document.body.inert = false;")); | 
 |   waitForInertPropagated(); | 
 |   EXPECT_TRUE(proxy_b->IsInertForTesting()); | 
 |   EXPECT_TRUE(proxy_c->IsInertForTesting()); | 
 |  | 
 |   // Make b non-inert, this should also make c non-inert. | 
 |   EXPECT_TRUE(ExecJs(frame_a, "document.body.inert = false;")); | 
 |   waitForInertPropagated(); | 
 |   EXPECT_FALSE(proxy_b->IsInertForTesting()); | 
 |   EXPECT_FALSE(proxy_c->IsInertForTesting()); | 
 |  | 
 |   // Make b anc inert. | 
 |   EXPECT_TRUE(ExecJs(frame_a, "document.body.inert = true;")); | 
 |   EXPECT_TRUE(ExecJs(frame_b, "document.body.inert = true;")); | 
 |   waitForInertPropagated(); | 
 |   EXPECT_TRUE(proxy_b->IsInertForTesting()); | 
 |   EXPECT_TRUE(proxy_c->IsInertForTesting()); | 
 |  | 
 |   // Make b non-inert, c should continue being inert. | 
 |   EXPECT_TRUE(ExecJs(frame_a, "document.body.inert = false;")); | 
 |   waitForInertPropagated(); | 
 |   EXPECT_FALSE(proxy_b->IsInertForTesting()); | 
 |   EXPECT_TRUE(proxy_c->IsInertForTesting()); | 
 | } | 
 |  | 
 | // Check that main frames for the same site rendering in unrelated tabs start | 
 | // sharing processes that are already dedicated to that site when over process | 
 | // limit. See https://crbug.com/513036. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        MainFrameProcessReuseWhenOverLimit) { | 
 |   // Set the process limit to 1. | 
 |   RenderProcessHost::SetMaxRendererProcessCount(1); | 
 |  | 
 |   GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_a)); | 
 |  | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // Create an unrelated shell window. | 
 |   GURL url_b(embedded_test_server()->GetURL("b.com", "/title2.html")); | 
 |   Shell* new_shell = CreateBrowser(); | 
 |   EXPECT_TRUE(NavigateToURL(new_shell, url_b)); | 
 |  | 
 |   FrameTreeNode* new_shell_root = | 
 |       static_cast<WebContentsImpl*>(new_shell->web_contents()) | 
 |           ->GetPrimaryFrameTree() | 
 |           .root(); | 
 |  | 
 |   // The new window's b.com root should not reuse the a.com process. | 
 |   EXPECT_NE(root->current_frame_host()->GetProcess(), | 
 |             new_shell_root->current_frame_host()->GetProcess()); | 
 |  | 
 |   // Navigating the new window to a.com should reuse the first window's | 
 |   // process. | 
 |   EXPECT_TRUE(NavigateToURL(new_shell, url_a)); | 
 |   EXPECT_EQ(root->current_frame_host()->GetProcess(), | 
 |             new_shell_root->current_frame_host()->GetProcess()); | 
 | } | 
 |  | 
 | // Check that subframes for the same site rendering in unrelated tabs start | 
 | // sharing processes that are already dedicated to that site when over process | 
 | // limit. See https://crbug.com/513036. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        SubframeProcessReuseWhenOverLimit) { | 
 |   // Set the process limit to 1. | 
 |   RenderProcessHost::SetMaxRendererProcessCount(1); | 
 |  | 
 |   GURL first_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b,b(c))")); | 
 |   ASSERT_TRUE(NavigateToURL(shell(), first_url)); | 
 |  | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // Processes for dedicated sites should never be reused. | 
 |   EXPECT_NE(root->current_frame_host()->GetProcess(), | 
 |             root->child_at(0)->current_frame_host()->GetProcess()); | 
 |   EXPECT_NE(root->current_frame_host()->GetProcess(), | 
 |             root->child_at(1)->current_frame_host()->GetProcess()); | 
 |   EXPECT_NE(root->current_frame_host()->GetProcess(), | 
 |             root->child_at(1)->child_at(0)->current_frame_host()->GetProcess()); | 
 |   EXPECT_NE(root->child_at(1)->current_frame_host()->GetProcess(), | 
 |             root->child_at(1)->child_at(0)->current_frame_host()->GetProcess()); | 
 |   EXPECT_EQ(root->child_at(0)->current_frame_host()->GetProcess(), | 
 |             root->child_at(1)->current_frame_host()->GetProcess()); | 
 |  | 
 |   // Create an unrelated shell window. | 
 |   Shell* new_shell = CreateBrowser(); | 
 |  | 
 |   GURL new_shell_url(embedded_test_server()->GetURL( | 
 |       "d.com", "/cross_site_iframe_factory.html?d(a(b))")); | 
 |   ASSERT_TRUE(NavigateToURL(new_shell, new_shell_url)); | 
 |  | 
 |   FrameTreeNode* new_shell_root = | 
 |       static_cast<WebContentsImpl*>(new_shell->web_contents()) | 
 |           ->GetPrimaryFrameTree() | 
 |           .root(); | 
 |  | 
 |   // New tab's root (d.com) should go into a separate process. | 
 |   EXPECT_NE(root->current_frame_host()->GetProcess(), | 
 |             new_shell_root->current_frame_host()->GetProcess()); | 
 |   EXPECT_NE(root->child_at(0)->current_frame_host()->GetProcess(), | 
 |             new_shell_root->current_frame_host()->GetProcess()); | 
 |   EXPECT_NE(root->child_at(1)->child_at(0)->current_frame_host()->GetProcess(), | 
 |             new_shell_root->current_frame_host()->GetProcess()); | 
 |  | 
 |   // The new tab's subframe should reuse the a.com process. | 
 |   EXPECT_EQ(root->current_frame_host()->GetProcess(), | 
 |             new_shell_root->child_at(0)->current_frame_host()->GetProcess()); | 
 |  | 
 |   // The new tab's grandchild frame should reuse the b.com process. | 
 |   EXPECT_EQ(root->child_at(0)->current_frame_host()->GetProcess(), | 
 |             new_shell_root->child_at(0) | 
 |                 ->child_at(0) | 
 |                 ->current_frame_host() | 
 |                 ->GetProcess()); | 
 | } | 
 |  | 
 | // Check that when a main frame and a subframe start navigating to the same | 
 | // cross-site URL at the same time, the new RenderFrame for the subframe is | 
 | // created successfully without crashing, and the navigations complete | 
 | // successfully.  This test checks the scenario where the main frame ends up | 
 | // committing before the subframe, and the test below checks the case where the | 
 | // subframe commits first. | 
 | // | 
 | // This used to be problematic in that the main frame navigation created an | 
 | // active RenderViewHost with a RenderFrame already swapped into the tree, and | 
 | // then while that navigation was still pending, the subframe navigation | 
 | // created its RenderFrame, which crashed when referencing its parent by a | 
 | // proxy which didn't exist. | 
 | // | 
 | // All cross-process navigations now require creating a `blink::RemoteFrame` | 
 | // before creating a RenderFrame, which makes such navigations follow the | 
 | // provisional frame (remote-to-local navigation) paths, where such a scenario | 
 | // is no longer possible.  See https://crbug.com/756790. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        TwoCrossSitePendingNavigationsAndMainFrameWins) { | 
 |   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()->GetPrimaryFrameTree().root(); | 
 |   FrameTreeNode* child = root->child_at(0); | 
 |  | 
 |   // Navigate both frames cross-site to b.com simultaneously. | 
 |   GURL new_url_1(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |   GURL new_url_2(embedded_test_server()->GetURL("b.com", "/title2.html")); | 
 |   TestNavigationManager manager1(web_contents(), new_url_1); | 
 |   TestNavigationManager manager2(web_contents(), new_url_2); | 
 |   auto script = JsReplace("location = $1; frames[0].location = $2;", new_url_1, | 
 |                           new_url_2); | 
 |   EXPECT_TRUE(ExecJs(web_contents(), script)); | 
 |  | 
 |   // Wait for main frame request, but don't commit it yet.  This should create | 
 |   // a speculative RenderFrameHost. | 
 |   manager1.WaitForSpeculativeRenderFrameHostCreation(); | 
 |   RenderFrameHostImpl* root_speculative_rfh = | 
 |       root->render_manager()->speculative_frame_host(); | 
 |   EXPECT_TRUE(root_speculative_rfh); | 
 |   scoped_refptr<SiteInstanceImpl> b_root_site_instance( | 
 |       root_speculative_rfh->GetSiteInstance()); | 
 |  | 
 |   // There should now be a live b.com proxy for the root, since it is doing a | 
 |   // cross-process navigation. | 
 |   RenderFrameProxyHost* root_proxy = | 
 |       root->current_frame_host() | 
 |           ->browsing_context_state() | 
 |           ->GetRenderFrameProxyHost(b_root_site_instance->group()); | 
 |   EXPECT_TRUE(root_proxy); | 
 |   EXPECT_TRUE(root_proxy->is_render_frame_proxy_live()); | 
 |  | 
 |   // Wait for subframe request, but don't commit it yet. | 
 |   manager2.WaitForSpeculativeRenderFrameHostCreation(); | 
 |   RenderFrameHostImpl* subframe_speculative_rfh = | 
 |       child->render_manager()->speculative_frame_host(); | 
 |   EXPECT_TRUE(child->render_manager()->speculative_frame_host()); | 
 |   scoped_refptr<SiteInstanceImpl> b_subframe_site_instance( | 
 |       subframe_speculative_rfh->GetSiteInstance()); | 
 |  | 
 |   // Similarly, the subframe should also have a b.com proxy (unused in this | 
 |   // test), since it is also doing a cross-process navigation. | 
 |   RenderFrameProxyHost* child_proxy = | 
 |       child->current_frame_host() | 
 |           ->browsing_context_state() | 
 |           ->GetRenderFrameProxyHost(b_subframe_site_instance->group()); | 
 |   EXPECT_TRUE(child_proxy); | 
 |   EXPECT_TRUE(child_proxy->is_render_frame_proxy_live()); | 
 |  | 
 |   // Now let the main frame commit. | 
 |   ASSERT_TRUE(manager1.WaitForNavigationFinished()); | 
 |  | 
 |   // Make sure the process is live and at the new URL. | 
 |   EXPECT_TRUE(b_root_site_instance->GetProcess()->IsInitializedAndNotDead()); | 
 |   EXPECT_TRUE(root->current_frame_host()->IsRenderFrameLive()); | 
 |   EXPECT_EQ(root_speculative_rfh, root->current_frame_host()); | 
 |   EXPECT_EQ(new_url_1, root->current_frame_host()->GetLastCommittedURL()); | 
 |  | 
 |   // The subframe should be gone, so the second navigation should have no | 
 |   // effect. | 
 |   ASSERT_TRUE(manager2.WaitForNavigationFinished()); | 
 |  | 
 |   // The new commit should have detached the old child frame. | 
 |   EXPECT_EQ(0U, root->child_count()); | 
 |   EXPECT_EQ(0, EvalJs(web_contents(), "frames.length;")); | 
 |  | 
 |   // The root proxy should be gone. | 
 |   if (b_subframe_site_instance->group()) { | 
 |     EXPECT_FALSE( | 
 |         root->current_frame_host() | 
 |             ->browsing_context_state() | 
 |             ->GetRenderFrameProxyHost(b_subframe_site_instance->group())); | 
 |   } | 
 | } | 
 |  | 
 | // Similar to TwoCrossSitePendingNavigationsAndMainFrameWins, but checks the | 
 | // case where the subframe navigation commits before the main frame.  See | 
 | // https://crbug.com/756790. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        TwoCrossSitePendingNavigationsAndSubframeWins) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(a,a)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   FrameTreeNode* child = root->child_at(0); | 
 |   FrameTreeNode* child2 = root->child_at(1); | 
 |  | 
 |   // Install postMessage handlers in main frame and second subframe for later | 
 |   // use. | 
 |   EXPECT_TRUE(ExecJs(root->current_frame_host(), | 
 |                      "window.addEventListener('message', function(event) {\n" | 
 |                      "  event.source.postMessage(event.data + '-reply', '*');\n" | 
 |                      "});")); | 
 |   EXPECT_TRUE(ExecJs( | 
 |       child2->current_frame_host(), | 
 |       "window.addEventListener('message', function(event) {\n" | 
 |       "  event.source.postMessage(event.data + '-subframe-reply', '*');\n" | 
 |       "});")); | 
 |  | 
 |   // Start a main frame navigation to b.com. | 
 |   GURL new_url_1(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |   TestNavigationManager manager1(web_contents(), new_url_1); | 
 |   EXPECT_TRUE(ExecJs(web_contents(), JsReplace("location = $1", new_url_1))); | 
 |  | 
 |   // Wait for main frame request and check the frame tree.  There should be a | 
 |   // proxy for b.com at the root, but nowhere else at this point. | 
 |   manager1.WaitForSpeculativeRenderFrameHostCreation(); | 
 |   EXPECT_EQ( | 
 |       " Site A (B speculative) -- proxies for B\n" | 
 |       "   |--Site A\n" | 
 |       "   +--Site A\n" | 
 |       "Where A = http://a.com/\n" | 
 |       "      B = http://b.com/", | 
 |       DepictFrameTree(root)); | 
 |  | 
 |   // Now start navigating the first subframe to b.com. | 
 |   GURL new_url_2(embedded_test_server()->GetURL("b.com", "/title2.html")); | 
 |   TestNavigationManager manager2(web_contents(), new_url_2); | 
 |   EXPECT_TRUE( | 
 |       ExecJs(web_contents(), JsReplace("frames[0].location = $1", new_url_2))); | 
 |  | 
 |   // Wait for subframe request. | 
 |   manager2.WaitForSpeculativeRenderFrameHostCreation(); | 
 |   RenderFrameHostImpl* child_speculative_rfh = | 
 |       child->render_manager()->speculative_frame_host(); | 
 |   EXPECT_TRUE(child_speculative_rfh); | 
 |   scoped_refptr<SiteInstanceImpl> b_site_instance( | 
 |       child_speculative_rfh->GetSiteInstance()); | 
 |  | 
 |   // Check that all frames have proxies for b.com at this point. The proxy for | 
 |   // |child2| is important to create since |child| has to use it to communicate | 
 |   // with |child2| if |child| commits first. | 
 |   if (CanCrossSiteNavigationsProactivelySwapBrowsingInstances()) { | 
 |     // With ProactivelySwapBrowsingInstance, the new main document and the new | 
 |     // iframe don't have the same SiteInstance, because they belong to two | 
 |     // unrelated pages. The two page use different BrowsingInstances. | 
 |     EXPECT_EQ( | 
 |         " Site A (B speculative) -- proxies for B C\n" | 
 |         "   |--Site A (C speculative) -- proxies for C\n" | 
 |         "   +--Site A ------- proxies for C\n" | 
 |         "Where A = http://a.com/\n" | 
 |         "      B = http://b.com/\n" | 
 |         "      C = http://b.com/", | 
 |         DepictFrameTree(root)); | 
 |   } else { | 
 |     EXPECT_EQ( | 
 |         " Site A (B speculative) -- proxies for B\n" | 
 |         "   |--Site A (B speculative) -- proxies for B\n" | 
 |         "   +--Site A ------- proxies for B\n" | 
 |         "Where A = http://a.com/\n" | 
 |         "      B = http://b.com/", | 
 |         DepictFrameTree(root)); | 
 |   } | 
 |  | 
 |   // Now let the subframe commit. | 
 |   ASSERT_TRUE(manager2.WaitForNavigationFinished()); | 
 |  | 
 |   // Make sure the process is live and at the new URL. | 
 |   EXPECT_TRUE(b_site_instance->GetProcess()->IsInitializedAndNotDead()); | 
 |   ASSERT_EQ(2U, root->child_count()); | 
 |   EXPECT_TRUE(child->current_frame_host()->IsRenderFrameLive()); | 
 |   EXPECT_EQ(child_speculative_rfh, child->current_frame_host()); | 
 |   EXPECT_EQ(new_url_2, child->current_frame_host()->GetLastCommittedURL()); | 
 |  | 
 |   // Recheck the proxies.  Main frame should still be pending. | 
 |   if (CanCrossSiteNavigationsProactivelySwapBrowsingInstances()) { | 
 |     EXPECT_EQ( | 
 |         " Site A (B speculative) -- proxies for B C\n" | 
 |         "   |--Site C ------- proxies for A\n" | 
 |         "   +--Site A ------- proxies for C\n" | 
 |         "Where A = http://a.com/\n" | 
 |         "      B = http://b.com/\n" | 
 |         "      C = http://b.com/", | 
 |         DepictFrameTree(root)); | 
 |   } else { | 
 |     EXPECT_EQ( | 
 |         " Site A (B speculative) -- proxies for B\n" | 
 |         "   |--Site B ------- proxies for A\n" | 
 |         "   +--Site A ------- proxies for B\n" | 
 |         "Where A = http://a.com/\n" | 
 |         "      B = http://b.com/", | 
 |         DepictFrameTree(root)); | 
 |   } | 
 |  | 
 |   // Make sure the subframe can communicate to both the root remote frame | 
 |   // (where the postMessage should go to the current RenderFrameHost rather | 
 |   // than the pending one) and its sibling remote frame in the a.com process. | 
 |   EXPECT_TRUE( | 
 |       ExecJs(child->current_frame_host(), WaitForMessageScript("event.data"))); | 
 |   EXPECT_TRUE(ExecJs(child, "parent.postMessage('root-ping', '*')")); | 
 |   EXPECT_EQ("root-ping-reply", | 
 |             EvalJs(child->current_frame_host(), "onMessagePromise")); | 
 |  | 
 |   EXPECT_TRUE( | 
 |       ExecJs(child->current_frame_host(), WaitForMessageScript("event.data"))); | 
 |   EXPECT_TRUE( | 
 |       ExecJs(child, "parent.frames[1].postMessage('sibling-ping', '*')")); | 
 |   EXPECT_EQ("sibling-ping-subframe-reply", | 
 |             EvalJs(child->current_frame_host(), "onMessagePromise")); | 
 |  | 
 |   // Cancel the pending main frame navigation, and verify that the subframe can | 
 |   // still communicate with the (old) main frame. | 
 |   root->navigator().CancelNavigation( | 
 |       root, NavigationDiscardReason::kExplicitCancellation); | 
 |   EXPECT_FALSE(root->render_manager()->speculative_frame_host()); | 
 |  | 
 |   EXPECT_TRUE( | 
 |       ExecJs(child->current_frame_host(), WaitForMessageScript("event.data"))); | 
 |   EXPECT_TRUE(ExecJs(child, "parent.postMessage('root-ping', '*')")); | 
 |   EXPECT_EQ("root-ping-reply", | 
 |             EvalJs(child->current_frame_host(), "onMessagePromise")); | 
 | } | 
 |  | 
 | // Similar to TwoCrossSitePendingNavigations* tests above, but checks the case | 
 | // where the current window and its opener navigate simultaneously. | 
 | // See https://crbug.com/756790. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        TwoCrossSitePendingNavigationsWithOpener) { | 
 |   GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // Install a postMessage handler in main frame for later use. | 
 |   EXPECT_TRUE(ExecJs(web_contents(), | 
 |                      "window.addEventListener('message', function(event) {\n" | 
 |                      "  event.source.postMessage(event.data + '-reply', '*');\n" | 
 |                      "});")); | 
 |  | 
 |   Shell* popup_shell = | 
 |       OpenPopup(shell()->web_contents(), GURL(url::kAboutBlankURL), "popup"); | 
 |  | 
 |   // Start a navigation to b.com in the first (opener) tab. | 
 |   GURL new_url_1(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |   TestNavigationManager manager(web_contents(), new_url_1); | 
 |   EXPECT_TRUE(ExecJs(web_contents(), JsReplace("location = $1", new_url_1))); | 
 |   manager.WaitForSpeculativeRenderFrameHostCreation(); | 
 |  | 
 |   // Before it commits, start and commit a navigation to b.com in the second | 
 |   // tab. | 
 |   GURL new_url_2(embedded_test_server()->GetURL("b.com", "/title2.html")); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(popup_shell, new_url_2)); | 
 |  | 
 |   // Check that the opener still has a speculative RenderFrameHost and a | 
 |   // corresponding proxy for b.com. | 
 |   RenderFrameHostImpl* speculative_rfh = | 
 |       root->render_manager()->speculative_frame_host(); | 
 |   EXPECT_TRUE(speculative_rfh); | 
 |   scoped_refptr<SiteInstanceImpl> b_site_instance( | 
 |       speculative_rfh->GetSiteInstance()); | 
 |   RenderFrameProxyHost* proxy = | 
 |       root->current_frame_host() | 
 |           ->browsing_context_state() | 
 |           ->GetRenderFrameProxyHost(b_site_instance->group()); | 
 |   EXPECT_TRUE(proxy); | 
 |   EXPECT_TRUE(proxy->is_render_frame_proxy_live()); | 
 |  | 
 |   // Make sure the second tab can communicate to its (old) opener remote frame. | 
 |   // The postMessage should go to the current RenderFrameHost rather than the | 
 |   // pending one in the first tab's main frame. | 
 |   EXPECT_TRUE( | 
 |       ExecJs(popup_shell->web_contents(), WaitForMessageScript("event.data"))); | 
 |  | 
 |   EXPECT_TRUE(ExecJs(popup_shell->web_contents(), | 
 |                      "opener.postMessage('opener-ping', '*');")); | 
 |   EXPECT_EQ("opener-ping-reply", | 
 |             EvalJs(popup_shell->web_contents(), "onMessagePromise")); | 
 |  | 
 |   // Cancel the pending main frame navigation, and verify that the subframe can | 
 |   // still communicate with the (old) main frame. | 
 |   root->navigator().CancelNavigation( | 
 |       root, NavigationDiscardReason::kExplicitCancellation); | 
 |   EXPECT_FALSE(root->render_manager()->speculative_frame_host()); | 
 |  | 
 |   EXPECT_TRUE( | 
 |       ExecJs(popup_shell->web_contents(), WaitForMessageScript("event.data"))); | 
 |   EXPECT_TRUE(ExecJs(popup_shell->web_contents(), | 
 |                      "opener.postMessage('opener-ping', '*')")); | 
 |   EXPECT_EQ("opener-ping-reply", | 
 |             EvalJs(popup_shell->web_contents(), "onMessagePromise")); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        DetachSpeculativeRenderFrameHost) { | 
 |   // Commit a page with one iframe. | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(a)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   // Start a cross-site navigation. | 
 |   GURL cross_site_url(embedded_test_server()->GetURL("b.com", "/title2.html")); | 
 |   TestNavigationManager nav_manager(shell()->web_contents(), cross_site_url); | 
 |   BeginNavigateIframeToURL(web_contents(), "child-0", cross_site_url); | 
 |  | 
 |   // Wait for the request, but don't commit it yet. This should create a | 
 |   // speculative RenderFrameHost. | 
 |   nav_manager.WaitForSpeculativeRenderFrameHostCreation(); | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   RenderFrameHostImpl* speculative_rfh = root->current_frame_host() | 
 |                                              ->child_at(0) | 
 |                                              ->render_manager() | 
 |                                              ->speculative_frame_host(); | 
 |   EXPECT_TRUE(speculative_rfh); | 
 |  | 
 |   // Currently, the browser process never handles an explicit Detach() for a | 
 |   // speculative RFH, since the speculative RFH or the entire FTN is always | 
 |   // destroyed before the renderer sends this IPC. | 
 |   speculative_rfh->Detach(); | 
 |  | 
 |   // Passes if there is no crash. | 
 | } | 
 |  | 
 | // Tests what happens if the renderer attempts to cancel a navigation after the | 
 | // NavigationRequest has already reached READY_TO_COMMIT. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        CancelNavigationAfterReadyToCommit) { | 
 |   class NavigationCanceller : public WebContentsObserver { | 
 |    public: | 
 |     NavigationCanceller(WebContents* web_contents, | 
 |                         RenderFrameHost& requesting_rfh) | 
 |         : WebContentsObserver(web_contents), requesting_rfh_(requesting_rfh) {} | 
 |  | 
 |     // WebContentsObserver overrides: | 
 |     void ReadyToCommitNavigation(NavigationHandle* navigation_handle) override { | 
 |       // Cancel the navigation in the renderer, but don't wait for the | 
 |       // reply. This is to ensure the browser process does not process any | 
 |       // incoming messages and learn about the renderer's cancellation | 
 |       // before the browser process dispatches a CommitNavigation() to the | 
 |       // renderer. | 
 |       ExecuteScriptAsync(&*requesting_rfh_, "window.stop()"); | 
 |     } | 
 |  | 
 |    private: | 
 |     const raw_ref<RenderFrameHost, AcrossTasksDanglingUntriaged> | 
 |         requesting_rfh_; | 
 |   }; | 
 |  | 
 |   // Set up a test page with a same-site child frame. | 
 |   // TODO(dcheng): In the future, it might be useful to also have a test where | 
 |   // the child frame is same-site but cross-origin, and have the parent | 
 |   // initiate the navigation in the child frame. | 
 |   GURL url1(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(a)")); | 
 |   EXPECT_TRUE(NavigateToURL(web_contents(), url1)); | 
 |  | 
 |   // Now navigate the first child to another same-site page. Note that with | 
 |   // subframe RenderDocument, this will create a speculative RFH. | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   GURL url2(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   TestNavigationManager nav_manager(web_contents(), url2); | 
 |   FrameTreeNode* first_child = root->child_at(0); | 
 |   EXPECT_TRUE(BeginNavigateToURLFromRenderer( | 
 |       first_child->render_manager()->current_frame_host(), url2)); | 
 |  | 
 |   EXPECT_TRUE(nav_manager.WaitForResponse()); | 
 |  | 
 |   bool using_speculative_rfh = | 
 |       !!first_child->render_manager()->speculative_frame_host(); | 
 |  | 
 |   NavigationCanceller canceller( | 
 |       web_contents(), *first_child->render_manager()->current_frame_host()); | 
 |  | 
 |   ASSERT_TRUE(nav_manager.WaitForNavigationFinished()); | 
 |   // The navigation should be committed if and only if it committed in a new | 
 |   // RFH (i.e. if the navigation used a speculative RFH). | 
 |   EXPECT_EQ(using_speculative_rfh, nav_manager.was_committed()); | 
 | } | 
 |  | 
 | namespace { | 
 |  | 
 | // Helper for various <object> navigation test cases that trigger fallback | 
 | // handling. Fallback handling should never reach ready-to-commit navigation, so | 
 | // this helper forces test failure if a ReadyToCommitNavigation() is received. | 
 | class AssertNoReadyToCommitNavigationCalls : public WebContentsObserver { | 
 |  public: | 
 |   explicit AssertNoReadyToCommitNavigationCalls(WebContents* contents) | 
 |       : WebContentsObserver(contents) {} | 
 |  | 
 |  private: | 
 |   // WebContentsObserver overrides: | 
 |   void ReadyToCommitNavigation(NavigationHandle* handle) override { | 
 |     ASSERT_TRUE(false); | 
 |   } | 
 | }; | 
 |  | 
 | }  // namespace | 
 |  | 
 | // Test that a same-site navigation in <object> that fails with an HTTP error | 
 | // directly triggers fallback handling, rather than triggering fallback handling | 
 | // in the renderer after it receives a `CommitNavigation()` IPC. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        ObjectTagSameSiteNavigationWithHTTPError) { | 
 |   // Set up a test page with a same-site child frame hosted in an <object> tag. | 
 |   // TODO(dcheng): In the future, it might be useful to also have a test where | 
 |   // the child frame is same-site but cross-origin, and have the parent | 
 |   // initiate the navigation in the child frame. | 
 |   GURL url1(embedded_test_server()->GetURL("a.com", "/object-frame.html")); | 
 |   EXPECT_TRUE(NavigateToURL(web_contents(), url1)); | 
 |  | 
 |   // There should be one nested browsing context. | 
 |   EXPECT_EQ(1, EvalJs(web_contents(), "window.length")); | 
 |   // And there should be no fallback content displayed. | 
 |   EXPECT_EQ("", EvalJs(web_contents(), "document.body.innerText")); | 
 |  | 
 |   // <object> fallback handling should never reach ReadyToCommitNavigation. | 
 |   AssertNoReadyToCommitNavigationCalls asserter(web_contents()); | 
 |  | 
 |   // Now navigate the first child to a same-site page that will result in a 404. | 
 |   // Note that with subframe RenderDocument, this will create a speculative RFH. | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   GURL url2(embedded_test_server()->GetURL("a.com", "/page404.html")); | 
 |   TestNavigationManager nav_manager(web_contents(), url2); | 
 |   FrameTreeNode* first_child = root->child_at(0); | 
 |   EXPECT_TRUE(BeginNavigateToURLFromRenderer( | 
 |       first_child->render_manager()->current_frame_host(), url2)); | 
 |  | 
 |   const bool using_speculative_rfh = | 
 |       !!first_child->render_manager()->speculative_frame_host(); | 
 |   // Speculative RFH will not be created at this point if we enable deferring. | 
 |   EXPECT_EQ(using_speculative_rfh, | 
 |             GetRenderDocumentLevel() >= RenderDocumentLevel::kSubframe && | 
 |                 !base::FeatureList::IsEnabled( | 
 |                     features::kDeferSpeculativeRFHCreation)); | 
 |  | 
 |   ASSERT_TRUE(nav_manager.WaitForNavigationFinished()); | 
 |   // There should be no commit... | 
 |   EXPECT_FALSE(nav_manager.was_committed()); | 
 |   // .. and the navigation should have been aborted. | 
 |   EXPECT_FALSE(nav_manager.was_successful()); | 
 |   // Fallback handling should discard the child browsing context and render the | 
 |   // fallback contents. | 
 |   EXPECT_EQ(0, EvalJs(web_contents(), "window.length")); | 
 |   EXPECT_EQ("fallback", EvalJs(web_contents(), "document.body.innerText")); | 
 | } | 
 |  | 
 | // Test that a cross-site navigation in <object> that fails with an HTTP error | 
 | // directly triggers fallback handling, rather than triggering fallback handling | 
 | // in the renderer after it receives a `CommitNavigation()` IPC. | 
 | // The test disables the delay of creating the speculative RFH since it | 
 | // will check the created speculative RFH for a failing request. The speculative | 
 | // RFH will not be created after receiving the 404 response. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTestWithoutSpeculativeRFHDelay, | 
 |                        ObjectTagCrossSiteNavigationWithHTTPError) { | 
 |   // Set up a test page with a same-site child frame hosted in an <object> tag. | 
 |   // TODO(dcheng): In the future, it might be useful to also have a test where | 
 |   // the child frame is same-site but cross-origin, and have the parent | 
 |   // initiate the navigation in the child frame. | 
 |   GURL url1(embedded_test_server()->GetURL("a.com", "/object-frame.html")); | 
 |   EXPECT_TRUE(NavigateToURL(web_contents(), url1)); | 
 |  | 
 |   // There should be one nested browsing context. | 
 |   EXPECT_EQ(1, EvalJs(web_contents(), "window.length")); | 
 |   // And there should be no fallback content displayed. | 
 |   EXPECT_EQ("", EvalJs(web_contents(), "document.body.innerText")); | 
 |  | 
 |   // <object> fallback handling should never reach ReadyToCommitNavigation. | 
 |   AssertNoReadyToCommitNavigationCalls asserter(web_contents()); | 
 |  | 
 |   // Now navigate the first child to a cross-site page that will result in a | 
 |   // 404. | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   GURL url2(embedded_test_server()->GetURL("b.com", "/page404.html")); | 
 |   TestNavigationManager nav_manager(web_contents(), url2); | 
 |   FrameTreeNode* first_child = root->child_at(0); | 
 |   EXPECT_TRUE(BeginNavigateToURLFromRenderer( | 
 |       first_child->render_manager()->current_frame_host(), url2)); | 
 |   nav_manager.WaitForSpeculativeRenderFrameHostCreation(); | 
 |   // Cross-site navigations always force a speculative RFH to be created. | 
 |   EXPECT_TRUE(first_child->render_manager()->speculative_frame_host()); | 
 |  | 
 |   ASSERT_TRUE(nav_manager.WaitForNavigationFinished()); | 
 |   // There should be no commit... | 
 |   EXPECT_FALSE(nav_manager.was_committed()); | 
 |   // .. and the navigation should have been aborted. | 
 |   EXPECT_FALSE(nav_manager.was_successful()); | 
 |   // Fallback handling should discard the child browsing context and render the | 
 |   // fallback contents. | 
 |   EXPECT_EQ(0, EvalJs(web_contents(), "window.length")); | 
 |   EXPECT_EQ("fallback", EvalJs(web_contents(), "document.body.innerText")); | 
 | } | 
 |  | 
 | // Test that a same-site navigation in <object> that fails with an HTTP error | 
 | // and also subsequently fails to load the body still directly triggers fallback | 
 | // handling, rather than triggering fallback handling in the renderer after it | 
 | // receives a `CommitNavigation()` IPC. | 
 | IN_PROC_BROWSER_TEST_P( | 
 |     SitePerProcessBrowserTest, | 
 |     ObjectTagSameSiteNavigationWithHTTPErrorAndFailedBodyLoad) { | 
 |   // Set up a test page with a same-site child frame hosted in an <object> tag. | 
 |   // TODO(dcheng): In the future, it might be useful to also have a test where | 
 |   // the child frame is same-site but cross-origin, and have the parent | 
 |   // initiate the navigation in the child frame. | 
 |   GURL url1(embedded_test_server()->GetURL("a.com", "/object-frame.html")); | 
 |   EXPECT_TRUE(NavigateToURL(web_contents(), url1)); | 
 |  | 
 |   // There should be one nested browsing context. | 
 |   EXPECT_EQ(1, EvalJs(web_contents(), "window.length")); | 
 |   // And there should be no fallback content displayed. | 
 |   EXPECT_EQ("", EvalJs(web_contents(), "document.body.innerText")); | 
 |  | 
 |   // This test differs from CommitNavigationWithHTTPErrorInObjectTag by | 
 |   // triggering a body load failure. `ObjectNavigationFallbackBodyLoader` | 
 |   // detects this by setting a disconnect handler on the `mojo::Receiver` for | 
 |   // `network:;mojom::URLLoaderClient`. Exercise this code path by: | 
 |   // 1. inserting a test `NavigationThrottle` | 
 |   // 2. replacing the `network::mojom::URLLoaderClient` endpoint with one where | 
 |   //    the corresponding `mojo::Remote` is simply closed at | 
 |   //    `WILL_PROCESS_RESPONSE` time. | 
 |   TestNavigationThrottleInserter navigation_throttle_inserter( | 
 |       web_contents(), | 
 |       base::BindRepeating( | 
 |           [](NavigationThrottleRegistry& registry) -> void { | 
 |             auto throttle = std::make_unique<TestNavigationThrottle>(registry); | 
 |             auto* handle = ®istry.GetNavigationHandle(); | 
 |             throttle->SetCallback( | 
 |                 TestNavigationThrottle::WILL_PROCESS_RESPONSE, | 
 |                 base::BindLambdaForTesting([handle]() { | 
 |                   // Swap out the URL loader client endpoint and just drop the | 
 |                   // mojo::Remote. This will trigger the mojo::Receiver to be | 
 |                   // disconnected, which should still trigger fallback handling | 
 |                   // despite body loading failing. | 
 |                   mojo::Remote<network::mojom::URLLoaderClient> | 
 |                       remote_to_be_dropped; | 
 |                   auto* request = static_cast<NavigationRequest*>(handle); | 
 |                   request->mutable_url_loader_client_endpoints_for_testing() | 
 |                       ->url_loader_client = | 
 |                       remote_to_be_dropped.BindNewPipeAndPassReceiver(); | 
 |                 })); | 
 |             registry.AddThrottle(std::move(throttle)); | 
 |           })); | 
 |  | 
 |   // <object> fallback handling should never reach ReadyToCommitNavigation. | 
 |   AssertNoReadyToCommitNavigationCalls asserter(web_contents()); | 
 |  | 
 |   // Now navigate the first child to a same-site page that will result in a 404, | 
 |   // though the body loading will fail. Note that with subframe RenderDocument, | 
 |   // this will create a speculative RFH. | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   GURL url2(embedded_test_server()->GetURL("a.com", "/page404.html")); | 
 |   TestNavigationManager nav_manager(web_contents(), url2); | 
 |   FrameTreeNode* first_child = root->child_at(0); | 
 |   EXPECT_TRUE(BeginNavigateToURLFromRenderer( | 
 |       first_child->render_manager()->current_frame_host(), url2)); | 
 |  | 
 |   const bool using_speculative_rfh = | 
 |       !!first_child->render_manager()->speculative_frame_host(); | 
 |   // Speculative RFH will not be created at this point if we enable deferring. | 
 |   EXPECT_EQ(using_speculative_rfh, | 
 |             GetRenderDocumentLevel() >= RenderDocumentLevel::kSubframe && | 
 |                 !base::FeatureList::IsEnabled( | 
 |                     features::kDeferSpeculativeRFHCreation)); | 
 |  | 
 |   ASSERT_TRUE(nav_manager.WaitForNavigationFinished()); | 
 |   // There should be no commit... | 
 |   EXPECT_FALSE(nav_manager.was_committed()); | 
 |   // .. and the navigation should have been aborted. | 
 |   EXPECT_FALSE(nav_manager.was_successful()); | 
 |   // Fallback handling should discard the child browsing context and render the | 
 |   // fallback contents. | 
 |   EXPECT_EQ(0, EvalJs(web_contents(), "window.length")); | 
 |   EXPECT_EQ("fallback", EvalJs(web_contents(), "document.body.innerText")); | 
 | } | 
 |  | 
 | // Test that a cross-site navigation in <object> that fails with an HTTP error | 
 | // and also subsequently fails to load the body still directly triggers fallback | 
 | // handling, rather than triggering fallback handling in the renderer after it | 
 | // receives a `CommitNavigation()` IPC. | 
 | // The test disables the delay of creating the speculative RFH since it | 
 | // will check the created speculative RFH for a failing request. The speculative | 
 | // RFH will not be created after receiving the 404 response. | 
 | IN_PROC_BROWSER_TEST_P( | 
 |     SitePerProcessBrowserTestWithoutSpeculativeRFHDelay, | 
 |     ObjectTagCrossSiteNavigationWithHTTPErrorAndFailedBodyLoad) { | 
 |   // Set up a test page with a same-site child frame hosted in an <object> tag. | 
 |   // TODO(dcheng): In the future, it might be useful to also have a test where | 
 |   // the child frame is same-site but cross-origin, and have the parent | 
 |   // initiate the navigation in the child frame. | 
 |   GURL url1(embedded_test_server()->GetURL("a.com", "/object-frame.html")); | 
 |   EXPECT_TRUE(NavigateToURL(web_contents(), url1)); | 
 |  | 
 |   // There should be one nested browsing context. | 
 |   EXPECT_EQ(1, EvalJs(web_contents(), "window.length")); | 
 |   // And there should be no fallback content displayed. | 
 |   EXPECT_EQ("", EvalJs(web_contents(), "document.body.innerText")); | 
 |  | 
 |   // This test differs from CommitNavigationWithHTTPErrorInObjectTag by | 
 |   // triggering a body load failure. `ObjectNavigationFallbackBodyLoader` | 
 |   // detects this by setting a disconnect handler on the `mojo::Receiver` for | 
 |   // `network:;mojom::URLLoaderClient`. Exercise this code path by: | 
 |   // 1. inserting a test `NavigationThrottle` | 
 |   // 2. replacing the `network::mojom::URLLoaderClient` endpoint with one where | 
 |   //    the corresponding `mojo::Remote` is simply closed at | 
 |   //    `WILL_PROCESS_RESPONSE` time. | 
 |   TestNavigationThrottleInserter navigation_throttle_inserter( | 
 |       web_contents(), | 
 |       base::BindRepeating( | 
 |           [](NavigationThrottleRegistry& registry) -> void { | 
 |             auto throttle = std::make_unique<TestNavigationThrottle>(registry); | 
 |             auto* handle = ®istry.GetNavigationHandle(); | 
 |             throttle->SetCallback( | 
 |                 TestNavigationThrottle::WILL_PROCESS_RESPONSE, | 
 |                 base::BindLambdaForTesting([handle]() { | 
 |                   // Swap out the URL loader client endpoint and just drop the | 
 |                   // mojo::Remote. This will trigger the mojo::Receiver to be | 
 |                   // disconnected, which should still trigger fallback handling | 
 |                   // despite body loading failing. | 
 |                   mojo::Remote<network::mojom::URLLoaderClient> | 
 |                       remote_to_be_dropped; | 
 |                   auto* request = static_cast<NavigationRequest*>(handle); | 
 |                   request->mutable_url_loader_client_endpoints_for_testing() | 
 |                       ->url_loader_client = | 
 |                       remote_to_be_dropped.BindNewPipeAndPassReceiver(); | 
 |                 })); | 
 |             registry.AddThrottle(std::move(throttle)); | 
 |           })); | 
 |  | 
 |   // <object> fallback handling should never reach ReadyToCommitNavigation. | 
 |   AssertNoReadyToCommitNavigationCalls asserter(web_contents()); | 
 |  | 
 |   // Now navigate the first child to a cross-site page that will result in a | 
 |   // 404, though the body loading will fail. | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   GURL url2(embedded_test_server()->GetURL("b.com", "/page404.html")); | 
 |   TestNavigationManager nav_manager(web_contents(), url2); | 
 |   FrameTreeNode* first_child = root->child_at(0); | 
 |   EXPECT_TRUE(BeginNavigateToURLFromRenderer( | 
 |       first_child->render_manager()->current_frame_host(), url2)); | 
 |   nav_manager.WaitForSpeculativeRenderFrameHostCreation(); | 
 |   // Cross-site navigations always force a speculative RFH to be created. | 
 |   EXPECT_TRUE(first_child->render_manager()->speculative_frame_host()); | 
 |  | 
 |   ASSERT_TRUE(nav_manager.WaitForNavigationFinished()); | 
 |   // There should be no commit... | 
 |   EXPECT_FALSE(nav_manager.was_committed()); | 
 |   // .. and the navigation should have been aborted. | 
 |   EXPECT_FALSE(nav_manager.was_successful()); | 
 |   // Fallback handling should discard the child browsing context and render the | 
 |   // fallback contents. | 
 |   EXPECT_EQ(0, EvalJs(web_contents(), "window.length")); | 
 |   EXPECT_EQ("fallback", EvalJs(web_contents(), "document.body.innerText")); | 
 | } | 
 |  | 
 | // Test that a same-site navigation in <object> that fails with a network error | 
 | // directly triggers fallback handling, rather than triggering fallback handling | 
 | // in the renderer after it receives a `CommitFailedNavigation()` IPC. | 
 | // The test disables the delay of creating the speculative RFH since it | 
 | // will check the created speculative RFH for a failing request. The speculative | 
 | // RFH will not be created after the network error. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTestWithoutSpeculativeRFHDelay, | 
 |                        ObjectTagSameSiteNavigationWithNetworkError) { | 
 |   // Set up a test page with a same-site child frame hosted in an <object> tag. | 
 |   GURL url1(embedded_test_server()->GetURL("a.com", "/object-frame.html")); | 
 |   EXPECT_TRUE(NavigateToURL(web_contents(), url1)); | 
 |  | 
 |   // <object> fallback handling should never reach ReadyToCommitNavigation. | 
 |   AssertNoReadyToCommitNavigationCalls asserter(web_contents()); | 
 |  | 
 |   // Now navigate the first child to a same-site page that will result in a | 
 |   // network error. Note that with subframe RenderDocument, this will create a | 
 |   // speculative RFH. | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   GURL error_url(embedded_test_server()->GetURL("a.com", "/empty.html")); | 
 |   std::unique_ptr<URLLoaderInterceptor> interceptor = | 
 |       URLLoaderInterceptor::SetupRequestFailForURL(error_url, | 
 |                                                    net::ERR_CONNECTION_REFUSED); | 
 |   TestNavigationManager nav_manager(web_contents(), error_url); | 
 |   FrameTreeNode* first_child = root->child_at(0); | 
 |   EXPECT_TRUE(BeginNavigateToURLFromRenderer( | 
 |       first_child->render_manager()->current_frame_host(), error_url)); | 
 |   if (GetRenderDocumentLevel() >= RenderDocumentLevel::kSubframe) { | 
 |     nav_manager.WaitForSpeculativeRenderFrameHostCreation(); | 
 |   } | 
 |  | 
 |   const bool using_speculative_rfh = | 
 |       !!first_child->render_manager()->speculative_frame_host(); | 
 |   EXPECT_EQ(using_speculative_rfh, | 
 |             GetRenderDocumentLevel() >= RenderDocumentLevel::kSubframe); | 
 |  | 
 |   // `WaitForResponse()` should signal failure by returning `false` false since | 
 |   // the URLLoaderInterceptor forces a network error. | 
 |   EXPECT_FALSE(nav_manager.WaitForResponse()); | 
 |  | 
 |   ASSERT_TRUE(nav_manager.WaitForNavigationFinished()); | 
 |   EXPECT_FALSE(nav_manager.was_committed()); | 
 |  | 
 |   // Make sure that the speculative RFH has been cleaned up, if needed. | 
 |   EXPECT_EQ(nullptr, first_child->render_manager()->speculative_frame_host()); | 
 | } | 
 |  | 
 | // Test that a cross-site navigation in <object> that fails with a network error | 
 | // directly triggers fallback handling, rather than triggering fallback handling | 
 | // in the renderer after it receives a `CommitFailedNavigation()` IPC. | 
 | // The test disables the delay of creating the speculative RFH since it | 
 | // will check the created speculative RFH for a failing request. The speculative | 
 | // RFH will not be created after the network error. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTestWithoutSpeculativeRFHDelay, | 
 |                        ObjectTagCrossSiteNavigationWithNetworkError) { | 
 |   // Set up a test page with a same-site child frame hosted in an <object> tag. | 
 |   GURL url1(embedded_test_server()->GetURL("a.com", "/object-frame.html")); | 
 |   EXPECT_TRUE(NavigateToURL(web_contents(), url1)); | 
 |  | 
 |   // <object> fallback handling should never reach ReadyToCommitNavigation. | 
 |   AssertNoReadyToCommitNavigationCalls asserter(web_contents()); | 
 |  | 
 |   // Now navigate the first child to a cross-site page that will result in a | 
 |   // network error. | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   GURL error_url(embedded_test_server()->GetURL("b.com", "/empty.html")); | 
 |   std::unique_ptr<URLLoaderInterceptor> interceptor = | 
 |       URLLoaderInterceptor::SetupRequestFailForURL(error_url, | 
 |                                                    net::ERR_CONNECTION_REFUSED); | 
 |   TestNavigationManager nav_manager(web_contents(), error_url); | 
 |   FrameTreeNode* first_child = root->child_at(0); | 
 |   EXPECT_TRUE(BeginNavigateToURLFromRenderer( | 
 |       first_child->render_manager()->current_frame_host(), error_url)); | 
 |   nav_manager.WaitForSpeculativeRenderFrameHostCreation(); | 
 |   // Cross-site navigations always force a speculative RFH to be created. | 
 |   EXPECT_TRUE(first_child->render_manager()->speculative_frame_host()); | 
 |  | 
 |   // `WaitForResponse()` should signal failure by returning `false` false since | 
 |   // the URLLoaderInterceptor forces a network error. | 
 |   EXPECT_FALSE(nav_manager.WaitForResponse()); | 
 |  | 
 |   ASSERT_TRUE(nav_manager.WaitForNavigationFinished()); | 
 |   EXPECT_FALSE(nav_manager.was_committed()); | 
 |  | 
 |   // Make sure that the speculative RFH has been cleaned up, if needed. | 
 |   EXPECT_EQ(nullptr, first_child->render_manager()->speculative_frame_host()); | 
 | } | 
 |  | 
 | class SitePerProcessBrowserTestWithLeakDetector | 
 |     : public SitePerProcessBrowserTest { | 
 |  public: | 
 |   void SetUpCommandLine(base::CommandLine* command_line) override { | 
 |     SitePerProcessBrowserTest::SetUpCommandLine(command_line); | 
 |     // Using the LeakDetector requires exposing GC. | 
 |     command_line->AppendSwitchASCII(blink::switches::kJavaScriptFlags, | 
 |                                     "--expose-gc"); | 
 |   } | 
 | }; | 
 |  | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTestWithLeakDetector, | 
 |                        CloseWebContentsWithSpeculativeRenderFrameHost) { | 
 |   const GURL url1(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   ASSERT_TRUE(NavigateToURL(web_contents(), url1)); | 
 |  | 
 |   // Open a popup in B. This is to prevent any fast shutdown shenanigans that | 
 |   // might otherwise happen when the speculative RFH is discarded later. | 
 |   Shell* new_shell = | 
 |       OpenPopup(web_contents(), | 
 |                 embedded_test_server()->GetURL("b.com", "/title1.html"), ""); | 
 |   ASSERT_TRUE(new_shell); | 
 |  | 
 |   mojo::Remote<blink::mojom::LeakDetector> leak_detector_remote; | 
 |   new_shell->web_contents()->GetPrimaryMainFrame()->GetProcess()->BindReceiver( | 
 |       leak_detector_remote.BindNewPipeAndPassReceiver()); | 
 |  | 
 |   // One live document is expected from the newly opened popup. | 
 |   { | 
 |     base::test::TestFuture<blink::mojom::LeakDetectionResultPtr> result_future; | 
 |     leak_detector_remote->PerformLeakDetection(result_future.GetCallback()); | 
 |     auto result = result_future.Take(); | 
 |     EXPECT_EQ(1u, result->number_of_live_documents); | 
 |     // Note: the number of live frames includes remote frames. | 
 |     EXPECT_EQ(2u, result->number_of_live_frames); | 
 |   } | 
 |  | 
 |   // Start a navigation to B, but don't let it commit. This should associate a | 
 |   // speculative RFH with the main frame. | 
 |   const GURL url2(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |   TestNavigationManager nav_manager(web_contents(), url2); | 
 |   ASSERT_TRUE(BeginNavigateToURLFromRenderer(web_contents(), url2)); | 
 |   ASSERT_TRUE(nav_manager.WaitForResponse()); | 
 |  | 
 |   // Speculative RFH should be created in B, increasing the number of live | 
 |   // documents and frames. | 
 |   { | 
 |     base::test::TestFuture<blink::mojom::LeakDetectionResultPtr> result_future; | 
 |     leak_detector_remote->PerformLeakDetection(result_future.GetCallback()); | 
 |     auto result = result_future.Take(); | 
 |     EXPECT_EQ(2u, result->number_of_live_documents); | 
 |     // Note: the number of live frames includes remote frames. | 
 |     EXPECT_EQ(3u, result->number_of_live_frames); | 
 |   } | 
 |  | 
 |   // Close the WebContents associated with the speculative RFH. | 
 |   shell()->Close(); | 
 |   // Synchronize with the renderer. | 
 |   EXPECT_TRUE(ExecJs(new_shell, "")); | 
 |  | 
 |   // The resources associated with the speculative RFH should be freed now, as | 
 |   // well as the original frame from the now closed shell. | 
 |   { | 
 |     base::test::TestFuture<blink::mojom::LeakDetectionResultPtr> result_future; | 
 |     leak_detector_remote->PerformLeakDetection(result_future.GetCallback()); | 
 |     auto result = result_future.Take(); | 
 |     EXPECT_EQ(1u, result->number_of_live_documents); | 
 |     // Note: the number of live frames includes remote frames. | 
 |     EXPECT_EQ(1u, result->number_of_live_frames); | 
 |   } | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTestWithLeakDetector, | 
 |                        DetachFrameWithSpeculativeRenderFrameHost) { | 
 |   const GURL url1(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(a)")); | 
 |   ASSERT_TRUE(NavigateToURL(web_contents(), url1)); | 
 |  | 
 |   // Open a popup in B. This is to prevent any fast shutdown shenanigans that | 
 |   // might otherwise happen when the speculative RFH is discarded later. | 
 |   Shell* new_shell = | 
 |       OpenPopup(web_contents(), | 
 |                 embedded_test_server()->GetURL("b.com", "/title1.html"), ""); | 
 |   ASSERT_TRUE(new_shell); | 
 |  | 
 |   mojo::Remote<blink::mojom::LeakDetector> leak_detector_remote; | 
 |   new_shell->web_contents()->GetPrimaryMainFrame()->GetProcess()->BindReceiver( | 
 |       leak_detector_remote.BindNewPipeAndPassReceiver()); | 
 |  | 
 |   // One live document is expected from the newly opened popup. | 
 |   { | 
 |     base::test::TestFuture<blink::mojom::LeakDetectionResultPtr> result_future; | 
 |     leak_detector_remote->PerformLeakDetection(result_future.GetCallback()); | 
 |     auto result = result_future.Take(); | 
 |     EXPECT_EQ(1u, result->number_of_live_documents); | 
 |     // Note: the number of live frames includes remote frames. | 
 |     EXPECT_EQ(3u, result->number_of_live_frames); | 
 |   } | 
 |  | 
 |   // Start a navigation to B in the iframe, but don't let it commit. This should | 
 |   // associate a speculative RFH with the child frame. | 
 |   const GURL url2(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |   TestNavigationManager nav_manager(web_contents(), url2); | 
 |   ASSERT_TRUE(BeginNavigateToURLFromRenderer(web_contents() | 
 |                                                  ->GetPrimaryFrameTree() | 
 |                                                  .root() | 
 |                                                  ->current_frame_host() | 
 |                                                  ->child_at(0), | 
 |                                              url2)); | 
 |   ASSERT_TRUE(nav_manager.WaitForResponse()); | 
 |  | 
 |   // Speculative RFH should be created in B, increasing the number of live | 
 |   // documents and frames. | 
 |   { | 
 |     base::test::TestFuture<blink::mojom::LeakDetectionResultPtr> result_future; | 
 |     leak_detector_remote->PerformLeakDetection(result_future.GetCallback()); | 
 |     auto result = result_future.Take(); | 
 |     EXPECT_EQ(2u, result->number_of_live_documents); | 
 |     // Note: the number of live frames includes remote frames. | 
 |     EXPECT_EQ(4u, result->number_of_live_frames); | 
 |   } | 
 |  | 
 |   // Detach the <iframe> associated with the speculative RFH. | 
 |   EXPECT_TRUE( | 
 |       ExecJs(web_contents(), "document.querySelector('iframe').remove()")); | 
 |   // Synchronize with the renderer. | 
 |   EXPECT_TRUE(ExecJs(new_shell, "")); | 
 |  | 
 |   // The resources associated with the speculative RFH should be freed now. | 
 |   { | 
 |     base::test::TestFuture<blink::mojom::LeakDetectionResultPtr> result_future; | 
 |     leak_detector_remote->PerformLeakDetection(result_future.GetCallback()); | 
 |     auto result = result_future.Take(); | 
 |     EXPECT_EQ(1u, result->number_of_live_documents); | 
 |     // Note: the number of live frames includes remote frames. | 
 |     EXPECT_EQ(2u, result->number_of_live_frames); | 
 |   } | 
 | } | 
 |  | 
 | #if BUILDFLAG(IS_ANDROID) | 
 |  | 
 | namespace { | 
 |  | 
 | class MockEventHandlerAndroid : public ui::EventHandlerAndroid { | 
 |  public: | 
 |   bool OnTouchEvent(const ui::MotionEventAndroid& event) override { | 
 |     did_receive_event_ = true; | 
 |     return true; | 
 |   } | 
 |  | 
 |   bool did_receive_event() { return did_receive_event_; } | 
 |  | 
 |  private: | 
 |   bool did_receive_event_ = false; | 
 | }; | 
 |  | 
 | }  // namespace | 
 |  | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        SpeculativeRenderFrameHostDoesNotReceiveInput) { | 
 |   GURL url1(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url1)); | 
 |  | 
 |   RenderWidgetHostViewAndroid* rwhva = | 
 |       static_cast<RenderWidgetHostViewAndroid*>( | 
 |           shell()->web_contents()->GetRenderWidgetHostView()); | 
 |   ui::ViewAndroid* rwhva_native_view = rwhva->GetNativeView(); | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // Start a cross-site navigation. | 
 |   GURL url2(embedded_test_server()->GetURL("b.com", "/title2.html")); | 
 |   TestNavigationManager nav_manager(web_contents(), url2); | 
 |   shell()->LoadURL(url2); | 
 |  | 
 |   // Wait for the request, but don't commit it yet. This should create a | 
 |   // speculative RenderFrameHost. | 
 |   nav_manager.WaitForSpeculativeRenderFrameHostCreation(); | 
 |   RenderFrameHostImpl* root_speculative_rfh = | 
 |       root->render_manager()->speculative_frame_host(); | 
 |   EXPECT_TRUE(root_speculative_rfh); | 
 |   RenderWidgetHostViewAndroid* rwhv_speculative = | 
 |       static_cast<RenderWidgetHostViewAndroid*>( | 
 |           root_speculative_rfh->GetView()); | 
 |   ui::ViewAndroid* rwhv_speculative_native_view = | 
 |       rwhv_speculative->GetNativeView(); | 
 |  | 
 |   ui::ViewAndroid* root_view = web_contents()->GetView()->GetNativeView(); | 
 |   EXPECT_TRUE(root_view); | 
 |  | 
 |   MockEventHandlerAndroid mock_handler; | 
 |   rwhva_native_view->set_event_handler(&mock_handler); | 
 |   MockEventHandlerAndroid mock_handler_speculative; | 
 |   rwhv_speculative_native_view->set_event_handler(&mock_handler_speculative); | 
 |   // Avoid having the root try to handle the following event. | 
 |   root_view->set_event_handler(nullptr); | 
 |  | 
 |   auto size = root_view->GetSizeDIPs(); | 
 |   float x = size.width() / 2; | 
 |   float y = size.height() / 2; | 
 |   ui::MotionEventAndroid::Pointer pointer0(0, x, y, 0, 0, 0, 0, 0, 0); | 
 |  | 
 |   JNIEnv* env = base::android::AttachCurrentThread(); | 
 |   base::android::ScopedJavaLocalRef<jobject> obj = | 
 |       JNI_MotionEvent::Java_MotionEvent_obtain( | 
 |           env, /*downTime=*/0, /*eventTime=*/0, /*action=*/0, /*x=*/0, /*y=*/0, | 
 |           /*metaState=*/0); | 
 |   auto event = ui::MotionEventAndroidFactory::CreateFromJava( | 
 |       env, obj, | 
 |       /*pix_to_dip=*/1.f / root_view->GetDipScale(), | 
 |       /*ticks_x=*/0.f, | 
 |       /*ticks_y=*/0.f, | 
 |       /*tick_multiplier=*/0.f, | 
 |       /*oldest_event_time=*/base::TimeTicks(), | 
 |       /*android_action=*/0, | 
 |       /*pointer_count=*/1, | 
 |       /*history_size=*/0, | 
 |       /*action_index=*/0, | 
 |       /*android_action_button=*/0, | 
 |       /*android_gesture_classification=*/0, | 
 |       /*android_button_state=*/0, | 
 |       /*raw_offset_x_pixels=*/0, | 
 |       /*raw_offset_y_pixels=*/0, | 
 |       /*for_touch_handle=*/false, | 
 |       /*pointer0=*/&pointer0, | 
 |       /*pointer1=*/nullptr); | 
 |   root_view->OnTouchEventForTesting(*event); | 
 |  | 
 |   EXPECT_TRUE(mock_handler.did_receive_event()); | 
 |   EXPECT_FALSE(mock_handler_speculative.did_receive_event()); | 
 | } | 
 |  | 
 | class SitePerProcessBrowserTestWithSubframePriority | 
 |     : public SitePerProcessBrowserTest { | 
 |  public: | 
 |   SitePerProcessBrowserTestWithSubframePriority() { | 
 |     scoped_feature_list_.InitWithFeatures( | 
 |         /* enabled_features= */ {features::kSubframePriorityContribution, | 
 |                                  features::kSubframeImportance}, | 
 |         /* disabled_features= */ {}); | 
 |   } | 
 |  | 
 |  private: | 
 |   base::test::ScopedFeatureList scoped_feature_list_; | 
 | }; | 
 |  | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTestWithSubframePriority, | 
 |                        TestChildProcessImportance) { | 
 |   web_contents()->SetPrimaryPageImportance(ChildProcessImportance::IMPORTANT, | 
 |                                            ChildProcessImportance::MODERATE); | 
 |  | 
 |   // Construct root page with one child in different domain. | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL( | 
 |                    "a.com", "/cross_site_iframe_factory.html?a(b)"))); | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   ASSERT_EQ(1u, root->child_count()); | 
 |   FrameTreeNode* child = root->child_at(0); | 
 |  | 
 |   // Importance should survive initial navigation. | 
 |   EXPECT_EQ(ChildProcessImportance::IMPORTANT, | 
 |             root->current_frame_host()->GetProcess()->GetEffectiveImportance()); | 
 |   EXPECT_EQ( | 
 |       ChildProcessImportance::MODERATE, | 
 |       child->current_frame_host()->GetProcess()->GetEffectiveImportance()); | 
 |  | 
 |   // Setting NORMAL importance for child frame. | 
 |   web_contents()->SetPrimaryPageImportance(ChildProcessImportance::MODERATE, | 
 |                                            ChildProcessImportance::NORMAL); | 
 |   EXPECT_EQ(ChildProcessImportance::MODERATE, | 
 |             root->current_frame_host()->GetProcess()->GetEffectiveImportance()); | 
 |   EXPECT_EQ( | 
 |       ChildProcessImportance::NORMAL, | 
 |       child->current_frame_host()->GetProcess()->GetEffectiveImportance()); | 
 |  | 
 |   // Check setting the same importance for main frame and subframe. | 
 |   web_contents()->SetPrimaryPageImportance(ChildProcessImportance::MODERATE, | 
 |                                            ChildProcessImportance::MODERATE); | 
 |   EXPECT_EQ(ChildProcessImportance::MODERATE, | 
 |             root->current_frame_host()->GetProcess()->GetEffectiveImportance()); | 
 |   EXPECT_EQ( | 
 |       ChildProcessImportance::MODERATE, | 
 |       child->current_frame_host()->GetProcess()->GetEffectiveImportance()); | 
 |  | 
 |   // Check setting importance. | 
 |   web_contents()->SetPrimaryPageImportance(ChildProcessImportance::NORMAL, | 
 |                                            ChildProcessImportance::NORMAL); | 
 |   EXPECT_EQ(ChildProcessImportance::NORMAL, | 
 |             root->current_frame_host()->GetProcess()->GetEffectiveImportance()); | 
 |   EXPECT_EQ( | 
 |       ChildProcessImportance::NORMAL, | 
 |       child->current_frame_host()->GetProcess()->GetEffectiveImportance()); | 
 |   web_contents()->SetPrimaryPageImportance(ChildProcessImportance::IMPORTANT, | 
 |                                            ChildProcessImportance::MODERATE); | 
 |   EXPECT_EQ(ChildProcessImportance::IMPORTANT, | 
 |             root->current_frame_host()->GetProcess()->GetEffectiveImportance()); | 
 |   EXPECT_EQ( | 
 |       ChildProcessImportance::MODERATE, | 
 |       child->current_frame_host()->GetProcess()->GetEffectiveImportance()); | 
 |  | 
 |   // Check importance is maintained if child navigates to new domain. | 
 |   int old_child_process_id = | 
 |       child->current_frame_host()->GetProcess()->GetDeprecatedID(); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer( | 
 |       root->child_at(0), | 
 |       embedded_test_server()->GetURL("foo.com", "/title2.html"))); | 
 |   int new_child_process_id = | 
 |       child->current_frame_host()->GetProcess()->GetDeprecatedID(); | 
 |   EXPECT_NE(old_child_process_id, new_child_process_id); | 
 |   EXPECT_EQ( | 
 |       ChildProcessImportance::MODERATE, | 
 |       child->current_frame_host()->GetProcess()->GetEffectiveImportance()); | 
 |   EXPECT_EQ(ChildProcessImportance::IMPORTANT, | 
 |             root->current_frame_host()->GetProcess()->GetEffectiveImportance()); | 
 |  | 
 |   // Check importance is maintained if root navigates to new domain. | 
 |   int old_root_process_id = | 
 |       root->current_frame_host()->GetProcess()->GetDeprecatedID(); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer( | 
 |       root, embedded_test_server()->GetURL( | 
 |                 "b.com", "/cross_site_iframe_factory.html?b(a)"))); | 
 |   int new_root_process_id = | 
 |       root->current_frame_host()->GetProcess()->GetDeprecatedID(); | 
 |   EXPECT_NE(old_root_process_id, new_root_process_id); | 
 |   EXPECT_EQ(ChildProcessImportance::IMPORTANT, | 
 |             root->current_frame_host()->GetProcess()->GetEffectiveImportance()); | 
 |  | 
 |   ASSERT_EQ(1u, root->child_count()); | 
 |   child = root->child_at(0); | 
 |   int new_child_process_id_2 = | 
 |       child->current_frame_host()->GetProcess()->GetDeprecatedID(); | 
 |   EXPECT_NE(new_child_process_id, new_child_process_id_2); | 
 |   EXPECT_EQ( | 
 |       ChildProcessImportance::MODERATE, | 
 |       child->current_frame_host()->GetProcess()->GetEffectiveImportance()); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        TestChildProcessImportanceWithNormalSubframes) { | 
 |   // In this test case, subframe importance is always NORMAL. With that | 
 |   // WebContents never updates subframe importance. | 
 |   web_contents()->SetPrimaryPageImportance(ChildProcessImportance::MODERATE, | 
 |                                            ChildProcessImportance::NORMAL); | 
 |  | 
 |   // Construct root page with one child in different domain. | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   ASSERT_EQ(1u, root->child_count()); | 
 |   FrameTreeNode* child = root->child_at(0); | 
 |  | 
 |   // Importance should survive initial navigation. | 
 |   EXPECT_EQ(ChildProcessImportance::MODERATE, | 
 |             root->current_frame_host()->GetProcess()->GetEffectiveImportance()); | 
 |   EXPECT_EQ( | 
 |       ChildProcessImportance::NORMAL, | 
 |       child->current_frame_host()->GetProcess()->GetEffectiveImportance()); | 
 |  | 
 |   // Check setting importance. | 
 |   web_contents()->SetPrimaryPageImportance(ChildProcessImportance::NORMAL, | 
 |                                            ChildProcessImportance::NORMAL); | 
 |   EXPECT_EQ(ChildProcessImportance::NORMAL, | 
 |             root->current_frame_host()->GetProcess()->GetEffectiveImportance()); | 
 |   EXPECT_EQ( | 
 |       ChildProcessImportance::NORMAL, | 
 |       child->current_frame_host()->GetProcess()->GetEffectiveImportance()); | 
 |   web_contents()->SetPrimaryPageImportance(ChildProcessImportance::IMPORTANT, | 
 |                                            ChildProcessImportance::NORMAL); | 
 |   EXPECT_EQ(ChildProcessImportance::IMPORTANT, | 
 |             root->current_frame_host()->GetProcess()->GetEffectiveImportance()); | 
 |   EXPECT_EQ( | 
 |       ChildProcessImportance::NORMAL, | 
 |       child->current_frame_host()->GetProcess()->GetEffectiveImportance()); | 
 |  | 
 |   // Check importance is maintained if child navigates to new domain. | 
 |   int old_child_process_id = | 
 |       child->current_frame_host()->GetProcess()->GetDeprecatedID(); | 
 |   GURL url = embedded_test_server()->GetURL("foo.com", "/title2.html"); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(0), url)); | 
 |   int new_child_process_id = | 
 |       child->current_frame_host()->GetProcess()->GetDeprecatedID(); | 
 |   EXPECT_NE(old_child_process_id, new_child_process_id); | 
 |   EXPECT_EQ( | 
 |       ChildProcessImportance::NORMAL, | 
 |       child->current_frame_host()->GetProcess()->GetEffectiveImportance()); | 
 |   EXPECT_EQ(ChildProcessImportance::IMPORTANT, | 
 |             root->current_frame_host()->GetProcess()->GetEffectiveImportance()); | 
 |  | 
 |   // Check importance is maintained if root navigates to new domain. | 
 |   int old_root_process_id = | 
 |       root->current_frame_host()->GetProcess()->GetDeprecatedID(); | 
 |   child = nullptr;  // Going to navigate root to page without any child. | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(root, url)); | 
 |   EXPECT_EQ(0u, root->child_count()); | 
 |   int new_root_process_id = | 
 |       root->current_frame_host()->GetProcess()->GetDeprecatedID(); | 
 |   EXPECT_NE(old_root_process_id, new_root_process_id); | 
 |   EXPECT_EQ(ChildProcessImportance::IMPORTANT, | 
 |             root->current_frame_host()->GetProcess()->GetEffectiveImportance()); | 
 | } | 
 |  | 
 | class TouchSelectionControllerClientTestWrapper | 
 |     : public ui::TouchSelectionControllerClient { | 
 |  public: | 
 |   explicit TouchSelectionControllerClientTestWrapper( | 
 |       ui::TouchSelectionControllerClient* client) | 
 |       : expected_event_(ui::SELECTION_HANDLES_SHOWN), client_(client) {} | 
 |  | 
 |   TouchSelectionControllerClientTestWrapper( | 
 |       const TouchSelectionControllerClientTestWrapper&) = delete; | 
 |   TouchSelectionControllerClientTestWrapper& operator=( | 
 |       const TouchSelectionControllerClientTestWrapper&) = delete; | 
 |  | 
 |   ~TouchSelectionControllerClientTestWrapper() override {} | 
 |  | 
 |   void InitWaitForSelectionEvent(ui::SelectionEventType expected_event) { | 
 |     DCHECK(!run_loop_); | 
 |     expected_event_ = expected_event; | 
 |     run_loop_ = std::make_unique<base::RunLoop>(); | 
 |   } | 
 |  | 
 |   void Wait() { | 
 |     DCHECK(run_loop_); | 
 |     run_loop_->Run(); | 
 |     run_loop_.reset(); | 
 |   } | 
 |  | 
 |  private: | 
 |   // TouchSelectionControllerClient: | 
 |   void OnSelectionEvent(ui::SelectionEventType event) override { | 
 |     client_->OnSelectionEvent(event); | 
 |     if (run_loop_ && event == expected_event_) | 
 |       run_loop_->Quit(); | 
 |   } | 
 |  | 
 |   bool SupportsAnimation() const override { | 
 |     return client_->SupportsAnimation(); | 
 |   } | 
 |  | 
 |   void SetNeedsAnimate() override { client_->SetNeedsAnimate(); } | 
 |  | 
 |   void MoveCaret(const gfx::PointF& position) override { | 
 |     client_->MoveCaret(position); | 
 |   } | 
 |  | 
 |   void MoveRangeSelectionExtent(const gfx::PointF& extent) override { | 
 |     client_->MoveRangeSelectionExtent(extent); | 
 |   } | 
 |  | 
 |   void SelectBetweenCoordinates(const gfx::PointF& base, | 
 |                                 const gfx::PointF& extent) override { | 
 |     client_->SelectBetweenCoordinates(base, extent); | 
 |   } | 
 |  | 
 |   std::unique_ptr<ui::TouchHandleDrawable> CreateDrawable() override { | 
 |     return client_->CreateDrawable(); | 
 |   } | 
 |  | 
 |   void DidScroll() override {} | 
 |  | 
 |   void OnDragUpdate(const ui::TouchSelectionDraggable::Type type, | 
 |                     const gfx::PointF& position) override {} | 
 |  | 
 |   ui::SelectionEventType expected_event_; | 
 |   std::unique_ptr<base::RunLoop> run_loop_; | 
 |   // Not owned. | 
 |   raw_ptr<ui::TouchSelectionControllerClient, DanglingUntriaged> client_; | 
 | }; | 
 |  | 
 | class TouchSelectionControllerClientAndroidSiteIsolationTest | 
 |     : public SitePerProcessBrowserTest { | 
 |  public: | 
 |   TouchSelectionControllerClientAndroidSiteIsolationTest() | 
 |       : root_rwhv_(nullptr), | 
 |         child_rwhv_(nullptr), | 
 |         child_frame_tree_node_(nullptr), | 
 |         selection_controller_client_(nullptr) {} | 
 |  | 
 |   void SetUpCommandLine(base::CommandLine* command_line) override { | 
 |     SitePerProcessBrowserTestBase::SetUpCommandLine(command_line); | 
 |     IsolateAllSitesForTesting(command_line); | 
 |   } | 
 |  | 
 |   RenderWidgetHostViewAndroid* GetRenderWidgetHostViewAndroid() { | 
 |     return static_cast<RenderWidgetHostViewAndroid*>( | 
 |         shell()->web_contents()->GetRenderWidgetHostView()); | 
 |   } | 
 |  | 
 |   void SelectWithLongPress(gfx::Point point) { | 
 |     // Get main frame view for event insertion. | 
 |     RenderWidgetHostViewAndroid* main_view = GetRenderWidgetHostViewAndroid(); | 
 |  | 
 |     SendTouch(main_view, ui::MotionEvent::Action::DOWN, point); | 
 |     // action_timeout() is far longer than needed for a LongPress, so we use | 
 |     // a custom timeout here. | 
 |     DelayBy(base::Milliseconds(2000)); | 
 |     SendTouch(main_view, ui::MotionEvent::Action::UP, point); | 
 |   } | 
 |  | 
 |   void SimpleTap(gfx::Point point) { | 
 |     // Get main frame view for event insertion. | 
 |     RenderWidgetHostViewAndroid* main_view = GetRenderWidgetHostViewAndroid(); | 
 |  | 
 |     SendTouch(main_view, ui::MotionEvent::Action::DOWN, point); | 
 |     // tiny_timeout() is way shorter than a reasonable user-created tap gesture, | 
 |     // so we use a custom timeout here. | 
 |     DelayBy(base::Milliseconds(300)); | 
 |     SendTouch(main_view, ui::MotionEvent::Action::UP, point); | 
 |   } | 
 |  | 
 |   void SetupTest() { | 
 |     GURL test_url(embedded_test_server()->GetURL( | 
 |         "a.com", "/cross_site_iframe_factory.html?a(a)")); | 
 |     EXPECT_TRUE(NavigateToURL(shell(), test_url)); | 
 |     frame_observer_ = std::make_unique<RenderFrameSubmissionObserver>( | 
 |         shell()->web_contents()); | 
 |     FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) | 
 |                               ->GetPrimaryFrameTree() | 
 |                               .root(); | 
 |     EXPECT_EQ( | 
 |         " Site A\n" | 
 |         "   +--Site A\n" | 
 |         "Where A = http://a.com/", | 
 |         FrameTreeVisualizer().DepictFrameTree(root)); | 
 |     TestNavigationObserver observer(shell()->web_contents()); | 
 |     EXPECT_EQ(1u, root->child_count()); | 
 |     child_frame_tree_node_ = root->child_at(0); | 
 |  | 
 |     root_rwhv_ = static_cast<RenderWidgetHostViewAndroid*>( | 
 |         root->current_frame_host()->GetRenderWidgetHost()->GetView()); | 
 |     selection_controller_client_ = | 
 |         new TouchSelectionControllerClientTestWrapper( | 
 |             static_cast<TouchSelectionControllerClientManagerAndroid*>( | 
 |                 root_rwhv_->GetTouchSelectionControllerClientManager())); | 
 |     root_rwhv_->SetSelectionControllerClientForTesting( | 
 |         base::WrapUnique(selection_controller_client_.get())); | 
 |  | 
 |     // We need to load the desired subframe and then wait until it's stable, | 
 |     // i.e. generates no new compositor frames for some reasonable time period: | 
 |     // a stray frame between touch selection's pre-handling of GestureLongPress | 
 |     // and the expected frame containing the selected region can confuse the | 
 |     // TouchSelectionController, causing it to fail to show selection handles. | 
 |     // Note this is an issue with the TouchSelectionController in general, and | 
 |     // not a property of this test. | 
 |     GURL child_url( | 
 |         embedded_test_server()->GetURL("b.com", "/touch_selection.html")); | 
 |     EXPECT_TRUE( | 
 |         NavigateToURLFromRenderer(child_frame_tree_node_.get(), child_url)); | 
 |     EXPECT_EQ( | 
 |         " Site A ------------ proxies for B\n" | 
 |         "   +--Site B ------- proxies for A\n" | 
 |         "Where A = http://a.com/\n" | 
 |         "      B = http://b.com/", | 
 |         FrameTreeVisualizer().DepictFrameTree(root)); | 
 |     // The child will change with the cross-site navigation. It shouldn't change | 
 |     // after this. | 
 |     child_frame_tree_node_ = root->child_at(0); | 
 |     WaitForHitTestData(child_frame_tree_node_->current_frame_host()); | 
 |  | 
 |     child_rwhv_ = static_cast<RenderWidgetHostViewChildFrame*>( | 
 |         child_frame_tree_node_->current_frame_host() | 
 |             ->GetRenderWidgetHost() | 
 |             ->GetView()); | 
 |  | 
 |     EXPECT_EQ(child_url, observer.last_navigation_url()); | 
 |     EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |   } | 
 |  | 
 |   // This must be called before the main-frame's RenderWidgetHostView is freed, | 
 |   // else we'll have a nullptr dereference on shutdown. | 
 |   void ShutdownTest() { | 
 |     ASSERT_TRUE(frame_observer_); | 
 |     frame_observer_.reset(); | 
 |   } | 
 |  | 
 |   gfx::PointF GetPointInChild() { | 
 |     gfx::PointF point_f; | 
 |     std::string str = EvalJs(child_frame_tree_node_->current_frame_host(), | 
 |                              "get_top_left_of_text()") | 
 |                           .ExtractString(); | 
 |     ConvertJSONToPoint(str, &point_f); | 
 |     // Offset the point so that it is within the text. Character dimensions are | 
 |     // based on the font size in `touch_selection.html`. | 
 |     constexpr int kCharacterWidth = 15; | 
 |     constexpr int kCharacterHeight = 15; | 
 |     point_f.Offset(2 * kCharacterWidth, 0.5f * kCharacterHeight); | 
 |     point_f = child_rwhv()->TransformPointToRootCoordSpaceF(point_f); | 
 |     return point_f; | 
 |   } | 
 |  | 
 |   void VerifyHandlePosition() { | 
 |     // Check that selection handles are close to the selection range. | 
 |     // The test will timeout if this never happens. | 
 |     ui::TouchSelectionController* touch_selection_controller = | 
 |         root_rwhv()->touch_selection_controller(); | 
 |  | 
 |     bool handles_in_place = false; | 
 |     while (!handles_in_place) { | 
 |       gfx::PointF selection_start = | 
 |           touch_selection_controller->GetStartPosition(); | 
 |       gfx::PointF selection_end = touch_selection_controller->GetEndPosition(); | 
 |       gfx::RectF handle_start = | 
 |           touch_selection_controller->GetStartHandleRect(); | 
 |       gfx::RectF handle_end = touch_selection_controller->GetEndHandleRect(); | 
 |  | 
 |       // Not all Android bots seem to actually show the handle, so check first. | 
 |       if (handle_start.IsEmpty()) { | 
 |         handles_in_place = true; | 
 |       } else { | 
 |         bool has_end_handle = | 
 |             !touch_selection_controller->GetEndHandleRect().IsEmpty(); | 
 |         // handle_start.y() defined the top of the handle's rect, and x() is | 
 |         // left. | 
 |         bool start_near_y = | 
 |             std::abs(selection_start.y() - handle_start.y()) <= 3.f; | 
 |         bool start_in_x_range = selection_start.x() >= handle_start.x() && | 
 |                                 selection_start.x() <= handle_start.right(); | 
 |         bool end_near_y = std::abs(selection_end.y() - handle_end.y()) <= 3.f; | 
 |         bool end_in_x_range = selection_end.x() >= handle_end.x() && | 
 |                               selection_end.x() <= handle_end.right(); | 
 |         handles_in_place = start_near_y && start_in_x_range && end_near_y && | 
 |                            end_in_x_range && has_end_handle; | 
 |       } | 
 |       if (!handles_in_place) | 
 |         DelayBy(base::Milliseconds(100)); | 
 |     } | 
 |   } | 
 |  | 
 |   RenderWidgetHostViewAndroid* root_rwhv() { return root_rwhv_; } | 
 |  | 
 |   RenderWidgetHostViewChildFrame* child_rwhv() { return child_rwhv_; } | 
 |  | 
 |   float PageScaleFactor() { | 
 |     return frame_observer_->LastRenderFrameMetadata().page_scale_factor; | 
 |   } | 
 |  | 
 |   TouchSelectionControllerClientTestWrapper* selection_controller_client() { | 
 |     return selection_controller_client_; | 
 |   } | 
 |  | 
 |   void OnSyntheticGestureSent() { | 
 |     gesture_run_loop_ = std::make_unique<base::RunLoop>(); | 
 |     gesture_run_loop_->Run(); | 
 |   } | 
 |  | 
 |   void OnSyntheticGestureCompleted(SyntheticGesture::Result result) { | 
 |     EXPECT_EQ(SyntheticGesture::GESTURE_FINISHED, result); | 
 |     gesture_run_loop_->Quit(); | 
 |   } | 
 |  | 
 |  protected: | 
 |   void DelayBy(base::TimeDelta delta) { | 
 |     base::RunLoop run_loop; | 
 |     base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( | 
 |         FROM_HERE, run_loop.QuitClosure(), delta); | 
 |     run_loop.Run(); | 
 |   } | 
 |  | 
 |  private: | 
 |   void SendTouch(RenderWidgetHostViewAndroid* view, | 
 |                  ui::MotionEvent::Action action, | 
 |                  gfx::Point point) { | 
 |     DCHECK(action >= ui::MotionEvent::Action::DOWN && | 
 |            action < ui::MotionEvent::Action::CANCEL); | 
 |  | 
 |     ui::MotionEventAndroid::Pointer p(0, point.x(), point.y(), 10, 0, 0, 0, 0, | 
 |                                       0); | 
 |     JNIEnv* env = base::android::AttachCurrentThread(); | 
 |     auto time_ns = (ui::EventTimeForNow() - base::TimeTicks()).InNanoseconds(); | 
 |  | 
 |     base::android::ScopedJavaLocalRef<jobject> obj = | 
 |         JNI_MotionEvent::Java_MotionEvent_obtain( | 
 |             env, /*downTime=*/0, /*eventTime=*/0, /*action=*/0, /*x=*/0, | 
 |             /*y=*/0, /*metaState=*/0); | 
 |     auto touch = ui::MotionEventAndroidFactory::CreateFromJava( | 
 |         env, obj, | 
 |         /*pix_to_dip=*/1.f, | 
 |         /*ticks_x=*/0, | 
 |         /*ticks_y=*/0, | 
 |         /*tick_multiplier=*/0, | 
 |         /*oldest_event_time=*/base::TimeTicks::FromJavaNanoTime(time_ns), | 
 |         /*android_action=*/ui::MotionEventAndroid::GetAndroidAction(action), | 
 |         /*pointer_count=*/1, | 
 |         /*history_size=*/0, | 
 |         /*action_index=*/0, | 
 |         /*android_action_button=*/0, | 
 |         /*android_gesture_classification=*/0, | 
 |         /*android_button_state=*/0, | 
 |         /*raw_offset_x_pixels=*/0, | 
 |         /*raw_offset_y_pixels=*/0, | 
 |         /*for_touch_handle=*/false, | 
 |         /*pointer0=*/&p, | 
 |         /*pointer1=*/nullptr); | 
 |     view->OnTouchEvent(*touch); | 
 |   } | 
 |  | 
 |   raw_ptr<RenderWidgetHostViewAndroid, DanglingUntriaged> root_rwhv_; | 
 |   raw_ptr<RenderWidgetHostViewChildFrame, DanglingUntriaged> child_rwhv_; | 
 |   raw_ptr<FrameTreeNode, DanglingUntriaged> child_frame_tree_node_; | 
 |   std::unique_ptr<RenderFrameSubmissionObserver> frame_observer_; | 
 |   raw_ptr<TouchSelectionControllerClientTestWrapper, DanglingUntriaged> | 
 |       selection_controller_client_; | 
 |  | 
 |   std::unique_ptr<base::RunLoop> gesture_run_loop_; | 
 | }; | 
 |  | 
 | IN_PROC_BROWSER_TEST_P(TouchSelectionControllerClientAndroidSiteIsolationTest, | 
 |                        BasicSelectionIsolatedIframe) { | 
 |   // Load test URL with cross-process child. | 
 |   SetupTest(); | 
 |  | 
 |   EXPECT_EQ(ui::TouchSelectionController::INACTIVE, | 
 |             root_rwhv()->touch_selection_controller()->active_status()); | 
 |   // Find the location of some text to select. | 
 |   gfx::PointF point_f = GetPointInChild(); | 
 |  | 
 |   // Initiate selection with a sequence of events that go through the targeting | 
 |   // system. | 
 |   selection_controller_client()->InitWaitForSelectionEvent( | 
 |       ui::SELECTION_HANDLES_SHOWN); | 
 |  | 
 |   SelectWithLongPress(gfx::Point(point_f.x(), point_f.y())); | 
 |  | 
 |   selection_controller_client()->Wait(); | 
 |  | 
 |   // Check that selection is active and the quick menu is showing. | 
 |   EXPECT_EQ(ui::TouchSelectionController::SELECTION_ACTIVE, | 
 |             root_rwhv()->touch_selection_controller()->active_status()); | 
 |  | 
 |   // Make sure handles are correctly positioned. | 
 |   VerifyHandlePosition(); | 
 |  | 
 |   // Tap inside/outside the iframe and make sure the selection handles go away. | 
 |   selection_controller_client()->InitWaitForSelectionEvent( | 
 |       ui::SELECTION_HANDLES_CLEARED); | 
 |   // Since Android tests may run with page_scale_factor < 1, use an offset a | 
 |   // bigger than +/-1 for doing the inside/outside taps to cancel the selection | 
 |   // handles. | 
 |   gfx::PointF point_inside_iframe = | 
 |       child_rwhv()->TransformPointToRootCoordSpaceF(gfx::PointF(+5.f, +5.f)); | 
 |   SimpleTap(gfx::Point(point_inside_iframe.x(), point_inside_iframe.y())); | 
 |   selection_controller_client()->Wait(); | 
 |  | 
 |   EXPECT_EQ(ui::TouchSelectionController::INACTIVE, | 
 |             root_rwhv()->touch_selection_controller()->active_status()); | 
 |  | 
 |   // Let's wait for the previous events to clear the round-trip to the renders | 
 |   // and back. | 
 |   DelayBy(base::Milliseconds(2000)); | 
 |  | 
 |   // Initiate selection with a sequence of events that go through the targeting | 
 |   // system. Repeat of above but this time we'l cancel the selection by | 
 |   // tapping outside of the OOPIF. | 
 |   selection_controller_client()->InitWaitForSelectionEvent( | 
 |       ui::SELECTION_HANDLES_SHOWN); | 
 |  | 
 |   SelectWithLongPress(gfx::Point(point_f.x(), point_f.y())); | 
 |  | 
 |   selection_controller_client()->Wait(); | 
 |  | 
 |   // Check that selection is active and the quick menu is showing. | 
 |   EXPECT_EQ(ui::TouchSelectionController::SELECTION_ACTIVE, | 
 |             root_rwhv()->touch_selection_controller()->active_status()); | 
 |  | 
 |   // Tap inside/outside the iframe and make sure the selection handles go away. | 
 |   selection_controller_client()->InitWaitForSelectionEvent( | 
 |       ui::SELECTION_HANDLES_CLEARED); | 
 |   // Since Android tests may run with page_scale_factor < 1, use an offset a | 
 |   // bigger than +/-1 for doing the inside/outside taps to cancel the selection | 
 |   // handles. | 
 |   gfx::PointF point_outside_iframe = | 
 |       child_rwhv()->TransformPointToRootCoordSpaceF(gfx::PointF(-5.f, -5.f)); | 
 |   SimpleTap(gfx::Point(point_outside_iframe.x(), point_outside_iframe.y())); | 
 |   selection_controller_client()->Wait(); | 
 |  | 
 |   EXPECT_EQ(ui::TouchSelectionController::INACTIVE, | 
 |             root_rwhv()->touch_selection_controller()->active_status()); | 
 |  | 
 |   // Cleanup before shutting down. | 
 |   ShutdownTest(); | 
 | } | 
 |  | 
 | // This test verifies that the handles associated with an active touch selection | 
 | // are still correctly positioned after a pinch-zoom operation. | 
 | #if BUILDFLAG(IS_ANDROID)  // Flaky on Android.  See https://crbug.com/906204. | 
 | #define MAYBE_SelectionThenPinchInOOPIF DISABLED_SelectionThenPinchInOOPIF | 
 | #else | 
 | #define MAYBE_SelectionThenPinchInOOPIF SelectionThenPinchInOOPIF | 
 | #endif | 
 | IN_PROC_BROWSER_TEST_P(TouchSelectionControllerClientAndroidSiteIsolationTest, | 
 |                        MAYBE_SelectionThenPinchInOOPIF) { | 
 |   // Load test URL with cross-process child. | 
 |   SetupTest(); | 
 |  | 
 |   EXPECT_EQ(ui::TouchSelectionController::INACTIVE, | 
 |             root_rwhv()->touch_selection_controller()->active_status()); | 
 |   // Find the location of some text to select. | 
 |   gfx::PointF point_f = GetPointInChild(); | 
 |  | 
 |   // Initiate selection with a sequence of events that go through the targeting | 
 |   // system. | 
 |   selection_controller_client()->InitWaitForSelectionEvent( | 
 |       ui::SELECTION_HANDLES_SHOWN); | 
 |  | 
 |   SelectWithLongPress(gfx::Point(point_f.x(), point_f.y())); | 
 |  | 
 |   selection_controller_client()->Wait(); | 
 |  | 
 |   // Check that selection is active and the quick menu is showing. | 
 |   EXPECT_EQ(ui::TouchSelectionController::SELECTION_ACTIVE, | 
 |             root_rwhv()->touch_selection_controller()->active_status()); | 
 |  | 
 |   // Make sure handles are correctly positioned. | 
 |   VerifyHandlePosition(); | 
 |  | 
 |   // Generate a pinch sequence, then re-verify handles are in the correct | 
 |   // location. | 
 |   float page_scale_delta = 2.f; | 
 |   float current_page_scale = PageScaleFactor(); | 
 |   float target_page_scale = current_page_scale * page_scale_delta; | 
 |  | 
 |   SyntheticPinchGestureParams params; | 
 |   // We'll use the selection point for the pinch center to minimize the | 
 |   // likelihood of the selection getting zoomed offscreen. | 
 |   params.anchor = point_f; | 
 |   // Note: the |scale_factor| in |params| is actually treated as a delta, not | 
 |   // absolute, page scale. | 
 |   params.scale_factor = page_scale_delta; | 
 |   auto synthetic_pinch_gesture = | 
 |       std::make_unique<SyntheticTouchscreenPinchGesture>(params); | 
 |  | 
 |   auto* host = | 
 |       static_cast<RenderWidgetHostImpl*>(root_rwhv()->GetRenderWidgetHost()); | 
 |   InputEventAckWaiter gesture_pinch_end_waiter( | 
 |       host, blink::WebInputEvent::Type::kGesturePinchEnd); | 
 |   host->QueueSyntheticGesture( | 
 |       std::move(synthetic_pinch_gesture), | 
 |       base::BindOnce(&TouchSelectionControllerClientAndroidSiteIsolationTest:: | 
 |                          OnSyntheticGestureCompleted, | 
 |                      base::Unretained(this))); | 
 |   OnSyntheticGestureSent(); | 
 |   // Make sure the gesture is complete from the renderer's point of view. | 
 |   gesture_pinch_end_waiter.Wait(); | 
 |  | 
 |   VerifyHandlePosition(); | 
 |   // TODO(wjmaclean): Investigate why SyntheticTouchscreenPinchGesture final | 
 |   // scales are so imprecise. | 
 |   // https://crbug.com/897173 | 
 |   const float kScaleFactorTolerance = 0.05f; | 
 |   EXPECT_NEAR(target_page_scale, PageScaleFactor(), kScaleFactorTolerance); | 
 |  | 
 |   // Cleanup before shutting down. | 
 |   ShutdownTest(); | 
 | } | 
 | #endif  // BUILDFLAG(IS_ANDROID) | 
 |  | 
 | class TouchEventObserver : public RenderWidgetHost::InputEventObserver { | 
 |  public: | 
 |   TouchEventObserver(std::vector<uint32_t>* outgoing_touch_event_ids, | 
 |                      std::vector<uint32_t>* acked_touch_event_ids) | 
 |       : outgoing_touch_event_ids_(outgoing_touch_event_ids), | 
 |         acked_touch_event_ids_(acked_touch_event_ids) {} | 
 |  | 
 |   TouchEventObserver(const TouchEventObserver&) = delete; | 
 |   TouchEventObserver& operator=(const TouchEventObserver&) = delete; | 
 |  | 
 |   void OnInputEvent(const RenderWidgetHost& widget, | 
 |                     const blink::WebInputEvent& event) override { | 
 |     if (!blink::WebInputEvent::IsTouchEventType(event.GetType())) | 
 |       return; | 
 |  | 
 |     const auto& touch_event = static_cast<const blink::WebTouchEvent&>(event); | 
 |     outgoing_touch_event_ids_->push_back(touch_event.unique_touch_event_id); | 
 |   } | 
 |  | 
 |   void OnInputEventAck(const RenderWidgetHost& widget, | 
 |                        blink::mojom::InputEventResultSource source, | 
 |                        blink::mojom::InputEventResultState state, | 
 |                        const blink::WebInputEvent& event) override { | 
 |     if (!blink::WebInputEvent::IsTouchEventType(event.GetType())) | 
 |       return; | 
 |  | 
 |     const auto& touch_event = static_cast<const blink::WebTouchEvent&>(event); | 
 |     acked_touch_event_ids_->push_back(touch_event.unique_touch_event_id); | 
 |   } | 
 |  | 
 |  private: | 
 |   raw_ptr<std::vector<uint32_t>> outgoing_touch_event_ids_; | 
 |   raw_ptr<std::vector<uint32_t>> acked_touch_event_ids_; | 
 | }; | 
 |  | 
 | // This test verifies the ability of the TouchEventAckQueue to send TouchEvent | 
 | // acks to the root view in the correct order in the event of a slow renderer. | 
 | // This test uses a main-frame which acks instantly (no touch handler), and a | 
 | // child frame which acks very slowly. A synthetic gesture tap is sent to the | 
 | // child first, then the main frame. In this scenario, we expect the touch | 
 | // events sent to the main-frame to ack first, which will be problematic if | 
 | // the events are acked to the GestureRecognizer out of order. | 
 | // | 
 | // This test is disabled due to flakiness on all platforms, but especially on | 
 | // Android.  See https://crbug.com/945025. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        DISABLED_TouchEventAckQueueOrdering) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   ASSERT_EQ(1u, root->child_count()); | 
 |   FrameTreeNode* child_node = root->child_at(0); | 
 |  | 
 |   // Add a *slow* & non-passive touch event handler in the child. It needs to | 
 |   // be non-passive to ensure TouchStart doesn't get acked until after the | 
 |   // touch handler completes. | 
 |   EXPECT_TRUE(ExecJs(child_node, | 
 |                      "touch_event_count = 0;\ | 
 |        function touch_handler(ev) {\ | 
 |          var start = Date.now();\ | 
 |          while (Date.now() < start + 1000) {}\ | 
 |          touch_event_count++;\ | 
 |        }\ | 
 |        document.body.addEventListener('touchstart', touch_handler,\ | 
 |                                       { passive : false });\ | 
 |        document.body.addEventListener('touchend', touch_handler,\ | 
 |                                       { passive : false });")); | 
 |  | 
 |   WaitForHitTestData(child_node->current_frame_host()); | 
 |  | 
 |   auto* root_host = static_cast<RenderWidgetHostImpl*>( | 
 |       root->current_frame_host()->GetRenderWidgetHost()); | 
 |   auto* child_host = static_cast<RenderWidgetHostImpl*>( | 
 |       child_node->current_frame_host()->GetRenderWidgetHost()); | 
 |  | 
 |   // Create InputEventObserver for both, with access to common queue for | 
 |   // logging. | 
 |   std::vector<uint32_t> outgoing_touch_event_ids; | 
 |   std::vector<uint32_t> acked_touch_event_ids; | 
 |  | 
 |   TouchEventObserver parent_touch_event_observer(&outgoing_touch_event_ids, | 
 |                                                  &acked_touch_event_ids); | 
 |   TouchEventObserver child_touch_event_observer(&outgoing_touch_event_ids, | 
 |                                                 &acked_touch_event_ids); | 
 |  | 
 |   root_host->AddInputEventObserver(&parent_touch_event_observer); | 
 |   child_host->AddInputEventObserver(&child_touch_event_observer); | 
 |  | 
 |   InputEventAckWaiter root_ack_waiter(root_host, | 
 |                                       blink::WebInputEvent::Type::kTouchEnd); | 
 |   InputEventAckWaiter child_ack_waiter(child_host, | 
 |                                        blink::WebInputEvent::Type::kTouchEnd); | 
 |   InputEventAckWaiter child_gesture_tap_ack_waiter( | 
 |       child_host, blink::WebInputEvent::Type::kGestureTap); | 
 |  | 
 |   // Create GestureTap for child. | 
 |   gfx::PointF child_tap_point; | 
 |   { | 
 |     // We need to know the center of the child's body, but in root view | 
 |     // coordinates. | 
 |     std::string str = EvalJs(child_node, | 
 |                              "var rect = document.body.getBoundingClientRect();\ | 
 |          var point = {\ | 
 |            x: rect.left + rect.width / 2,\ | 
 |            y: rect.top + rect.height / 2\ | 
 |          };\ | 
 |          JSON.stringify(point);") | 
 |                           .ExtractString(); | 
 |     ConvertJSONToPoint(str, &child_tap_point); | 
 |     child_tap_point = child_node->current_frame_host() | 
 |                           ->GetView() | 
 |                           ->TransformPointToRootCoordSpaceF(child_tap_point); | 
 |   } | 
 |   SyntheticTapGestureParams child_tap_params; | 
 |   child_tap_params.position = child_tap_point; | 
 |   child_tap_params.gesture_source_type = | 
 |       content::mojom::GestureSourceType::kTouchInput; | 
 |   child_tap_params.duration_ms = 300.f; | 
 |   auto child_tap_gesture = | 
 |       std::make_unique<SyntheticTapGesture>(child_tap_params); | 
 |  | 
 |   // Create GestureTap for root. | 
 |   SyntheticTapGestureParams root_tap_params; | 
 |   root_tap_params.position = gfx::PointF(5.f, 5.f); | 
 |   root_tap_params.duration_ms = 300.f; | 
 |   root_tap_params.gesture_source_type = | 
 |       content::mojom::GestureSourceType::kTouchInput; | 
 |   auto root_tap_gesture = | 
 |       std::make_unique<SyntheticTapGesture>(root_tap_params); | 
 |  | 
 |   // Queue both GestureTaps, child first. | 
 |   // Note that we want the SyntheticGestureController to start sending the | 
 |   // root tap gesture as soon as it's finished sending the events for the | 
 |   // child tap gesture, otherwise it would wait for the acks from the child | 
 |   // before starting the root gesture which defeats the purpose of this test. | 
 |   root_host->QueueSyntheticGestureCompleteImmediately( | 
 |       std::move(child_tap_gesture)); | 
 |   root_host->QueueSyntheticGesture( | 
 |       std::move(root_tap_gesture), | 
 |       base::BindOnce([](SyntheticGesture::Result result) { | 
 |         EXPECT_EQ(SyntheticGesture::GESTURE_FINISHED, result); | 
 |       })); | 
 |  | 
 |   root_ack_waiter.Wait(); | 
 |   child_ack_waiter.Wait(); | 
 |  | 
 |   // Verify the child did receive two touch events. | 
 |   EXPECT_EQ(2, EvalJs(child_node, "touch_event_count;")); | 
 |  | 
 |   // Verify Acks from parent arrive first. | 
 |   EXPECT_EQ(4u, outgoing_touch_event_ids.size()); | 
 |   EXPECT_EQ(4u, acked_touch_event_ids.size()); | 
 |   EXPECT_EQ(outgoing_touch_event_ids[2], acked_touch_event_ids[0]); | 
 |   EXPECT_EQ(outgoing_touch_event_ids[3], acked_touch_event_ids[1]); | 
 |  | 
 |   // Verify no DCHECKs from GestureRecognizer, indicating acks happened in | 
 |   // order. | 
 |   child_gesture_tap_ack_waiter.Wait(); | 
 | } | 
 |  | 
 | // Verify that sandbox flags specified by a CSP header are properly inherited by | 
 | // child frames, but are removed when the frame navigates. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        ActiveSandboxFlagsMaintainedAcrossNavigation) { | 
 |   bool sandboxed_iframes_are_isolated = | 
 |       SiteIsolationPolicy::AreIsolatedSandboxedIframesEnabled(); | 
 |   GURL main_url( | 
 |       embedded_test_server()->GetURL("a.com", "/sandbox_main_frame_csp.html")); | 
 |   RenderFrameDeletedObserver deleted_observer( | 
 |       web_contents()->GetPrimaryFrameTree().root()->current_frame_host()); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |   if (sandboxed_iframes_are_isolated) { | 
 |     // The initial navigation is away from an initial un-sandboxed mainframe to | 
 |     // a sandboxed mainframe, so before we call DepictFrameTree below we need to | 
 |     // wait for the RenderFrameHost from the initial mainframe to be deleted and | 
 |     // its proxies removed. | 
 |     // TODO(crbug.com/40282613): See if we can reuse the initial RFH for | 
 |     // a navigation to a sandboxed frame instead? | 
 |     deleted_observer.WaitUntilDeleted(); | 
 |   } | 
 |  | 
 |   // It is safe to obtain the root frame tree node here, as it doesn't change. | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   ASSERT_EQ(1u, root->child_count()); | 
 |  | 
 |   EXPECT_EQ( | 
 |       base::StringPrintf(" Site A\n" | 
 |                          "   +--Site A\n" | 
 |                          "Where A = http://a.com/%s", | 
 |                          sandboxed_iframes_are_isolated ? " (sandboxed)" : ""), | 
 |       DepictFrameTree(root)); | 
 |   if (sandboxed_iframes_are_isolated && | 
 |       blink::features::kIsolateSandboxedIframesGroupingParam.Get() == | 
 |           blink::features::IsolateSandboxedIframesGrouping::kPerOrigin) { | 
 |     // In per-origin IsolatedSandboxedIframes mode, the server port is retained | 
 |     // in the site URL. | 
 |     GURL main_site(embedded_test_server()->GetURL("a.com", "/")); | 
 |     EXPECT_EQ(main_site, | 
 |               root->current_frame_host()->GetSiteInstance()->GetSiteURL()); | 
 |   } | 
 |  | 
 |   FrameTreeNode* child_node = root->child_at(0); | 
 |  | 
 |   EXPECT_EQ(shell()->web_contents()->GetSiteInstance(), | 
 |             child_node->current_frame_host()->GetSiteInstance()); | 
 |  | 
 |   // Main page is served with a CSP header applying sandbox flags allow-popups, | 
 |   // allow-pointer-lock and allow-scripts. | 
 |   EXPECT_EQ(network::mojom::WebSandboxFlags::kNone, | 
 |             root->pending_frame_policy().sandbox_flags); | 
 |   EXPECT_EQ(network::mojom::WebSandboxFlags::kNone, | 
 |             root->effective_frame_policy().sandbox_flags); | 
 |   EXPECT_EQ( | 
 |       network::mojom::WebSandboxFlags::kAll & | 
 |           ~network::mojom::WebSandboxFlags::kAutomaticFeatures & | 
 |           ~network::mojom::WebSandboxFlags::kPointerLock & | 
 |           ~network::mojom::WebSandboxFlags::kPopups & | 
 |           ~network::mojom::WebSandboxFlags::kScripts & | 
 |           ~network::mojom::WebSandboxFlags::kTopNavigationToCustomProtocols, | 
 |       root->active_sandbox_flags()); | 
 |  | 
 |   // Child frame has iframe sandbox flags allow-popups, allow-scripts, and | 
 |   // allow-orientation-lock. It should receive the intersection of those with | 
 |   // the parent sandbox flags: allow-popups and allow-scripts. | 
 |   EXPECT_EQ( | 
 |       network::mojom::WebSandboxFlags::kAll & | 
 |           ~network::mojom::WebSandboxFlags::kAutomaticFeatures & | 
 |           ~network::mojom::WebSandboxFlags::kPopups & | 
 |           ~network::mojom::WebSandboxFlags::kScripts & | 
 |           ~network::mojom::WebSandboxFlags::kTopNavigationToCustomProtocols, | 
 |       root->child_at(0)->pending_frame_policy().sandbox_flags); | 
 |   EXPECT_EQ( | 
 |       network::mojom::WebSandboxFlags::kAll & | 
 |           ~network::mojom::WebSandboxFlags::kAutomaticFeatures & | 
 |           ~network::mojom::WebSandboxFlags::kPopups & | 
 |           ~network::mojom::WebSandboxFlags::kScripts & | 
 |           ~network::mojom::WebSandboxFlags::kTopNavigationToCustomProtocols, | 
 |       root->child_at(0)->effective_frame_policy().sandbox_flags); | 
 |  | 
 |   // Document in child frame is served with a CSP header giving sandbox flags | 
 |   // allow-scripts, allow-popups and allow-pointer-lock. The final effective | 
 |   // flags should only include allow-scripts and allow-popups. | 
 |   EXPECT_EQ( | 
 |       network::mojom::WebSandboxFlags::kAll & | 
 |           ~network::mojom::WebSandboxFlags::kAutomaticFeatures & | 
 |           ~network::mojom::WebSandboxFlags::kPopups & | 
 |           ~network::mojom::WebSandboxFlags::kScripts & | 
 |           ~network::mojom::WebSandboxFlags::kTopNavigationToCustomProtocols, | 
 |       root->child_at(0)->active_sandbox_flags()); | 
 |  | 
 |   // Navigate the child frame to a new page. This should clear any CSP-applied | 
 |   // sandbox flags. | 
 |   GURL frame_url(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(0), frame_url)); | 
 |  | 
 |   EXPECT_NE(shell()->web_contents()->GetSiteInstance(), | 
 |             child_node->current_frame_host()->GetSiteInstance()); | 
 |  | 
 |   // Navigating should reset the sandbox flags to the frame owner flags: | 
 |   // allow-popups and allow-scripts. | 
 |   EXPECT_EQ( | 
 |       network::mojom::WebSandboxFlags::kAll & | 
 |           ~network::mojom::WebSandboxFlags::kAutomaticFeatures & | 
 |           ~network::mojom::WebSandboxFlags::kPopups & | 
 |           ~network::mojom::WebSandboxFlags::kScripts & | 
 |           ~network::mojom::WebSandboxFlags::kTopNavigationToCustomProtocols, | 
 |       root->child_at(0)->active_sandbox_flags()); | 
 |   EXPECT_EQ( | 
 |       network::mojom::WebSandboxFlags::kAll & | 
 |           ~network::mojom::WebSandboxFlags::kAutomaticFeatures & | 
 |           ~network::mojom::WebSandboxFlags::kPopups & | 
 |           ~network::mojom::WebSandboxFlags::kScripts & | 
 |           ~network::mojom::WebSandboxFlags::kTopNavigationToCustomProtocols, | 
 |       root->child_at(0)->pending_frame_policy().sandbox_flags); | 
 |   EXPECT_EQ( | 
 |       network::mojom::WebSandboxFlags::kAll & | 
 |           ~network::mojom::WebSandboxFlags::kAutomaticFeatures & | 
 |           ~network::mojom::WebSandboxFlags::kPopups & | 
 |           ~network::mojom::WebSandboxFlags::kScripts & | 
 |           ~network::mojom::WebSandboxFlags::kTopNavigationToCustomProtocols, | 
 |       root->child_at(0)->effective_frame_policy().sandbox_flags); | 
 | } | 
 |  | 
 | // Test that after an RFH is unloaded, its old sandbox flags remain active. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        ActiveSandboxFlagsRetainedAfterUnload) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/sandboxed_main_frame_script.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()) | 
 |                             ->GetPrimaryFrameTree() | 
 |                             .root(); | 
 |  | 
 |   RenderFrameHostImpl* rfh = | 
 |       static_cast<WebContentsImpl*>(shell()->web_contents()) | 
 |           ->GetPrimaryMainFrame(); | 
 |  | 
 |   // Check sandbox flags on RFH before navigating away. | 
 |   EXPECT_EQ( | 
 |       network::mojom::WebSandboxFlags::kAll & | 
 |           ~network::mojom::WebSandboxFlags::kAutomaticFeatures & | 
 |           ~network::mojom::WebSandboxFlags::kPointerLock & | 
 |           ~network::mojom::WebSandboxFlags::kPopups & | 
 |           ~network::mojom::WebSandboxFlags::kScripts & | 
 |           ~network::mojom::WebSandboxFlags::kTopNavigationToCustomProtocols, | 
 |       rfh->active_sandbox_flags()); | 
 |  | 
 |   // Set up a slow unload handler to force the RFH to linger in the unloaded but | 
 |   // not-yet-deleted state. | 
 |   EXPECT_TRUE(ExecJs(rfh, "window.onunload=function(e){ while(1); };\n")); | 
 |  | 
 |   rfh->DisableUnloadTimerForTesting(); | 
 |   RenderFrameDeletedObserver rfh_observer(rfh); | 
 |  | 
 |   // Navigate to a page with no sandbox, but wait for commit, not for the actual | 
 |   // load to finish. | 
 |   TestFrameNavigationObserver commit_observer(root); | 
 |   shell()->LoadURL( | 
 |       GURL(embedded_test_server()->GetURL("b.com", "/title1.html"))); | 
 |   commit_observer.WaitForCommit(); | 
 |  | 
 |   // The previous RFH should be either: | 
 |   // 1) In the BackForwardCache, or | 
 |   // 2) Pending deletion, waiting for the | 
 |   // mojo::AgentSchedulingGroupHost::DidUnloadRenderFrame. As a result, it must | 
 |   // still be alive. | 
 |   ASSERT_TRUE(rfh->IsRenderFrameLive()); | 
 |   EXPECT_THAT( | 
 |       rfh->lifecycle_state(), | 
 |       testing::AnyOf( | 
 |           testing::Eq( | 
 |               RenderFrameHostImpl::LifecycleStateImpl::kRunningUnloadHandlers), | 
 |           testing::Eq( | 
 |               RenderFrameHostImpl::LifecycleStateImpl::kInBackForwardCache))); | 
 |  | 
 |   ASSERT_FALSE(rfh_observer.deleted()); | 
 |  | 
 |   // Check sandbox flags on old RFH -- they should be unchanged. | 
 |   EXPECT_EQ( | 
 |       network::mojom::WebSandboxFlags::kAll & | 
 |           ~network::mojom::WebSandboxFlags::kAutomaticFeatures & | 
 |           ~network::mojom::WebSandboxFlags::kPointerLock & | 
 |           ~network::mojom::WebSandboxFlags::kPopups & | 
 |           ~network::mojom::WebSandboxFlags::kScripts & | 
 |           ~network::mojom::WebSandboxFlags::kTopNavigationToCustomProtocols, | 
 |       rfh->active_sandbox_flags()); | 
 |  | 
 |   // The FrameTreeNode should have flags which represent the new state. | 
 |   EXPECT_EQ(network::mojom::WebSandboxFlags::kNone, | 
 |             root->effective_frame_policy().sandbox_flags); | 
 | } | 
 |  | 
 | // Verify that when CSP-set sandbox flags on a page change due to navigation, | 
 | // the new flags are propagated to proxies in other SiteInstances. | 
 | // | 
 | //   A        A         A         A | 
 | //    \        \         \         \     . | 
 | //     B  ->    B*   ->   B*   ->   B* | 
 | //             /  \      /  \      /  \  . | 
 | //            B    B    A    B    C    B | 
 | // | 
 | // (B* has CSP-set sandbox flags) | 
 | // The test checks sandbox flags for the proxy added in step 2, by checking | 
 | // whether the grandchild frames navigated to in step 3 and 4 see the correct | 
 | // sandbox flags. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        ActiveSandboxFlagsCorrectInProxies) { | 
 |   bool sandboxed_iframes_are_isolated = | 
 |       SiteIsolationPolicy::AreIsolatedSandboxedIframesEnabled(); | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "foo.com", "/cross_site_iframe_factory.html?foo(bar)")); | 
 |   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()->GetPrimaryFrameTree().root(); | 
 |   TestNavigationObserver observer(shell()->web_contents()); | 
 |  | 
 |   EXPECT_EQ( | 
 |       " Site A ------------ proxies for B\n" | 
 |       "   +--Site B ------- proxies for A\n" | 
 |       "Where A = http://foo.com/\n" | 
 |       "      B = http://bar.com/", | 
 |       DepictFrameTree(root)); | 
 |  | 
 |   // Navigate the child to a CSP-sandboxed page on the same origin as it is | 
 |   // currently. This should update the flags in its proxies as well. | 
 |   auto* child = root->child_at(0); | 
 |   RenderFrameDeletedObserver deleted_observer_child( | 
 |       child->current_frame_host()); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer( | 
 |       root->child_at(0), | 
 |       embedded_test_server()->GetURL("bar.com", "/csp_sandboxed_frame.html"))); | 
 |   // DepictFrameTree remembers all the sites it has seen in the test, so the | 
 |   // expected output changes depending on whether we have additional sites from | 
 |   // process-isolated sandboxed frames. How many additional sites we have | 
 |   // depends on the grouping mode. | 
 |   if (sandboxed_iframes_are_isolated) { | 
 |     // Sandboxed iframes force the RFH to change; wait for the old one to go | 
 |     // away so that proxies in its SiteInstance don't affect DepictFrameTree | 
 |     // output. | 
 |     deleted_observer_child.WaitUntilDeleted(); | 
 |     switch (blink::features::kIsolateSandboxedIframesGroupingParam.Get()) { | 
 |       case blink::features::IsolateSandboxedIframesGrouping::kPerSite: | 
 |       case blink::features::IsolateSandboxedIframesGrouping::kPerOrigin: | 
 |         EXPECT_EQ( | 
 |             " Site A ------------ proxies for C\n" | 
 |             "   +--Site C ------- proxies for A\n" | 
 |             "        |--Site C -- proxies for A\n" | 
 |             "        +--Site C -- proxies for A\n" | 
 |             "Where A = http://foo.com/\n" | 
 |             "      C = http://bar.com/ (sandboxed)", | 
 |             DepictFrameTree(root)); | 
 |         break; | 
 |       case blink::features::IsolateSandboxedIframesGrouping::kPerDocument: | 
 |         // TODO(crbug.com/40941714): Add output for the PerDocument | 
 |         // case, and parameterize this test to run all variants (none, per-site, | 
 |         // per-origin, per-document). | 
 |         break; | 
 |     } | 
 |   } else { | 
 |     EXPECT_EQ( | 
 |         " Site A ------------ proxies for B\n" | 
 |         "   +--Site B ------- proxies for A\n" | 
 |         "        |--Site B -- proxies for A\n" | 
 |         "        +--Site B -- proxies for A\n" | 
 |         "Where A = http://foo.com/\n" | 
 |         "      B = http://bar.com/", | 
 |         DepictFrameTree(root)); | 
 |   } | 
 |  | 
 |   // Now navigate the first grandchild to a page on the same origin as the main | 
 |   // frame. It should still be sandboxed, as it should get its flags from its | 
 |   // (remote) parent. | 
 |   // TODO(crbug.com/40943240): When IsolateSandboxedIframes is enabled, | 
 |   // this test no longer uses proxy inheritance; the grandchild and the main | 
 |   // frame won't be in the same SiteInstance anymore, so this test will no | 
 |   // longer exercise sandbox flags inheritance from an existing remote frame. | 
 |   // Restructure the test so it still provides coverage for proxy inheritance | 
 |   // when IsolateSandboxedIframes is enabled. | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer( | 
 |       root->child_at(0)->child_at(0), | 
 |       embedded_test_server()->GetURL("foo.com", "/title1.html"))); | 
 |  | 
 |   if (sandboxed_iframes_are_isolated) { | 
 |     switch (blink::features::kIsolateSandboxedIframesGroupingParam.Get()) { | 
 |       case blink::features::IsolateSandboxedIframesGrouping::kPerSite: | 
 |       case blink::features::IsolateSandboxedIframesGrouping::kPerOrigin: | 
 |         EXPECT_EQ( | 
 |             " Site A ------------ proxies for C D\n" | 
 |             "   +--Site C ------- proxies for A D\n" | 
 |             "        |--Site D -- proxies for A C\n" | 
 |             "        +--Site C -- proxies for A D\n" | 
 |             "Where A = http://foo.com/\n" | 
 |             "      C = http://bar.com/ (sandboxed)\n" | 
 |             "      D = http://foo.com/ (sandboxed)", | 
 |             DepictFrameTree(root)); | 
 |         break; | 
 |       case blink::features::IsolateSandboxedIframesGrouping::kPerDocument: | 
 |         // TODO(crbug.com/40941714): Add output for the PerDocument | 
 |         // case, and parameterize this test to run all variants (none, per-site, | 
 |         // per-origin, per-document). | 
 |         break; | 
 |     } | 
 |   } else { | 
 |     EXPECT_EQ( | 
 |         " Site A ------------ proxies for B\n" | 
 |         "   +--Site B ------- proxies for A\n" | 
 |         "        |--Site A -- proxies for B\n" | 
 |         "        +--Site B -- proxies for A\n" | 
 |         "Where A = http://foo.com/\n" | 
 |         "      B = http://bar.com/", | 
 |         DepictFrameTree(root)); | 
 |   } | 
 |  | 
 |   // The child of the sandboxed frame should've inherited sandbox flags, so it | 
 |   // should not be able to create popups. | 
 |   EXPECT_EQ( | 
 |       network::mojom::WebSandboxFlags::kAll & | 
 |           ~network::mojom::WebSandboxFlags::kScripts & | 
 |           ~network::mojom::WebSandboxFlags::kAutomaticFeatures, | 
 |       root->child_at(0)->child_at(0)->effective_frame_policy().sandbox_flags); | 
 |   EXPECT_EQ( | 
 |       root->child_at(0)->child_at(0)->active_sandbox_flags(), | 
 |       root->child_at(0)->child_at(0)->effective_frame_policy().sandbox_flags); | 
 |   EXPECT_EQ(true, EvalJs(root->child_at(0)->child_at(0), | 
 |                          "!window.open('data:text/html,dataurl');")); | 
 |   EXPECT_EQ(1u, Shell::windows().size()); | 
 |  | 
 |   // Finally, navigate the grandchild frame to a new origin, creating a new site | 
 |   // instance. Again, the new document should be sandboxed, as it should get its | 
 |   // flags from its (remote) parent in B. | 
 |   RenderFrameDeletedObserver deleted_observer_grandchild( | 
 |       root->child_at(0)->child_at(0)->current_frame_host()); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer( | 
 |       root->child_at(0)->child_at(0), | 
 |       embedded_test_server()->GetURL("baz.com", "/title1.html"))); | 
 |  | 
 |   deleted_observer_grandchild.WaitUntilDeleted(); | 
 |   if (sandboxed_iframes_are_isolated) { | 
 |     switch (blink::features::kIsolateSandboxedIframesGroupingParam.Get()) { | 
 |       case blink::features::IsolateSandboxedIframesGrouping::kPerSite: | 
 |       case blink::features::IsolateSandboxedIframesGrouping::kPerOrigin: | 
 |         EXPECT_EQ( | 
 |             " Site A ------------ proxies for C E\n" | 
 |             "   +--Site C ------- proxies for A E\n" | 
 |             "        |--Site E -- proxies for A C\n" | 
 |             "        +--Site C -- proxies for A E\n" | 
 |             "Where A = http://foo.com/\n" | 
 |             "      C = http://bar.com/ (sandboxed)\n" | 
 |             "      E = http://baz.com/ (sandboxed)", | 
 |             DepictFrameTree(root)); | 
 |         break; | 
 |       case blink::features::IsolateSandboxedIframesGrouping::kPerDocument: | 
 |         // TODO(crbug.com/40941714): Add output for the PerDocument | 
 |         // case, and parameterize this test to run all variants (none, per-site, | 
 |         // per-origin, per-document). | 
 |         break; | 
 |     } | 
 |   } else { | 
 |     EXPECT_EQ( | 
 |         " Site A ------------ proxies for B C\n" | 
 |         "   +--Site B ------- proxies for A C\n" | 
 |         "        |--Site C -- proxies for A B\n" | 
 |         "        +--Site B -- proxies for A C\n" | 
 |         "Where A = http://foo.com/\n" | 
 |         "      B = http://bar.com/\n" | 
 |         "      C = http://baz.com/", | 
 |         DepictFrameTree(root)); | 
 |   } | 
 |  | 
 |   // The child of the sandboxed frame should've inherited sandbox flags, so it | 
 |   // should not be able to create popups. | 
 |   EXPECT_EQ( | 
 |       network::mojom::WebSandboxFlags::kAll & | 
 |           ~network::mojom::WebSandboxFlags::kScripts & | 
 |           ~network::mojom::WebSandboxFlags::kAutomaticFeatures, | 
 |       root->child_at(0)->child_at(0)->effective_frame_policy().sandbox_flags); | 
 |   EXPECT_EQ( | 
 |       root->child_at(0)->child_at(0)->active_sandbox_flags(), | 
 |       root->child_at(0)->child_at(0)->effective_frame_policy().sandbox_flags); | 
 |   EXPECT_EQ(true, EvalJs(root->child_at(0)->child_at(0), | 
 |                          "!window.open('data:text/html,dataurl');")); | 
 |   EXPECT_EQ(1u, Shell::windows().size()); | 
 | } | 
 |  | 
 | // Verify that when the sandbox iframe attribute changes on a page which also | 
 | // has CSP-set sandbox flags, that the correct combination of flags is set in | 
 | // the sandboxed page after navigation. | 
 | // | 
 | //   A        A         A                                  A | 
 | //    \        \         \                                  \     . | 
 | //     B  ->    B*   ->   B*   -> (change sandbox attr) ->   B* | 
 | //             /  \      /  \                               /  \  . | 
 | //            B    B    A    B                             A'   B | 
 | // | 
 | // (B* has CSP-set sandbox flags) | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        ActiveSandboxFlagsCorrectAfterUpdate) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "foo.com", "/cross_site_iframe_factory.html?foo(bar)")); | 
 |   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()->GetPrimaryFrameTree().root(); | 
 |   TestNavigationObserver observer(shell()->web_contents()); | 
 |  | 
 |   // Navigate the child to a CSP-sandboxed page on the same origin as it is | 
 |   // currently. This should update the flags in its proxies as well. | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer( | 
 |       root->child_at(0), | 
 |       embedded_test_server()->GetURL("bar.com", "/csp_sandboxed_frame.html"))); | 
 |  | 
 |   // Now navigate the first grandchild to a page on the same origin as the main | 
 |   // frame. It should still be sandboxed, as it should get its flags from its | 
 |   // (remote) parent. | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer( | 
 |       root->child_at(0)->child_at(0), | 
 |       embedded_test_server()->GetURL("foo.com", "/title1.html"))); | 
 |  | 
 |   // The child of the sandboxed frame should've inherited sandbox flags, so it | 
 |   // should not be able to create popups. | 
 |   EXPECT_EQ( | 
 |       network::mojom::WebSandboxFlags::kAll & | 
 |           ~network::mojom::WebSandboxFlags::kScripts & | 
 |           ~network::mojom::WebSandboxFlags::kAutomaticFeatures, | 
 |       root->child_at(0)->child_at(0)->effective_frame_policy().sandbox_flags); | 
 |   EXPECT_EQ( | 
 |       root->child_at(0)->child_at(0)->active_sandbox_flags(), | 
 |       root->child_at(0)->child_at(0)->effective_frame_policy().sandbox_flags); | 
 |   EXPECT_EQ(true, EvalJs(root->child_at(0)->child_at(0), | 
 |                          "!window.open('data:text/html,dataurl');")); | 
 |   EXPECT_EQ(1u, Shell::windows().size()); | 
 |  | 
 |   // Update the sandbox attribute in the child frame. This should be overridden | 
 |   // by the CSP-set sandbox on this frame: The grandchild should *not* receive | 
 |   // an allowance for popups after it is navigated. | 
 |   EXPECT_TRUE(ExecJs(root->child_at(0), | 
 |                      "document.querySelector('iframe').sandbox = " | 
 |                      "    'allow-scripts allow-popups';")); | 
 |   // Finally, navigate the grandchild frame to another page on the top-level | 
 |   // origin; the active sandbox flags should still come from the it's parent's | 
 |   // CSP and the frame owner attributes. | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer( | 
 |       root->child_at(0)->child_at(0), | 
 |       embedded_test_server()->GetURL("foo.com", "/title2.html"))); | 
 |   EXPECT_EQ( | 
 |       network::mojom::WebSandboxFlags::kAll & | 
 |           ~network::mojom::WebSandboxFlags::kScripts & | 
 |           ~network::mojom::WebSandboxFlags::kAutomaticFeatures, | 
 |       root->child_at(0)->child_at(0)->effective_frame_policy().sandbox_flags); | 
 |   EXPECT_EQ( | 
 |       root->child_at(0)->child_at(0)->active_sandbox_flags(), | 
 |       root->child_at(0)->child_at(0)->effective_frame_policy().sandbox_flags); | 
 |   EXPECT_EQ(true, EvalJs(root->child_at(0)->child_at(0), | 
 |                          "!window.open('data:text/html,dataurl');")); | 
 |   EXPECT_EQ(1u, Shell::windows().size()); | 
 | } | 
 |  | 
 | // Verify that when the sandbox iframe attribute is removed from a page which | 
 | // also has CSP-set sandbox flags, that the flags are cleared in the browser | 
 | // and renderers (including proxies) after navigation to a page without CSP-set | 
 | // flags. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        ActiveSandboxFlagsCorrectWhenCleared) { | 
 |   GURL main_url( | 
 |       embedded_test_server()->GetURL("foo.com", "/sandboxed_frames_csp.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()->GetPrimaryFrameTree().root(); | 
 |   TestNavigationObserver observer(shell()->web_contents()); | 
 |  | 
 |   // The second child has both iframe-attribute sandbox flags and CSP-set flags. | 
 |   // Verify that it the flags are combined correctly in the frame tree. | 
 |   EXPECT_EQ(network::mojom::WebSandboxFlags::kAll & | 
 |                 ~network::mojom::WebSandboxFlags::kPointerLock & | 
 |                 ~network::mojom::WebSandboxFlags::kOrientationLock & | 
 |                 ~network::mojom::WebSandboxFlags::kScripts & | 
 |                 ~network::mojom::WebSandboxFlags::kAutomaticFeatures, | 
 |             root->child_at(1)->effective_frame_policy().sandbox_flags); | 
 |   EXPECT_EQ(network::mojom::WebSandboxFlags::kAll & | 
 |                 ~network::mojom::WebSandboxFlags::kPointerLock & | 
 |                 ~network::mojom::WebSandboxFlags::kScripts & | 
 |                 ~network::mojom::WebSandboxFlags::kAutomaticFeatures, | 
 |             root->child_at(1)->active_sandbox_flags()); | 
 |  | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer( | 
 |       root->child_at(1), embedded_test_server()->GetURL( | 
 |                              "bar.com", "/sandboxed_child_frame.html"))); | 
 |   EXPECT_EQ(network::mojom::WebSandboxFlags::kAll & | 
 |                 ~network::mojom::WebSandboxFlags::kPointerLock & | 
 |                 ~network::mojom::WebSandboxFlags::kOrientationLock & | 
 |                 ~network::mojom::WebSandboxFlags::kScripts & | 
 |                 ~network::mojom::WebSandboxFlags::kAutomaticFeatures, | 
 |             root->child_at(1)->effective_frame_policy().sandbox_flags); | 
 |   EXPECT_EQ(network::mojom::WebSandboxFlags::kAll & | 
 |                 ~network::mojom::WebSandboxFlags::kPointerLock & | 
 |                 ~network::mojom::WebSandboxFlags::kScripts & | 
 |                 ~network::mojom::WebSandboxFlags::kAutomaticFeatures, | 
 |             root->child_at(1)->active_sandbox_flags()); | 
 |  | 
 |   // Remove the sandbox attribute from the child frame. | 
 |   EXPECT_TRUE(ExecJs(root, | 
 |                      "document.querySelectorAll('iframe')[1]" | 
 |                      ".removeAttribute('sandbox');")); | 
 |   // Finally, navigate that child frame to another page on the same origin with | 
 |   // no CSP-set sandbox. Its sandbox flags should be completely cleared, and | 
 |   // should be cleared in the proxy in the main frame's renderer as well. | 
 |   // We can check that the flags were properly cleared by nesting another frame | 
 |   // under the child, and ensuring that *it* saw no sandbox flags in the | 
 |   // browser, or in the RemoteSecurityContext in the main frame's renderer. | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer( | 
 |       root->child_at(1), | 
 |       embedded_test_server()->GetURL( | 
 |           "bar.com", "/cross_site_iframe_factory.html?bar(foo)"))); | 
 |  | 
 |   // Check the sandbox flags on the child frame in the browser process. | 
 |   EXPECT_EQ(network::mojom::WebSandboxFlags::kNone, | 
 |             root->child_at(1)->effective_frame_policy().sandbox_flags); | 
 |   EXPECT_EQ(network::mojom::WebSandboxFlags::kNone, | 
 |             root->child_at(1)->active_sandbox_flags()); | 
 |  | 
 |   // Check the sandbox flags on the grandchid frame in the browser process. | 
 |   EXPECT_EQ( | 
 |       network::mojom::WebSandboxFlags::kNone, | 
 |       root->child_at(1)->child_at(0)->effective_frame_policy().sandbox_flags); | 
 |   EXPECT_EQ( | 
 |       root->child_at(1)->child_at(0)->active_sandbox_flags(), | 
 |       root->child_at(1)->child_at(0)->effective_frame_policy().sandbox_flags); | 
 |  | 
 |   // Check the sandbox flags in the grandchild frame's renderer by attempting | 
 |   // to open a popup. This should succeed. | 
 |   EXPECT_EQ(true, EvalJs(root->child_at(1)->child_at(0), | 
 |                          "!!window.open('data:text/html,dataurl');")); | 
 |   EXPECT_EQ(2u, Shell::windows().size()); | 
 | } | 
 |  | 
 | // Check that a subframe that requires a dedicated process will attempt to | 
 | // reuse an existing process for the same site, even across BrowsingInstances. | 
 | // This helps consolidate processes when running under --site-per-process. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        SubframeReusesExistingProcess) { | 
 |   GURL foo_url( | 
 |       embedded_test_server()->GetURL("foo.com", "/page_with_iframe.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), foo_url)); | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   FrameTreeNode* child = root->child_at(0); | 
 |  | 
 |   // Open an unrelated tab in a separate BrowsingInstance, and navigate it to | 
 |   // to bar.com.  This SiteInstance should have a default process reuse | 
 |   // policy - only subframes attempt process reuse. | 
 |   GURL bar_url( | 
 |       embedded_test_server()->GetURL("bar.com", "/page_with_iframe.html")); | 
 |   Shell* second_shell = CreateBrowser(); | 
 |   EXPECT_TRUE(NavigateToURL(second_shell, bar_url)); | 
 |   scoped_refptr<SiteInstanceImpl> second_shell_instance = | 
 |       static_cast<SiteInstanceImpl*>(second_shell->web_contents() | 
 |                                          ->GetPrimaryMainFrame() | 
 |                                          ->GetSiteInstance()); | 
 |   EXPECT_FALSE(second_shell_instance->IsRelatedSiteInstance( | 
 |       root->current_frame_host()->GetSiteInstance())); | 
 |   RenderProcessHost* bar_process = second_shell_instance->GetProcess(); | 
 |   EXPECT_EQ(ProcessReusePolicy::DEFAULT, | 
 |             second_shell_instance->process_reuse_policy()); | 
 |  | 
 |   // Now navigate the first tab's subframe to bar.com.  Confirm that it reuses | 
 |   // |bar_process|. | 
 |   NavigateIframeToURL(web_contents(), "test_iframe", bar_url); | 
 |   EXPECT_EQ(bar_url, child->current_url()); | 
 |   EXPECT_EQ(bar_process, child->current_frame_host()->GetProcess()); | 
 |   EXPECT_EQ( | 
 |       ProcessReusePolicy::REUSE_PENDING_OR_COMMITTED_SITE_SUBFRAME, | 
 |       child->current_frame_host()->GetSiteInstance()->process_reuse_policy()); | 
 |  | 
 |   EXPECT_TRUE(child->current_frame_host()->IsCrossProcessSubframe()); | 
 |   EXPECT_EQ( | 
 |       bar_url.host(), | 
 |       child->current_frame_host()->GetSiteInstance()->GetSiteURL().host()); | 
 |  | 
 |   // The subframe's SiteInstance should still be different from second_shell's | 
 |   // SiteInstance, and they should be in separate BrowsingInstances. | 
 |   EXPECT_NE(second_shell_instance, | 
 |             child->current_frame_host()->GetSiteInstance()); | 
 |   EXPECT_FALSE(second_shell_instance->IsRelatedSiteInstance( | 
 |       child->current_frame_host()->GetSiteInstance())); | 
 |  | 
 |   // Navigate the second tab to a foo.com URL with a same-site subframe.  This | 
 |   // leaves only the first tab's subframe in the bar.com process. | 
 |   EXPECT_TRUE(NavigateToURL(second_shell, foo_url)); | 
 |   EXPECT_NE(bar_process, | 
 |             second_shell->web_contents()->GetPrimaryMainFrame()->GetProcess()); | 
 |  | 
 |   // Navigate the second tab's subframe to bar.com, and check that this | 
 |   // new subframe reuses the process of the subframe in the first tab, even | 
 |   // though the two are in separate BrowsingInstances. | 
 |   NavigateIframeToURL(second_shell->web_contents(), "test_iframe", bar_url); | 
 |   FrameTreeNode* second_subframe = | 
 |       static_cast<WebContentsImpl*>(second_shell->web_contents()) | 
 |           ->GetPrimaryFrameTree() | 
 |           .root() | 
 |           ->child_at(0); | 
 |   EXPECT_EQ(bar_process, second_subframe->current_frame_host()->GetProcess()); | 
 |   EXPECT_NE(child->current_frame_host()->GetSiteInstance(), | 
 |             second_subframe->current_frame_host()->GetSiteInstance()); | 
 |  | 
 |   // Open a third, unrelated tab, navigate it to bar.com, and check that | 
 |   // its main frame doesn't share a process with the existing bar.com | 
 |   // subframes. | 
 |   Shell* third_shell = CreateBrowser(); | 
 |   EXPECT_TRUE(NavigateToURL(third_shell, bar_url)); | 
 |   SiteInstanceImpl* third_shell_instance = static_cast<SiteInstanceImpl*>( | 
 |       third_shell->web_contents()->GetPrimaryMainFrame()->GetSiteInstance()); | 
 |   EXPECT_NE(third_shell_instance, | 
 |             second_subframe->current_frame_host()->GetSiteInstance()); | 
 |   EXPECT_NE(third_shell_instance, | 
 |             child->current_frame_host()->GetSiteInstance()); | 
 |   EXPECT_NE(third_shell_instance->GetProcess(), bar_process); | 
 | } | 
 |  | 
 | class SitePerProcessNoSharingBrowserTest : public SitePerProcessBrowserTest { | 
 |  public: | 
 |   SitePerProcessNoSharingBrowserTest() { | 
 |     scoped_feature_list_.InitAndDisableFeature( | 
 |         features::kProcessPerSiteUpToMainFrameThreshold); | 
 |   } | 
 |  | 
 |  private: | 
 |   base::test::ScopedFeatureList scoped_feature_list_; | 
 | }; | 
 |  | 
 | // Check that when a subframe reuses an existing process for the same site | 
 | // across BrowsingInstances, a browser-initiated navigation in that subframe's | 
 | // tab doesn't unnecessarily share the reused process.  See | 
 | // https://crbug.com/803367. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessNoSharingBrowserTest, | 
 |                        NoProcessSharingAfterSubframeReusesExistingProcess) { | 
 |   GURL foo_url(embedded_test_server()->GetURL("foo.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), foo_url)); | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   SiteInstanceImpl* foo_instance = | 
 |       root->current_frame_host()->GetSiteInstance(); | 
 |  | 
 |   // Open an unrelated tab in a separate BrowsingInstance, and navigate it to | 
 |   // to bar.com. | 
 |   GURL bar_url( | 
 |       embedded_test_server()->GetURL("bar.com", "/page_with_iframe.html")); | 
 |   Shell* second_shell = CreateBrowser(); | 
 |   EXPECT_TRUE(NavigateToURL(second_shell, bar_url)); | 
 |   FrameTreeNode* second_root = | 
 |       static_cast<WebContentsImpl*>(second_shell->web_contents()) | 
 |           ->GetPrimaryFrameTree() | 
 |           .root(); | 
 |   FrameTreeNode* second_child = second_root->child_at(0); | 
 |   scoped_refptr<SiteInstanceImpl> bar_instance = | 
 |       second_root->current_frame_host()->GetSiteInstance(); | 
 |   EXPECT_FALSE(bar_instance->IsRelatedSiteInstance(foo_instance)); | 
 |  | 
 |   // Navigate the second tab's subframe to foo.com.  Confirm that it reuses | 
 |   // first tab's process. | 
 |   NavigateIframeToURL(second_shell->web_contents(), "test_iframe", foo_url); | 
 |   EXPECT_EQ(foo_url, second_child->current_url()); | 
 |   scoped_refptr<SiteInstanceImpl> second_child_foo_instance = | 
 |       second_child->current_frame_host()->GetSiteInstance(); | 
 |   EXPECT_EQ(ProcessReusePolicy::REUSE_PENDING_OR_COMMITTED_SITE_SUBFRAME, | 
 |             second_child_foo_instance->process_reuse_policy()); | 
 |   EXPECT_NE(foo_instance, second_child_foo_instance); | 
 |   EXPECT_EQ(foo_instance->GetProcess(), | 
 |             second_child_foo_instance->GetProcess()); | 
 |  | 
 |   // Perform a browser-initiated address bar navigation in the second tab to | 
 |   // foo.com.  This should swap BrowsingInstances and end up in a separate | 
 |   // process from the first tab. | 
 |   EXPECT_TRUE(NavigateToURL(second_shell, foo_url)); | 
 |   SiteInstanceImpl* new_instance = | 
 |       second_root->current_frame_host()->GetSiteInstance(); | 
 |   EXPECT_NE(second_child_foo_instance, new_instance); | 
 |   EXPECT_FALSE(second_child_foo_instance->IsRelatedSiteInstance(new_instance)); | 
 |   EXPECT_FALSE(bar_instance->IsRelatedSiteInstance(new_instance)); | 
 |   EXPECT_FALSE(foo_instance->IsRelatedSiteInstance(new_instance)); | 
 |   EXPECT_NE(new_instance->GetProcess(), foo_instance->GetProcess()); | 
 |   EXPECT_NE(new_instance->GetProcess(), | 
 |             bar_instance->GetOrCreateProcessForTesting()); | 
 | } | 
 |  | 
 | namespace { | 
 |  | 
 | // Intercepts the next DidCommitProvisionalLoad message for |deferred_url| in | 
 | // any frame of the |web_contents|, and holds off on dispatching it until | 
 | // *after* the DidCommitProvisionalLoad message for the next navigation in the | 
 | // |web_contents| has been dispatched. | 
 | // | 
 | // Reversing the order in which the commit messages are dispatched simulates a | 
 | // busy renderer that takes a very long time to actually commit the navigation | 
 | // to |deferred_url| after receiving FrameNavigationControl::CommitNavigation; | 
 | // whereas there is a fast cross-site navigation taking place in the same | 
 | // frame which starts second but finishes first. | 
 | class CommitMessageOrderReverser : public DidCommitNavigationInterceptor { | 
 |  public: | 
 |   using DidStartDeferringCommitCallback = | 
 |       base::OnceCallback<void(RenderFrameHost*)>; | 
 |  | 
 |   CommitMessageOrderReverser( | 
 |       WebContents* web_contents, | 
 |       const GURL& deferred_url, | 
 |       DidStartDeferringCommitCallback deferred_url_triggered_action) | 
 |       : DidCommitNavigationInterceptor(web_contents), | 
 |         deferred_url_(deferred_url), | 
 |         deferred_url_triggered_action_( | 
 |             std::move(deferred_url_triggered_action)) {} | 
 |  | 
 |   CommitMessageOrderReverser(const CommitMessageOrderReverser&) = delete; | 
 |   CommitMessageOrderReverser& operator=(const CommitMessageOrderReverser&) = | 
 |       delete; | 
 |  | 
 |   ~CommitMessageOrderReverser() override = default; | 
 |  | 
 |   void WaitForBothCommits() { outer_run_loop.Run(); } | 
 |  | 
 |  protected: | 
 |   bool WillProcessDidCommitNavigation( | 
 |       RenderFrameHost* render_frame_host, | 
 |       NavigationRequest* navigation_request, | 
 |       mojom::DidCommitProvisionalLoadParamsPtr* params, | 
 |       mojom::DidCommitProvisionalLoadInterfaceParamsPtr* interface_params) | 
 |       override { | 
 |     // The DidCommitProvisionalLoad message is dispatched once this method | 
 |     // returns, so to defer committing the the navigation to |deferred_url_|, | 
 |     // run a nested message loop until the subsequent other commit message is | 
 |     // dispatched. | 
 |     if ((**params).url == deferred_url_) { | 
 |       std::move(deferred_url_triggered_action_).Run(render_frame_host); | 
 |  | 
 |       base::RunLoop nested_run_loop(base::RunLoop::Type::kNestableTasksAllowed); | 
 |       nested_loop_quit_ = nested_run_loop.QuitClosure(); | 
 |       nested_run_loop.Run(); | 
 |       outer_run_loop.Quit(); | 
 |     } else if (nested_loop_quit_) { | 
 |       std::move(nested_loop_quit_).Run(); | 
 |     } | 
 |     return true; | 
 |   } | 
 |  | 
 |  private: | 
 |   base::RunLoop outer_run_loop; | 
 |   base::OnceClosure nested_loop_quit_; | 
 |  | 
 |   const GURL deferred_url_; | 
 |   DidStartDeferringCommitCallback deferred_url_triggered_action_; | 
 | }; | 
 |  | 
 | }  // namespace | 
 |  | 
 | // Create an out-of-process iframe that causes itself to be detached during | 
 | // its layout/animate phase. See https://crbug.com/802932. | 
 | // | 
 | // TODO(crbug.com/40561636): Disabled on Android, Mac, and ChromeOS due to | 
 | // flakiness. | 
 | #if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_CHROMEOS) | 
 | #define MAYBE_OOPIFDetachDuringAnimation DISABLED_OOPIFDetachDuringAnimation | 
 | #else | 
 | #define MAYBE_OOPIFDetachDuringAnimation OOPIFDetachDuringAnimation | 
 | #endif | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        MAYBE_OOPIFDetachDuringAnimation) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/frame_tree/frame-detached-in-animationstart-event.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   EXPECT_EQ( | 
 |       " Site A ------------ proxies for B\n" | 
 |       "   +--Site B ------- proxies for A\n" | 
 |       "        +--Site A -- proxies for B\n" | 
 |       "Where A = http://a.com/\n" | 
 |       "      B = http://b.com/", | 
 |       DepictFrameTree(root)); | 
 |  | 
 |   FrameTreeNode* nested_child = root->child_at(0)->child_at(0); | 
 |   WaitForHitTestData(nested_child->current_frame_host()); | 
 |  | 
 |   EXPECT_TRUE(ExecJs(nested_child->current_frame_host(), "startTest();")); | 
 |  | 
 |   // Test passes if the main renderer doesn't crash. Ping to verify. | 
 |   EXPECT_EQ(true, EvalJs(root->current_frame_host(), "true;")); | 
 | } | 
 |  | 
 | // Tests that a cross-process iframe asked to navigate to the same URL will | 
 | // successfully commit the navigation. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        IFrameSameDocumentNavigation) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "foo.com", "/cross_site_iframe_factory.html?foo(bar)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   FrameTreeNode* iframe = root->child_at(0); | 
 |  | 
 |   EXPECT_NE(root->current_frame_host()->GetSiteInstance(), | 
 |             iframe->current_frame_host()->GetSiteInstance()); | 
 |  | 
 |   // The iframe navigates same-document to a fragment. | 
 |   GURL iframe_fragment_url = GURL(iframe->current_url().spec() + "#foo"); | 
 |   { | 
 |     TestNavigationObserver observer(shell()->web_contents()); | 
 |     EXPECT_TRUE(ExecJs(iframe->current_frame_host(), | 
 |                        JsReplace("location.href=$1", iframe_fragment_url))); | 
 |     observer.Wait(); | 
 |     EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |     EXPECT_EQ(iframe_fragment_url, iframe->current_url()); | 
 |   } | 
 |  | 
 |   // The parent frame wants the iframe do a navigation to the same URL. Because | 
 |   // the URL has a fragment, this will be treated as a same-document navigation, | 
 |   // and not as a normal load of the same URL. This should succeed. | 
 |   { | 
 |     TestNavigationObserver observer(shell()->web_contents()); | 
 |     EXPECT_TRUE(ExecJs(root->current_frame_host(), | 
 |                        JsReplace("document.getElementById('child-0').src=$1", | 
 |                                  iframe_fragment_url))); | 
 |     observer.Wait(); | 
 |     EXPECT_TRUE(observer.last_navigation_succeeded()); | 
 |     EXPECT_EQ(iframe_fragment_url, iframe->current_url()); | 
 |   } | 
 | } | 
 |  | 
 | // Verifies the the renderer has the size of the frame after commit. | 
 | // https://crbug/804046, https://crbug.com/801091 | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, SizeAvailableAfterCommit) { | 
 |   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()->GetPrimaryFrameTree().root(); | 
 |   FrameTreeNode* child = root->child_at(0); | 
 |  | 
 |   GURL b_url(embedded_test_server()->GetURL("b.com", "/title2.html")); | 
 |   TestFrameNavigationObserver commit_observer(child); | 
 |   NavigationController::LoadURLParams params(b_url); | 
 |   params.transition_type = PageTransitionFromInt(ui::PAGE_TRANSITION_LINK); | 
 |   params.frame_tree_node_id = child->frame_tree_node_id(); | 
 |   child->navigator().controller().LoadURLWithParams(params); | 
 |   commit_observer.WaitForCommit(); | 
 |  | 
 |   EXPECT_GT(EvalJs(child, "window.innerHeight;").ExtractDouble(), 0); | 
 | } | 
 |  | 
 | // Test that a late mojo::AgentSchedulingGroupHost::DidUnloadRenderFrame won't | 
 | // incorrectly mark RenderViewHost as inactive if it's already been reused and | 
 | // switched to active by another navigation.  See https://crbug.com/823567. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        RenderViewHostStaysActiveWithLateUnloadACK) { | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("a.com", "/title1.html"))); | 
 |  | 
 |   // Open a popup and navigate it to a.com. | 
 |   Shell* popup = OpenPopup( | 
 |       shell(), embedded_test_server()->GetURL("a.com", "/title2.html"), "foo"); | 
 |   WebContentsImpl* popup_contents = | 
 |       static_cast<WebContentsImpl*>(popup->web_contents()); | 
 |   RenderFrameHostImpl* rfh = popup_contents->GetPrimaryMainFrame(); | 
 |   RenderViewHostImpl* rvh = rfh->render_view_host(); | 
 |  | 
 |   // Disable the unload ACK and the unload timer. | 
 |   auto unload_ack_filter = base::BindRepeating([] { return true; }); | 
 |   rfh->SetUnloadACKCallbackForTesting(unload_ack_filter); | 
 |   rfh->DisableUnloadTimerForTesting(); | 
 |  | 
 |   // Navigate popup to b.com.  Because there's an opener, the RVH for a.com | 
 |   // stays around in swapped-out state. | 
 |   EXPECT_TRUE(NavigateToURLInSameBrowsingInstance( | 
 |       popup, embedded_test_server()->GetURL("b.com", "/title3.html"))); | 
 |   EXPECT_FALSE(rvh->is_active()); | 
 |  | 
 |   // The old RenderFrameHost is now pending deletion. | 
 |   ASSERT_TRUE(rfh->IsRenderFrameLive()); | 
 |   ASSERT_TRUE(rfh->IsPendingDeletion()); | 
 |  | 
 |   // Kill the b.com process. | 
 |   RenderProcessHost* b_process = | 
 |       popup_contents->GetPrimaryMainFrame()->GetProcess(); | 
 |   RenderProcessHostWatcher crash_observer( | 
 |       b_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); | 
 |   b_process->Shutdown(0); | 
 |   crash_observer.Wait(); | 
 |  | 
 |   // Go back in the popup from b.com to a.com/title2.html.  Because the current | 
 |   // b.com RFH is dead, the new RFH is committed right away (without waiting | 
 |   // for renderer to commit), so that users don't need to look at the sad tab. | 
 |   TestNavigationObserver back_observer(popup_contents); | 
 |   popup_contents->GetController().GoBack(); | 
 |  | 
 |   // Pretend that the original RFH in a.com now finishes running its unload | 
 |   // handler and sends the mojo::AgentSchedulingGroupHost::DidUnloadRenderFrame. | 
 |   rfh->OnUnloaded(); | 
 |  | 
 |   // Wait for the new a.com navigation to finish. | 
 |   back_observer.Wait(); | 
 |  | 
 |   // The RVH for a.com should've been reused, and it should be active.  Its | 
 |   // main frame should've been updated to the RFH from the back navigation. | 
 |   EXPECT_EQ(popup_contents->GetPrimaryMainFrame()->render_view_host(), rvh); | 
 |   EXPECT_TRUE(rvh->is_active()); | 
 |   EXPECT_EQ(rvh->GetMainRenderFrameHost(), | 
 |             popup_contents->GetPrimaryMainFrame()); | 
 | } | 
 |  | 
 | // Check that when A opens a new window with B which embeds an A subframe, the | 
 | // subframe is visible and generates paint events.  See | 
 | // https://crbug.com/638375. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        SubframeVisibleAfterRenderViewBecomesSwappedOut) { | 
 |   GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   GURL popup_url(embedded_test_server()->GetURL( | 
 |       "b.com", "/cross_site_iframe_factory.html?b(b)")); | 
 |   Shell* popup_shell = OpenPopup(shell()->web_contents(), popup_url, "popup"); | 
 |   FrameTreeNode* popup_child = | 
 |       static_cast<WebContentsImpl*>(popup_shell->web_contents()) | 
 |           ->GetPrimaryFrameTree() | 
 |           .root() | 
 |           ->child_at(0); | 
 |  | 
 |   // Navigate popup's subframe to a page on a.com, which will generate | 
 |   // continuous compositor frames by incrementing a counter on the page. | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer( | 
 |       popup_child, embedded_test_server()->GetURL("a.com", "/counter.html"))); | 
 |  | 
 |   RenderWidgetHostViewChildFrame* child_view = | 
 |       static_cast<RenderWidgetHostViewChildFrame*>( | 
 |           popup_child->current_frame_host()->GetView()); | 
 |  | 
 |   // Make sure the child frame keeps generating compositor frames. | 
 |   RenderFrameSubmissionObserver frame_counter( | 
 |       child_view->host_->render_frame_metadata_provider()); | 
 |   while (frame_counter.render_frame_count() < 10) | 
 |     frame_counter.WaitForAnyFrameSubmission(); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, FrameDepthSimple) { | 
 |   // Five nodes, from depth 0 to 4. | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b(c(d(e))))")); | 
 |   const size_t number_of_nodes = 5; | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   FrameTreeNode* node = web_contents()->GetPrimaryFrameTree().root(); | 
 |   for (unsigned int expected_depth = 0; expected_depth < number_of_nodes; | 
 |        ++expected_depth) { | 
 |     CheckFrameDepth(expected_depth, node); | 
 |  | 
 |     if (expected_depth + 1 < number_of_nodes) | 
 |       node = node->child_at(0); | 
 |   } | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, FrameDepthTest) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(a,b(a))")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   CheckFrameDepth(0u, root); | 
 |  | 
 |   FrameTreeNode* child0 = root->child_at(0); | 
 |   { | 
 |     EXPECT_EQ(1u, child0->current_frame_host()->GetFrameDepth()); | 
 |     RenderProcessHostPriorityClient::Priority priority = | 
 |         child0->current_frame_host()->GetRenderWidgetHost()->GetPriority(); | 
 |     // Same site instance as root. | 
 |     EXPECT_EQ(0u, priority.frame_depth); | 
 |     EXPECT_EQ(0u, child0->current_frame_host()->GetProcess()->GetFrameDepth()); | 
 |   } | 
 |  | 
 |   FrameTreeNode* child1 = root->child_at(1); | 
 |   CheckFrameDepth(1u, child1); | 
 |   // In addition, site b's inactive Widget should not contribute priority. | 
 |   RenderViewHostImpl* child1_rvh = | 
 |       child1->current_frame_host()->render_view_host(); | 
 |   EXPECT_FALSE(child1_rvh->is_active()); | 
 |   EXPECT_EQ(RenderProcessHostImpl::kMaxFrameDepthForPriority, | 
 |             child1_rvh->GetWidget()->GetPriority().frame_depth); | 
 |   EXPECT_FALSE(static_cast<RenderWidgetHostOwnerDelegate*>(child1_rvh) | 
 |                    ->ShouldContributePriorityToProcess()); | 
 |   // The RenderWidgetHost of the RenderFrameHost is different from the | 
 |   // RenderWidgetHost of the RenderViewHost and contributes to the priority. | 
 |   EXPECT_NE(child1->current_frame_host()->GetRenderWidgetHost(), | 
 |             child1_rvh->GetWidget()); | 
 |   EXPECT_EQ(1u, child1->current_frame_host() | 
 |                     ->GetLocalRenderWidgetHost() | 
 |                     ->GetPriority() | 
 |                     .frame_depth); | 
 |   EXPECT_EQ(1u, child1->current_frame_host()->GetProcess()->GetFrameDepth()); | 
 |  | 
 |   FrameTreeNode* grand_child = root->child_at(1)->child_at(0); | 
 |   { | 
 |     EXPECT_EQ(2u, grand_child->current_frame_host()->GetFrameDepth()); | 
 |     RenderProcessHostPriorityClient::Priority priority = | 
 |         grand_child->current_frame_host()->GetRenderWidgetHost()->GetPriority(); | 
 |     EXPECT_EQ(2u, priority.frame_depth); | 
 |     // Same process as root | 
 |     EXPECT_EQ(0u, | 
 |               grand_child->current_frame_host()->GetProcess()->GetFrameDepth()); | 
 |   } | 
 | } | 
 |  | 
 | // Disabled due to flakiness. crbug.com/1146083 | 
 | #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) | 
 | #define MAYBE_VisibilityFrameDepthTest DISABLED_VisibilityFrameDepthTest | 
 | #else | 
 | #define MAYBE_VisibilityFrameDepthTest VisibilityFrameDepthTest | 
 | #endif | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        MAYBE_VisibilityFrameDepthTest) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b)")); | 
 |   GURL popup_url(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   Shell* new_shell = OpenPopup(root->child_at(0), popup_url, ""); | 
 |   FrameTreeNode* popup_root = | 
 |       static_cast<WebContentsImpl*>(new_shell->web_contents()) | 
 |           ->GetPrimaryFrameTree() | 
 |           .root(); | 
 |  | 
 |   // Subframe and popup share the same process. Both are visible, so depth | 
 |   // should be 0. | 
 |   RenderProcessHost* subframe_process = | 
 |       root->child_at(0)->current_frame_host()->GetProcess(); | 
 |   RenderProcessHost* popup_process = | 
 |       popup_root->current_frame_host()->GetProcess(); | 
 |   EXPECT_EQ(subframe_process, popup_process); | 
 |   EXPECT_EQ(2, popup_process->VisibleClientCount()); | 
 |   EXPECT_EQ(0u, popup_process->GetFrameDepth()); | 
 |  | 
 |   // Hide popup. Process should have one visible client and depth should be 1, | 
 |   // since depth 0 popup is hidden. | 
 |   new_shell->web_contents()->WasHidden(); | 
 |   EXPECT_EQ(1, popup_process->VisibleClientCount()); | 
 |   EXPECT_EQ(1u, popup_process->GetFrameDepth()); | 
 |  | 
 |   // Navigate main page to same origin as popup in same BrowsingInstance, | 
 |   // s main page should run in the same process as the popup. The depth on the | 
 |   // process should be 0, from the main frame of main page. | 
 |   EXPECT_TRUE(NavigateToURLInSameBrowsingInstance(shell(), popup_url)); | 
 |   // Performing a Load causes aura window to be focused (see | 
 |   // Shell::LoadURLForFrame) which recomputes window occlusion for all windows | 
 |   // (on chromeos) which unhides the popup. Hide popup again. | 
 |   new_shell->web_contents()->WasHidden(); | 
 |   RenderProcessHost* new_root_process = | 
 |       root->current_frame_host()->GetProcess(); | 
 |   EXPECT_EQ(new_root_process, popup_process); | 
 |   EXPECT_EQ(1, popup_process->VisibleClientCount()); | 
 |   EXPECT_EQ(0u, popup_process->GetFrameDepth()); | 
 |  | 
 |   // Go back on main page. Should go back to same state as before navigation. | 
 |   TestNavigationObserver back_load_observer(shell()->web_contents()); | 
 |   shell()->web_contents()->GetController().GoBack(); | 
 |   back_load_observer.Wait(); | 
 |   new_shell->web_contents()->WasHidden(); | 
 |   EXPECT_EQ(1, popup_process->VisibleClientCount()); | 
 |   EXPECT_EQ(1u, popup_process->GetFrameDepth()); | 
 |  | 
 |   // Unhide popup. Should go back to same state as before hide. | 
 |   new_shell->web_contents()->WasShown(); | 
 |   EXPECT_EQ(2, popup_process->VisibleClientCount()); | 
 |   EXPECT_EQ(0u, popup_process->GetFrameDepth()); | 
 | } | 
 |  | 
 | // Check that when a postMessage is called on a remote frame, it waits for the | 
 | // current script block to finish executing before forwarding the postMessage, | 
 | // so that if the script causes any other IPCs to be sent in the same event | 
 | // loop iteration, those IPCs are processed, and their side effects are | 
 | // observed by the target frame before it receives the forwarded postMessage. | 
 | // See https://crbug.com/828529. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        CrossProcessPostMessageWaitsForCurrentScriptToFinish) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   EXPECT_EQ(root, root->frame_tree().GetFocusedFrame()); | 
 |  | 
 |   // Add an onmessage handler to the subframe to send back a bool of whether | 
 |   // the subframe has focus. | 
 |   EXPECT_TRUE( | 
 |       ExecJs(root->child_at(0), WaitForMessageScript("document.hasFocus()"))); | 
 |  | 
 |   // Now, send a postMessage from main frame to subframe, and then focus the | 
 |   // subframe in the same script.  postMessage should be scheduled after the | 
 |   // focus() call, so the IPC to focus the subframe should arrive before the | 
 |   // postMessage IPC, and the subframe should already know that it's focused in | 
 |   // the onmessage handler. | 
 |   EXPECT_EQ(true, ExecJs(root, | 
 |                          "frames[0].postMessage('','*');\n" | 
 |                          "frames[0].focus();\n")); | 
 |   EXPECT_EQ(true, EvalJs(root->child_at(0), "onMessagePromise")); | 
 | } | 
 |  | 
 | // Ensure that if a cross-process postMessage is scheduled, and then the target | 
 | // frame is detached before the postMessage is forwarded, the source frame's | 
 | // renderer does not crash. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        CrossProcessPostMessageAndDetachTarget) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // Send a postMessage to the subframe and then immediately detach the | 
 |   // subframe. | 
 |   EXPECT_TRUE(ExecJs(root, | 
 |                      "frames[0].postMessage('','*');\n" | 
 |                      "document.body.removeChild(\n" | 
 |                      "    document.querySelector('iframe'));\n")); | 
 |  | 
 |   // Test passes if the main renderer doesn't crash.  Use setTimeout to ensure | 
 |   // this ping is evaluated after the (scheduled) postMessage is processed. | 
 |   EXPECT_EQ( | 
 |       true, | 
 |       EvalJs( | 
 |           root, | 
 |           "new Promise(resolve => setTimeout(() => { resolve(true); }, 0))")); | 
 | } | 
 |  | 
 | // Tests that the last committed URL is preserved on an RFH even after the RFH | 
 | // goes into the pending deletion state. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        LastCommittedURLRetainedAfterUnload) { | 
 |   // Navigate to a.com. | 
 |   GURL start_url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), start_url)); | 
 |   RenderFrameHostImpl* rfh = web_contents()->GetPrimaryMainFrame(); | 
 |   EXPECT_EQ(start_url, rfh->GetLastCommittedURL()); | 
 |  | 
 |   // Disable the unload ACK and the unload timer. | 
 |   auto unload_ack_filter = base::BindRepeating([] { return true; }); | 
 |   rfh->SetUnloadACKCallbackForTesting(unload_ack_filter); | 
 |   rfh->DisableUnloadTimerForTesting(); | 
 |  | 
 |   // Open a popup on a.com to keep the process alive. | 
 |   OpenPopup(shell(), embedded_test_server()->GetURL("a.com", "/title2.html"), | 
 |             "foo"); | 
 |  | 
 |   // Navigate cross-process to b.com. | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("b.com", "/title3.html"))); | 
 |  | 
 |   // The old RFH should be pending deletion. | 
 |   EXPECT_TRUE(rfh->IsPendingDeletion()); | 
 |   EXPECT_FALSE(rfh->IsActive()); | 
 |   EXPECT_NE(rfh, web_contents()->GetPrimaryMainFrame()); | 
 |  | 
 |   // Check that it still has a valid last committed URL. | 
 |   EXPECT_EQ(start_url, rfh->GetLastCommittedURL()); | 
 | } | 
 |  | 
 | #if BUILDFLAG(IS_ANDROID) | 
 |  | 
 | // This test ensures that gestures from child frames notify the gesture manager | 
 | // which exists only on the root frame. i.e. the gesture manager knows we're in | 
 | // a scroll gesture when it's happening in a cross-process child frame. This is | 
 | // important in cases like hiding the text selection popup during a scroll. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        GestureManagerListensToChildFrames) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   FrameTreeNode* child = root->child_at(0); | 
 |   GURL b_url(embedded_test_server()->GetURL("b.com", "/scrollable_page.html")); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(child, b_url)); | 
 |  | 
 |   EXPECT_EQ( | 
 |       " Site A ------------ proxies for B\n" | 
 |       "   +--Site B ------- proxies for A\n" | 
 |       "Where A = http://a.com/\n" | 
 |       "      B = http://b.com/", | 
 |       DepictFrameTree(root)); | 
 |  | 
 |   RenderWidgetHost* rwh = root->current_frame_host()->GetRenderWidgetHost(); | 
 |   RenderWidgetHost* child_rwh = | 
 |       child->current_frame_host()->GetRenderWidgetHost(); | 
 |  | 
 |   RunUntilInputProcessed(rwh); | 
 |   RunUntilInputProcessed(child_rwh); | 
 |  | 
 |   RenderWidgetHostViewAndroid* rwhv_root = | 
 |       static_cast<RenderWidgetHostViewAndroid*>( | 
 |           root->current_frame_host()->GetRenderWidgetHost()->GetView()); | 
 |  | 
 |   ASSERT_FALSE( | 
 |       rwhv_root->gesture_listener_manager_->IsScrollInProgressForTesting()); | 
 |  | 
 |   // Start a scroll gesture in the child frame, ensure the main frame's gesture | 
 |   // listener manager records that its in a scroll. | 
 |   { | 
 |     blink::WebGestureEvent gesture_scroll_begin( | 
 |         blink::WebGestureEvent::Type::kGestureScrollBegin, | 
 |         blink::WebInputEvent::kNoModifiers, | 
 |         blink::WebInputEvent::GetStaticTimeStampForTests(), | 
 |         blink::WebGestureDevice::kTouchscreen); | 
 |     gesture_scroll_begin.data.scroll_begin.delta_hint_units = | 
 |         ui::ScrollGranularity::kScrollByPrecisePixel; | 
 |     gesture_scroll_begin.data.scroll_begin.delta_x_hint = 0.f; | 
 |     // Note: Negative y-delta in a gesture event results in scrolling down on a | 
 |     // page (i.e. causes positive window.scrollY). | 
 |     gesture_scroll_begin.data.scroll_begin.delta_y_hint = -5.f; | 
 |  | 
 |     blink::WebMouseEvent mouse_move( | 
 |         blink::WebInputEvent::Type::kMouseMove, | 
 |         blink::WebInputEvent::kNoModifiers, | 
 |         blink::WebInputEvent::GetStaticTimeStampForTests()); | 
 |  | 
 |     // We wait for the dummy mouse move event since the GestureScrollEnd ACK is | 
 |     // used change the gesture manager scrolling state but InputEventAckWaiter | 
 |     // is the first-in-line so the state won't yet be changed when it returns. | 
 |     // Thus we send a second event and when it's ACK'd we know the first has | 
 |     // already been processed (we do the same thing above but with a | 
 |     // ScrollUpdate). | 
 |     InputEventAckWaiter mouse_move_waiter( | 
 |         child_rwh, blink::WebInputEvent::Type::kMouseMove); | 
 |  | 
 |     child_rwh->ForwardGestureEvent(gesture_scroll_begin); | 
 |     child_rwh->ForwardMouseEvent(mouse_move); | 
 |     mouse_move_waiter.Wait(); | 
 |  | 
 |     EXPECT_TRUE( | 
 |         rwhv_root->gesture_listener_manager_->IsScrollInProgressForTesting()); | 
 |   } | 
 |  | 
 |   // Finish the scroll, ensure the gesture manager sees the scroll end. | 
 |   { | 
 |     blink::WebGestureEvent gesture_scroll_end( | 
 |         blink::WebGestureEvent::Type::kGestureScrollEnd, | 
 |         blink::WebInputEvent::kNoModifiers, | 
 |         blink::WebInputEvent::GetStaticTimeStampForTests(), | 
 |         blink::WebGestureDevice::kTouchscreen); | 
 |  | 
 |     // See comment above for why this is sent. | 
 |     blink::WebMouseEvent mouse_move( | 
 |         blink::WebInputEvent::Type::kMouseMove, | 
 |         blink::WebInputEvent::kNoModifiers, | 
 |         blink::WebInputEvent::GetStaticTimeStampForTests()); | 
 |  | 
 |     InputEventAckWaiter mouse_move_waiter( | 
 |         child_rwh, blink::WebInputEvent::Type::kMouseMove); | 
 |  | 
 |     child_rwh->ForwardGestureEvent(gesture_scroll_end); | 
 |     child_rwh->ForwardMouseEvent(mouse_move); | 
 |     mouse_move_waiter.Wait(); | 
 |  | 
 |     EXPECT_FALSE( | 
 |         rwhv_root->gesture_listener_manager_->IsScrollInProgressForTesting()); | 
 |   } | 
 | } | 
 | #endif  // BUILDFLAG(IS_ANDROID) | 
 |  | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, DisplayLockThrottlesOOPIF) { | 
 |   GURL url_a(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   FrameTreeNode* a_frame = web_contents()->GetPrimaryFrameTree().root(); | 
 |   FrameTreeNode* b_frame = a_frame->child_at(0); | 
 |  | 
 |   // Force a lifecycle update in both frames to get to steady state. | 
 |   ASSERT_TRUE(EvalJsAfterLifecycleUpdate(a_frame->current_frame_host(), "", "") | 
 |                   .is_ok()); | 
 |   ASSERT_TRUE(EvalJsAfterLifecycleUpdate(b_frame->current_frame_host(), "", "") | 
 |                   .is_ok()); | 
 |  | 
 |   // Display lock an ancestor of the <iframe> element in a_frame. The display | 
 |   // lock status will be propagated to the OOPIF during lifecycle update. | 
 |   ASSERT_TRUE(EvalJsAfterLifecycleUpdate( | 
 |                   a_frame->current_frame_host(), | 
 |                   "document.body.style = 'content-visibility: hidden'", "") | 
 |                   .is_ok()); | 
 |  | 
 |   // At this point, a_frame should have already sent an IPC to b_frame causing | 
 |   // b_frame to become throttled. Create an IntersectionObserver and observe a | 
 |   // visible element in b_frame. The display lock status should cause the | 
 |   // visible element to be reported as "not intersecting". | 
 |   static const char kObserverScript[] = R"( | 
 |       new Promise((resolve, reject) => { | 
 |         new IntersectionObserver((entries, observer) => { | 
 |           observer.unobserve(entries[0].target); | 
 |           resolve(String(entries[0].isIntersecting)) | 
 |         }).observe(document.getElementById('siteNameHeading')) | 
 |       }) | 
 |   )"; | 
 |   EvalJsResult result1 = EvalJs(b_frame->current_frame_host(), kObserverScript); | 
 |   ASSERT_TRUE(result1.is_ok()); | 
 |   EXPECT_EQ(result1.ExtractString(), "false"); | 
 |  | 
 |   // Unlock the element in a_frame, run through the same steps, and look for an | 
 |   // "is intersecting" notification. | 
 |   ASSERT_TRUE(EvalJsAfterLifecycleUpdate(a_frame->current_frame_host(), | 
 |                                          "document.body.style = ''", "") | 
 |                   .is_ok()); | 
 |   EvalJsResult result2 = EvalJs(b_frame->current_frame_host(), kObserverScript); | 
 |   EXPECT_EQ(result2.ExtractString(), "true"); | 
 | } | 
 |  | 
 | namespace { | 
 |  | 
 | // Helper class to intercept DidCommitProvisionalLoad messages and inject a | 
 | // call to close the current tab right before them. | 
 | class ClosePageBeforeCommitHelper : public DidCommitNavigationInterceptor { | 
 |  public: | 
 |   explicit ClosePageBeforeCommitHelper(WebContents* web_contents) | 
 |       : DidCommitNavigationInterceptor(web_contents) {} | 
 |  | 
 |   ClosePageBeforeCommitHelper(const ClosePageBeforeCommitHelper&) = delete; | 
 |   ClosePageBeforeCommitHelper& operator=(const ClosePageBeforeCommitHelper&) = | 
 |       delete; | 
 |  | 
 |   void Wait() { | 
 |     run_loop_ = std::make_unique<base::RunLoop>(); | 
 |     run_loop_->Run(); | 
 |     run_loop_.reset(); | 
 |   } | 
 |  | 
 |  private: | 
 |   // DidCommitNavigationInterceptor: | 
 |   bool WillProcessDidCommitNavigation( | 
 |       RenderFrameHost* render_frame_host, | 
 |       NavigationRequest* navigation_request, | 
 |       mojom::DidCommitProvisionalLoadParamsPtr* params, | 
 |       mojom::DidCommitProvisionalLoadInterfaceParamsPtr* interface_params) | 
 |       override { | 
 |     RenderFrameHostImpl* rfh = | 
 |         static_cast<RenderFrameHostImpl*>(render_frame_host); | 
 |     EXPECT_TRUE(rfh->render_view_host()->is_active()); | 
 |     rfh->GetMainFrame()->ClosePage( | 
 |         RenderFrameHostImpl::ClosePageSource::kBrowser); | 
 |     if (run_loop_) | 
 |       run_loop_->Quit(); | 
 |     return true; | 
 |   } | 
 |  | 
 |   std::unique_ptr<base::RunLoop> run_loop_; | 
 | }; | 
 |  | 
 | }  // namespace | 
 |  | 
 | // Verify that when a tab is closed just before a commit IPC arrives for a | 
 | // subframe in the tab, a subsequent resource timing IPC from the subframe RFH | 
 | // won't generate a renderer kill.  See https://crbug.com/805705. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        CloseTabBeforeSubframeCommits) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // Open a popup in a.com to keep that process alive. | 
 |   GURL same_site_url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   Shell* new_shell = OpenPopup(root, same_site_url, ""); | 
 |  | 
 |   // Add a blank grandchild frame. | 
 |   RenderFrameHostCreatedObserver frame_observer(shell()->web_contents(), 1); | 
 |   EXPECT_TRUE( | 
 |       ExecJs(root->child_at(0), | 
 |              "document.body.appendChild(document.createElement('iframe'));")); | 
 |   frame_observer.Wait(); | 
 |   FrameTreeNode* grandchild = root->child_at(0)->child_at(0); | 
 |  | 
 |   // Navigate grandchild to an a.com URL.  Note that only a frame's initial | 
 |   // navigation forwards resource timing info to parent, so it's important that | 
 |   // this iframe was initially blank. | 
 |   // | 
 |   // Just before this URL commits, close the page. | 
 |   ClosePageBeforeCommitHelper close_page_helper(web_contents()); | 
 |   EXPECT_TRUE(ExecJs(grandchild, JsReplace("location = $1", same_site_url))); | 
 |   close_page_helper.Wait(); | 
 |  | 
 |   // Test passes if the a.com renderer doesn't crash. Ping to verify. | 
 |   EXPECT_EQ(true, EvalJs(new_shell, "true;")); | 
 | } | 
 |  | 
 | class SitePerProcessBrowserTouchActionTest : public SitePerProcessBrowserTest { | 
 |  public: | 
 |   SitePerProcessBrowserTouchActionTest() = default; | 
 |  | 
 |   bool GetTouchActionForceEnableZoom(RenderWidgetHost* rwh) { | 
 |     input::InputRouterImpl* input_router = static_cast<input::InputRouterImpl*>( | 
 |         static_cast<RenderWidgetHostImpl*>(rwh)->input_router()); | 
 |     return input_router->touch_action_filter_.force_enable_zoom_; | 
 |   } | 
 |  | 
 |   // Computes the effective and allowed touch action for |rwhv_child| by | 
 |   // dispatching a touch to it through |rwhv_root|. |rwhv_root| is the root | 
 |   // frame containing |rwhv_child|. |rwhv_child| is the child (or indirect | 
 |   // descendent) of |rwhv_root| to get the touch action of. |event_position| | 
 |   // should be within |rwhv_child| in |rwhv_root|'s coordinate space. | 
 |   void GetTouchActionsForChild( | 
 |       input::RenderWidgetHostInputEventRouter* router, | 
 |       RenderWidgetHostViewBase* rwhv_root, | 
 |       RenderWidgetHostViewBase* rwhv_child, | 
 |       const gfx::Point& event_position, | 
 |       std::optional<cc::TouchAction>& effective_touch_action, | 
 |       std::optional<cc::TouchAction>& allowed_touch_action) { | 
 |     InputEventAckWaiter ack_observer( | 
 |         rwhv_child->GetRenderWidgetHost(), | 
 |         base::BindRepeating([](blink::mojom::InputEventResultSource source, | 
 |                                blink::mojom::InputEventResultState state, | 
 |                                const blink::WebInputEvent& event) { | 
 |           return event.GetType() == blink::WebGestureEvent::Type::kTouchStart || | 
 |                  event.GetType() == blink::WebGestureEvent::Type::kTouchMove || | 
 |                  event.GetType() == blink::WebGestureEvent::Type::kTouchEnd; | 
 |         })); | 
 |  | 
 |     input::InputRouterImpl* input_router = static_cast<input::InputRouterImpl*>( | 
 |         static_cast<RenderWidgetHostImpl*>(rwhv_child->GetRenderWidgetHost()) | 
 |             ->input_router()); | 
 |     // Clear the touch actions that were set by previous touches. | 
 |     input_router->touch_action_filter_.allowed_touch_action_.reset(); | 
 |     // Send a touch start event to child to get the TAF filled with child | 
 |     // frame's touch action. | 
 |     ack_observer.Reset(); | 
 |     blink::SyntheticWebTouchEvent touch_event; | 
 |     int index = touch_event.PressPoint(event_position.x(), event_position.y()); | 
 |     router->RouteTouchEvent(rwhv_root, &touch_event, ui::LatencyInfo()); | 
 |     ack_observer.Wait(); | 
 |     // Reset them to get the new value. | 
 |     effective_touch_action.reset(); | 
 |     allowed_touch_action.reset(); | 
 |     effective_touch_action = | 
 |         input_router->touch_action_filter_.allowed_touch_action_; | 
 |     // Effective touch action are sent from a separate IPC | 
 |     // channel, so it is not guaranteed to have value when the ACK for the | 
 |     // touch start arrived because the ACK is from the main thread. | 
 |     allowed_touch_action = | 
 |         input_router->touch_action_filter_.compositor_allowed_touch_action_; | 
 |  | 
 |     // Send a touch move and touch end to complete the sequence, this also | 
 |     // avoids triggering DCHECKs when sending followup events. | 
 |     ack_observer.Reset(); | 
 |     touch_event.MovePoint(index, 1, 1); | 
 |     router->RouteTouchEvent(rwhv_root, &touch_event, ui::LatencyInfo()); | 
 |     ack_observer.Wait(); | 
 |  | 
 |     ack_observer.Reset(); | 
 |     touch_event.ReleasePoint(index); | 
 |     router->RouteTouchEvent(rwhv_root, &touch_event, ui::LatencyInfo()); | 
 |     ack_observer.Wait(); | 
 |   } | 
 |  | 
 |   void GiveItSomeTime(const base::TimeDelta& t) { | 
 |     base::RunLoop run_loop; | 
 |     base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( | 
 |         FROM_HERE, run_loop.QuitClosure(), t); | 
 |     run_loop.Run(); | 
 |   } | 
 |  | 
 |   // Waits until the parent frame has had enough time to propagate the effective | 
 |   // touch action to the child frame and the child frame has had enough time to | 
 |   // process it. | 
 |   void WaitForTouchActionUpdated( | 
 |       MainThreadFrameObserver* root_thread_observer, | 
 |       MainThreadFrameObserver* child_thread_observer) { | 
 |     // Sends an event to the root frame's renderer main thread, upon return the | 
 |     // root frame should have calculated the new effective touch action for the | 
 |     // child frame. | 
 |     root_thread_observer->Wait(); | 
 |     // Sends an event to the child frame's renderer main thread, upon return the | 
 |     // child frame should have received the effective touch action from parent | 
 |     // and propagated it. | 
 |     child_thread_observer->Wait(); | 
 |     // The child's handling of the touch action may lead to further propagation | 
 |     // back to the parent. This sends an event to the root frame's renderer main | 
 |     // thread, upon return it should have handled any touch action update. | 
 |     root_thread_observer->Wait(); | 
 |   } | 
 | }; | 
 |  | 
 | #if BUILDFLAG(IS_ANDROID) | 
 | // Class to set |force_enable_zoom| to true in WebkitPrefs. | 
 | class EnableForceZoomContentClient | 
 |     : public ContentBrowserTestContentBrowserClient { | 
 |  public: | 
 |   EnableForceZoomContentClient() = default; | 
 |  | 
 |   EnableForceZoomContentClient(const EnableForceZoomContentClient&) = delete; | 
 |   EnableForceZoomContentClient& operator=(const EnableForceZoomContentClient&) = | 
 |       delete; | 
 |  | 
 |   void OverrideWebPreferences(WebContents* web_contents, | 
 |                               SiteInstance& main_frame_site, | 
 |                               blink::web_pref::WebPreferences* prefs) override { | 
 |     prefs->force_enable_zoom = true; | 
 |   } | 
 | }; | 
 |  | 
 | class AndroidInputBrowserTest : public SitePerProcessBrowserTest { | 
 |  public: | 
 |   AndroidInputBrowserTest() { | 
 |     scoped_feature_list_.InitWithFeatureStates( | 
 |         {{input::features::kInputOnViz, true}, | 
 |          {viz::mojom::EnableVizTestApis, true}}); | 
 |   } | 
 |  | 
 |   bool GetRenderInputRouterForceEnableZoom(RenderWidgetHostImpl* rwh) { | 
 |     return rwh->GetRenderInputRouter()->GetForceEnableZoom(); | 
 |   } | 
 |  | 
 |   RenderWidgetHostImpl* GetRenderWidgetHost() const { | 
 |     RenderWidgetHostImpl* const rwh = | 
 |         RenderWidgetHostImpl::From(shell() | 
 |                                        ->web_contents() | 
 |                                        ->GetRenderWidgetHostView() | 
 |                                        ->GetRenderWidgetHost()); | 
 |     CHECK(rwh); | 
 |     return rwh; | 
 |   } | 
 |  | 
 |  private: | 
 |   base::test::ScopedFeatureList scoped_feature_list_; | 
 | }; | 
 |  | 
 | // Check if browser's |force_enable_zoom| state is in sync with Viz's state with | 
 | // InputVizard enabled. | 
 | IN_PROC_BROWSER_TEST_P(AndroidInputBrowserTest, CheckForceEnableZoomValue) { | 
 |   // Return early if transferring input to Viz isn't supported. | 
 |   if (!input::InputUtils::IsTransferInputToVizSupported()) { | 
 |     return; | 
 |   } | 
 |  | 
 |   mojo::ScopedAllowSyncCallForTesting allowed_for_testing; | 
 |   content::RenderFrameSubmissionObserver render_frame_submission_observer( | 
 |       shell()->web_contents()); | 
 |  | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("foo.com", "/title1.html"))); | 
 |   if (render_frame_submission_observer.render_frame_count() == 0) { | 
 |     render_frame_submission_observer.WaitForAnyFrameSubmission(); | 
 |   } | 
 |  | 
 |   EXPECT_FALSE(GetRenderInputRouterForceEnableZoom(GetRenderWidgetHost())); | 
 |   bool enabled = false; | 
 |   content::GetHostFrameSinkManager() | 
 |       ->GetFrameSinkManagerTestApi() | 
 |       .GetForceEnableZoomState(GetRenderWidgetHost()->GetFrameSinkId(), | 
 |                                &enabled); | 
 |   EXPECT_FALSE(enabled); | 
 |  | 
 |   EnableForceZoomContentClient new_client; | 
 |  | 
 |   web_contents()->OnWebPreferencesChanged(); | 
 |   if (render_frame_submission_observer.render_frame_count() == 0) { | 
 |     render_frame_submission_observer.WaitForAnyFrameSubmission(); | 
 |   } | 
 |  | 
 |   EXPECT_TRUE(GetRenderInputRouterForceEnableZoom(GetRenderWidgetHost())); | 
 |   content::GetHostFrameSinkManager() | 
 |       ->GetFrameSinkManagerTestApi() | 
 |       .GetForceEnableZoomState(GetRenderWidgetHost()->GetFrameSinkId(), | 
 |                                &enabled); | 
 |   EXPECT_TRUE(enabled); | 
 |  | 
 |   // Navigate to a cross-site website. | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("bar.com", "/title2.html"))); | 
 |   if (render_frame_submission_observer.render_frame_count() == 0) { | 
 |     render_frame_submission_observer.WaitForAnyFrameSubmission(); | 
 |   } | 
 |  | 
 |   EXPECT_TRUE(GetRenderInputRouterForceEnableZoom(GetRenderWidgetHost())); | 
 |   content::GetHostFrameSinkManager() | 
 |       ->GetFrameSinkManagerTestApi() | 
 |       .GetForceEnableZoomState(GetRenderWidgetHost()->GetFrameSinkId(), | 
 |                                &enabled); | 
 |   EXPECT_TRUE(enabled); | 
 | } | 
 |  | 
 | class GpuInfoUpdateObserver : public GpuDataManagerObserver { | 
 |  public: | 
 |   explicit GpuInfoUpdateObserver(base::OnceClosure callback) | 
 |       : callback_(std::move(callback)) { | 
 |     observation_.Observe(GpuDataManager::GetInstance()); | 
 |   } | 
 |   ~GpuInfoUpdateObserver() override = default; | 
 |  | 
 |   void OnGpuInfoUpdate() override { | 
 |     if (callback_) { | 
 |       std::move(callback_).Run(); | 
 |     } | 
 |   } | 
 |  | 
 |  private: | 
 |   base::OnceClosure callback_; | 
 |   base::ScopedObservation<GpuDataManager, GpuDataManagerObserver> observation_{ | 
 |       this}; | 
 | }; | 
 |  | 
 | // Checks if RenderInputRouterDelegate mojo connection is reset when GPU process | 
 | // restarts. | 
 | IN_PROC_BROWSER_TEST_P(AndroidInputBrowserTest, | 
 |                        RestartingGPUProcessResetsMojoConnection) { | 
 |   base::test::TestTraceProcessor ttp; | 
 |   ttp.StartTrace("viz"); | 
 |   RenderFrameSubmissionObserver render_frame_submission_observer( | 
 |       web_contents()); | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("foo.com", "/title1.html"))); | 
 |   if (render_frame_submission_observer.render_frame_count() == 0) { | 
 |     render_frame_submission_observer.WaitForAnyFrameSubmission(); | 
 |   } | 
 |  | 
 |   base::RunLoop run_loop; | 
 |   // This observer is begin used here to signal if the GPU process has | 
 |   // restarted. | 
 |   GpuInfoUpdateObserver gpu_observer(run_loop.QuitClosure()); | 
 |  | 
 |   RenderFrameSubmissionObserver render_frame_submission_observer2( | 
 |       web_contents()); | 
 |  | 
 |   // Kill GPU process explicitly, this should trigger a restart. | 
 |   KillGpuProcess(); | 
 |   run_loop.Run(); | 
 |  | 
 |   if (render_frame_submission_observer2.render_frame_count() == 0) { | 
 |     render_frame_submission_observer2.WaitForAnyFrameSubmission(); | 
 |   } | 
 |  | 
 |   absl::Status status = ttp.StopAndParseTrace(); | 
 |   ASSERT_TRUE(status.ok()) << status.message(); | 
 |  | 
 |   std::string query = R"( | 
 |     SELECT COUNT(*) AS cnt | 
 |     FROM slice | 
 |     WHERE name = 'InputManager::SetupRenderInputRouterDelegateConnection' | 
 |     ORDER BY ts ASC | 
 |   )"; | 
 |   auto result = ttp.RunQuery(query); | 
 |   ASSERT_TRUE(result.has_value()); | 
 |  | 
 |   // `result.value()` would look something like this: {{"cnt"}, {"<num>"}}. | 
 |   EXPECT_THAT( | 
 |       result.value(), | 
 |       testing::ElementsAre( | 
 |           testing::ElementsAre("cnt"), | 
 |           testing::ElementsAre( | 
 |               input::InputUtils::IsTransferInputToVizSupported() ? "2" : "0"))); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTouchActionTest, | 
 |                        ForceEnableZoomPropagatesToChild) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   ASSERT_EQ(1U, root->child_count()); | 
 |   GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |   FrameTreeNode* child = root->child_at(0); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(child, b_url)); | 
 |   WaitForHitTestData(child->current_frame_host()); | 
 |  | 
 |   // Get access to child's TouchActionFilter. | 
 |   RenderWidgetHost* child_rwh = | 
 |       child->current_frame_host()->GetRenderWidgetHost(); | 
 |   EXPECT_FALSE(GetTouchActionForceEnableZoom(child_rwh)); | 
 |  | 
 |   EnableForceZoomContentClient new_client; | 
 |  | 
 |   web_contents()->OnWebPreferencesChanged(); | 
 |  | 
 |   EXPECT_TRUE(GetTouchActionForceEnableZoom(child_rwh)); | 
 |  | 
 |   // Add a new oopif child frame, and make sure it initializes with the correct | 
 |   // value of ForceEnableZoom. | 
 |   GURL c_url = embedded_test_server()->GetURL("c.com", "/title1.html"); | 
 |   std::string create_frame_script = base::StringPrintf( | 
 |       "var new_iframe = document.createElement('iframe');" | 
 |       "new_iframe.src = '%s';" | 
 |       "document.body.appendChild(new_iframe);", | 
 |       c_url.spec().c_str()); | 
 |   EXPECT_TRUE(ExecJs(root, create_frame_script)); | 
 |   EXPECT_TRUE(WaitForLoadStop(web_contents())); | 
 |   ASSERT_EQ(2U, root->child_count()); | 
 |  | 
 |   FrameTreeNode* new_child = root->child_at(1); | 
 |   EXPECT_NE(root->current_frame_host()->GetRenderWidgetHost(), | 
 |             new_child->current_frame_host()->GetRenderWidgetHost()); | 
 |   EXPECT_TRUE(GetTouchActionForceEnableZoom( | 
 |       new_child->current_frame_host()->GetRenderWidgetHost())); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTouchActionTest, | 
 |                        CheckForceEnableZoomValue) { | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("foo.com", "/title1.html"))); | 
 |   EXPECT_FALSE(GetTouchActionForceEnableZoom( | 
 |       web_contents()->GetPrimaryMainFrame()->GetRenderViewHost()->GetWidget())); | 
 |  | 
 |   EnableForceZoomContentClient new_client; | 
 |  | 
 |   web_contents()->OnWebPreferencesChanged(); | 
 |  | 
 |   EXPECT_TRUE(GetTouchActionForceEnableZoom( | 
 |       web_contents()->GetPrimaryMainFrame()->GetRenderViewHost()->GetWidget())); | 
 |  | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("bar.com", "/title2.html"))); | 
 |  | 
 |   EXPECT_TRUE(GetTouchActionForceEnableZoom( | 
 |       web_contents()->GetPrimaryMainFrame()->GetRenderViewHost()->GetWidget())); | 
 | } | 
 |  | 
 | #endif  // BUILDFLAG(IS_ANDROID) | 
 |  | 
 | // Flaky on every platform, failing most of the time on Android. | 
 | // See https://crbug.com/945734 | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTouchActionTest, | 
 |                        DISABLED_EffectiveTouchActionPropagatesAcrossFrames) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   FrameTreeNode* child = root->child_at(0); | 
 |   RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>( | 
 |       root->current_frame_host()->GetRenderWidgetHost()->GetView()); | 
 |   RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>( | 
 |       child->current_frame_host()->GetRenderWidgetHost()->GetView()); | 
 |   std::unique_ptr<MainThreadFrameObserver> root_thread_observer( | 
 |       new MainThreadFrameObserver( | 
 |           root->current_frame_host()->GetRenderWidgetHost())); | 
 |   root_thread_observer->Wait(); | 
 |  | 
 |   GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(child, b_url)); | 
 |  | 
 |   // Force the renderer to generate a new frame. | 
 |   EXPECT_TRUE(ExecJs(shell(), "document.body.style.touchAction = 'none'")); | 
 |   // Waits for the next frame. | 
 |   WaitForHitTestData(child->current_frame_host()); | 
 |   std::unique_ptr<MainThreadFrameObserver> child_thread_observer( | 
 |       new MainThreadFrameObserver( | 
 |           child->current_frame_host()->GetRenderWidgetHost())); | 
 |  | 
 |   RenderWidgetHostViewChildFrame* child_view = | 
 |       static_cast<RenderWidgetHostViewChildFrame*>( | 
 |           child->current_frame_host()->GetRenderWidgetHost()->GetView()); | 
 |   gfx::Point point_inside_child = ToFlooredPoint( | 
 |       child_view->TransformPointToRootCoordSpaceF(gfx::PointF(+5.f, +5.f))); | 
 |  | 
 |   input::RenderWidgetHostInputEventRouter* router = | 
 |       static_cast<WebContentsImpl*>(web_contents())->GetInputEventRouter(); | 
 |  | 
 |   WaitForTouchActionUpdated(root_thread_observer.get(), | 
 |                             child_thread_observer.get()); | 
 |   std::optional<cc::TouchAction> effective_touch_action; | 
 |   std::optional<cc::TouchAction> allowed_touch_action; | 
 |   cc::TouchAction expected_touch_action = cc::TouchAction::kPan; | 
 |   // Gestures are filtered by the intersection of touch-action values of the | 
 |   // touched element and all its ancestors up to the one that implements the | 
 |   // gesture. Since iframe allows scrolling, touch action pan restrictions will | 
 |   // not affect iframe's descendants, so we expect TouchAction::kPan instead of | 
 |   // TouchAction::kAuto in iframe's child. | 
 |   GetTouchActionsForChild(router, rwhv_root, rwhv_child, point_inside_child, | 
 |                           effective_touch_action, allowed_touch_action); | 
 |   if (allowed_touch_action.has_value()) | 
 |     EXPECT_EQ(expected_touch_action, allowed_touch_action.value()); | 
 |  | 
 |   EXPECT_TRUE(ExecJs(shell(), "document.body.style.touchAction = 'auto'")); | 
 |   WaitForTouchActionUpdated(root_thread_observer.get(), | 
 |                             child_thread_observer.get()); | 
 |   expected_touch_action = cc::TouchAction::kAuto; | 
 |   GetTouchActionsForChild(router, rwhv_root, rwhv_child, point_inside_child, | 
 |                           effective_touch_action, allowed_touch_action); | 
 |   EXPECT_EQ(expected_touch_action, effective_touch_action.has_value() | 
 |                                        ? effective_touch_action.value() | 
 |                                        : cc::TouchAction::kAuto); | 
 |   if (allowed_touch_action.has_value()) | 
 |     EXPECT_EQ(expected_touch_action, allowed_touch_action.value()); | 
 | } | 
 |  | 
 | // Flaky on all platform. http://crbug.com/9515270 | 
 | IN_PROC_BROWSER_TEST_F( | 
 |     SitePerProcessBrowserTouchActionTest, | 
 |     DISABLED_EffectiveTouchActionPropagatesAcrossNestedFrames) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b(c))")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   FrameTreeNode* parent = root->child_at(0); | 
 |   GURL b_url(embedded_test_server()->GetURL( | 
 |       "b.com", "/frame_tree/page_with_iframe_in_div.html")); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(parent, b_url)); | 
 |  | 
 |   ASSERT_EQ(1U, parent->child_count()); | 
 |   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://bar.com/", | 
 |       DepictFrameTree(root)); | 
 |  | 
 |   FrameTreeNode* child = root->child_at(0)->child_at(0); | 
 |   RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>( | 
 |       root->current_frame_host()->GetRenderWidgetHost()->GetView()); | 
 |   RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>( | 
 |       child->current_frame_host()->GetRenderWidgetHost()->GetView()); | 
 |   std::unique_ptr<MainThreadFrameObserver> root_thread_observer( | 
 |       new MainThreadFrameObserver( | 
 |           root->current_frame_host()->GetRenderWidgetHost())); | 
 |   root_thread_observer->Wait(); | 
 |  | 
 |   EXPECT_TRUE(ExecJs(shell(), "document.body.style.touchAction = 'none'")); | 
 |  | 
 |   // Wait for child frame ready in order to get the correct point inside child. | 
 |   WaitForHitTestData(child->current_frame_host()); | 
 |   std::unique_ptr<MainThreadFrameObserver> child_thread_observer( | 
 |       new MainThreadFrameObserver( | 
 |           child->current_frame_host()->GetRenderWidgetHost())); | 
 |   RenderWidgetHostViewChildFrame* child_view = | 
 |       static_cast<RenderWidgetHostViewChildFrame*>( | 
 |           child->current_frame_host()->GetRenderWidgetHost()->GetView()); | 
 |   gfx::Point point_inside_child = ToFlooredPoint( | 
 |       child_view->TransformPointToRootCoordSpaceF(gfx::PointF(+5.f, +5.f))); | 
 |  | 
 |   input::RenderWidgetHostInputEventRouter* router = | 
 |       static_cast<WebContentsImpl*>(web_contents())->GetInputEventRouter(); | 
 |  | 
 |   // Child should inherit effective touch action none from root. | 
 |   WaitForTouchActionUpdated(root_thread_observer.get(), | 
 |                             child_thread_observer.get()); | 
 |   std::optional<cc::TouchAction> effective_touch_action; | 
 |   std::optional<cc::TouchAction> allowed_touch_action; | 
 |   cc::TouchAction expected_touch_action = cc::TouchAction::kPan; | 
 |   GetTouchActionsForChild(router, rwhv_root, rwhv_child, point_inside_child, | 
 |                           effective_touch_action, allowed_touch_action); | 
 |   if (allowed_touch_action.has_value()) | 
 |     EXPECT_EQ(expected_touch_action, allowed_touch_action.value()); | 
 |  | 
 |   // Child should inherit effective touch action none from parent. | 
 |   EXPECT_TRUE(ExecJs(shell(), "document.body.style.touchAction = 'auto'")); | 
 |   EXPECT_TRUE(ExecJs( | 
 |       parent, | 
 |       "document.getElementById('parent-div').style.touchAction = 'none';")); | 
 |   WaitForTouchActionUpdated(root_thread_observer.get(), | 
 |                             child_thread_observer.get()); | 
 |   GetTouchActionsForChild(router, rwhv_root, rwhv_child, point_inside_child, | 
 |                           effective_touch_action, allowed_touch_action); | 
 |   if (allowed_touch_action.has_value()) | 
 |     EXPECT_EQ(expected_touch_action, allowed_touch_action.value()); | 
 |  | 
 |   // Child should inherit effective touch action auto from root and parent. | 
 |   EXPECT_TRUE(ExecJs( | 
 |       parent, | 
 |       "document.getElementById('parent-div').style.touchAction = 'auto'")); | 
 |   WaitForTouchActionUpdated(root_thread_observer.get(), | 
 |                             child_thread_observer.get()); | 
 |   expected_touch_action = cc::TouchAction::kAuto; | 
 |   GetTouchActionsForChild(router, rwhv_root, rwhv_child, point_inside_child, | 
 |                           effective_touch_action, allowed_touch_action); | 
 |   if (allowed_touch_action.has_value()) | 
 |     EXPECT_EQ(expected_touch_action, allowed_touch_action.value()); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTouchActionTest, | 
 |                        EffectiveTouchActionPropagatesWhenChildFrameNavigates) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   FrameTreeNode* child = root->child_at(0); | 
 |   GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(child, b_url)); | 
 |  | 
 |   EXPECT_EQ( | 
 |       " Site A ------------ proxies for B\n" | 
 |       "   +--Site B ------- proxies for A\n" | 
 |       "Where A = http://a.com/\n" | 
 |       "      B = http://b.com/", | 
 |       DepictFrameTree(root)); | 
 |  | 
 |   RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>( | 
 |       root->current_frame_host()->GetRenderWidgetHost()->GetView()); | 
 |   RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>( | 
 |       child->current_frame_host()->GetRenderWidgetHost()->GetView()); | 
 |   std::unique_ptr<MainThreadFrameObserver> root_thread_observer( | 
 |       new MainThreadFrameObserver( | 
 |           root->current_frame_host()->GetRenderWidgetHost())); | 
 |   root_thread_observer->Wait(); | 
 |  | 
 |   EXPECT_TRUE(ExecJs(shell(), "document.body.style.touchAction = 'none'")); | 
 |  | 
 |   // Wait for child frame ready in order to get the correct point inside child. | 
 |   WaitForHitTestData(child->current_frame_host()); | 
 |   std::unique_ptr<MainThreadFrameObserver> child_thread_observer( | 
 |       new MainThreadFrameObserver( | 
 |           child->current_frame_host()->GetRenderWidgetHost())); | 
 |   RenderWidgetHostViewChildFrame* child_view = | 
 |       static_cast<RenderWidgetHostViewChildFrame*>( | 
 |           child->current_frame_host()->GetRenderWidgetHost()->GetView()); | 
 |   gfx::Point point_inside_child = gfx::ToFlooredPoint( | 
 |       child_view->TransformPointToRootCoordSpaceF(gfx::PointF(+5.f, +5.f))); | 
 |  | 
 |   input::RenderWidgetHostInputEventRouter* router = | 
 |       static_cast<WebContentsImpl*>(web_contents())->GetInputEventRouter(); | 
 |   // Child should inherit effective touch action none from root. | 
 |   WaitForTouchActionUpdated(root_thread_observer.get(), | 
 |                             child_thread_observer.get()); | 
 |   std::optional<cc::TouchAction> effective_touch_action; | 
 |   std::optional<cc::TouchAction> allowed_touch_action; | 
 |   cc::TouchAction expected_touch_action = | 
 |       cc::TouchAction::kPan | cc::TouchAction::kInternalPanXScrolls | | 
 |       cc::TouchAction::kInternalNotWritable; | 
 |   GetTouchActionsForChild(router, rwhv_root, rwhv_child, point_inside_child, | 
 |                           effective_touch_action, allowed_touch_action); | 
 |   if (allowed_touch_action.has_value()) | 
 |     EXPECT_EQ(expected_touch_action, allowed_touch_action.value()); | 
 |  | 
 |   // After navigation, child should still inherit effective touch action none | 
 |   // from parent. | 
 |   GURL new_url(embedded_test_server()->GetURL("c.com", "/title2.html")); | 
 |   // Reset before navigation, as navigation destroys the underlying | 
 |   // RenderWidgetHost being observed. | 
 |   child_thread_observer.reset(); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(child, new_url)); | 
 |   WaitForHitTestData(child->current_frame_host()); | 
 |   // Navigation destroys the previous RenderWidgetHost, so we need to begin | 
 |   // observing the new renderer main thread associated with the child frame. | 
 |   child_thread_observer = std::make_unique<MainThreadFrameObserver>( | 
 |       child->current_frame_host()->GetRenderWidgetHost()); | 
 |  | 
 |   rwhv_child = static_cast<RenderWidgetHostViewBase*>( | 
 |       child->current_frame_host()->GetRenderWidgetHost()->GetView()); | 
 |  | 
 |   WaitForTouchActionUpdated(root_thread_observer.get(), | 
 |                             child_thread_observer.get()); | 
 |   GetTouchActionsForChild(router, rwhv_root, rwhv_child, point_inside_child, | 
 |                           effective_touch_action, allowed_touch_action); | 
 |   if (allowed_touch_action.has_value()) | 
 |     EXPECT_EQ(expected_touch_action, allowed_touch_action.value()); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        ChildFrameCrashMetrics_KilledMainFrame) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(a(b(b,c)))")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // Kill the main frame. | 
 |   base::HistogramTester histograms; | 
 |   RenderProcessHost* child_process = root->current_frame_host()->GetProcess(); | 
 |   RenderProcessHostWatcher crash_observer( | 
 |       child_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); | 
 |   child_process->Shutdown(0); | 
 |   crash_observer.Wait(); | 
 |  | 
 |   // Verify that no child frame metrics got logged. | 
 |   histograms.ExpectTotalCount("Stability.ChildFrameCrash.Visibility", 0); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        ChildFrameCrashMetrics_NeverShown) { | 
 |   // Set-up a frame tree that helps verify what the metrics tracks: | 
 |   // 1) frames (12 frames are affected if B process gets killed) or | 
 |   // 2) widgets (10 b widgets and 1 c widget are affected if B is killed) or | 
 |   // 3) crashes (1 crash if B process gets killed)? | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b(b,c),b,b,b,b,b,b,b,b,b)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // Hide the web contents (UpdateWebContentsVisibility is called twice to avoid | 
 |   // hitting the |!did_first_set_visible_| case). | 
 |   web_contents()->UpdateWebContentsVisibility(Visibility::VISIBLE); | 
 |   web_contents()->UpdateWebContentsVisibility(Visibility::HIDDEN); | 
 |  | 
 |   // Kill the subframe. | 
 |   base::HistogramTester histograms; | 
 |   RenderProcessHost* child_process = | 
 |       root->child_at(0)->current_frame_host()->GetProcess(); | 
 |   RenderProcessHostWatcher crash_observer( | 
 |       child_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); | 
 |   child_process->Shutdown(0); | 
 |   crash_observer.Wait(); | 
 |  | 
 |   // Navigate away - this will trigger logging of the UMA. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank"))); | 
 |  | 
 |   // Wait until the page with the crashed frame gets unloaded (triggering its | 
 |   // evicton if it got into the back/forward cache), so that the histogram will | 
 |   // be recorded when the renderer process is gone. | 
 |   // TODO(crbug.com/40175240): Ensure pages with crashed subframes won't | 
 |   // get into back/forward cache. | 
 |   InactiveRenderFrameHostDeletionObserver inactive_rfh_deletion_observer( | 
 |       web_contents()); | 
 |   inactive_rfh_deletion_observer.Wait(); | 
 |  | 
 |   histograms.ExpectUniqueSample("Stability.ChildFrameCrash.Visibility", | 
 |                                 CrashVisibility::kNeverVisibleAfterCrash, 10); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        ChildFrameCrashMetrics_ScrolledIntoView) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // Fill the main frame so that the subframe is pushed below the fold (is | 
 |   // scrolled outside of the current view) and wait until the main frame redraws | 
 |   // itself (i.e. making sure CPFC::OnUpdateViewportIntersection has arrived). | 
 |   std::string filling_script = R"( | 
 |     var frame = document.body.querySelectorAll("iframe")[0]; | 
 |     for (var i = 0; i < 100; i++) { | 
 |       var p = document.createElement("p"); | 
 |       p.innerText = "blah"; | 
 |       document.body.insertBefore(p, frame); | 
 |     } | 
 |   )"; | 
 |   EXPECT_TRUE(ExecJs(root, filling_script)); | 
 |   // This will ensure that browser has received the | 
 |   // FrameHostMsg_UpdateViewportIntersection IPC message from the renderer main | 
 |   // thread. | 
 |   EXPECT_EQ(true, | 
 |             EvalJsAfterLifecycleUpdate(root->current_frame_host(), "", "true")); | 
 |  | 
 |   // Kill the child frame. | 
 |   base::HistogramTester histograms; | 
 |   RenderProcessHost* child_process = | 
 |       root->child_at(0)->current_frame_host()->GetProcess(); | 
 |   RenderProcessHostWatcher crash_observer( | 
 |       child_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); | 
 |   child_process->Shutdown(0); | 
 |   crash_observer.Wait(); | 
 |  | 
 |   // Verify that no child frame metrics got logged (yet - while the subframe is | 
 |   // below the fold / is not scrolled into view). | 
 |   histograms.ExpectTotalCount("Stability.ChildFrameCrash.Visibility", 0); | 
 |   histograms.ExpectTotalCount( | 
 |       "Stability.ChildFrameCrash.ShownAfterCrashingReason", 0); | 
 |  | 
 |   // Scroll the subframe into view and wait until the scrolled frame draws | 
 |   // itself. | 
 |   std::string scrolling_script = R"( | 
 |     var frame = document.body.querySelectorAll("iframe")[0]; | 
 |     frame.scrollIntoView(); | 
 |   )"; | 
 |   EXPECT_TRUE(ExecJs(root, scrolling_script)); | 
 |   // Wait for FrameHostMsg_UpdateViewportIntersection again. | 
 |   EXPECT_EQ(true, | 
 |             EvalJsAfterLifecycleUpdate(root->current_frame_host(), "", "true")); | 
 |  | 
 |   // Verify that the expected metrics got logged. | 
 |   histograms.ExpectUniqueSample( | 
 |       "Stability.ChildFrameCrash.Visibility", | 
 |       CrossProcessFrameConnector::CrashVisibility::kShownAfterCrashing, 1); | 
 |   histograms.ExpectUniqueSample( | 
 |       "Stability.ChildFrameCrash.ShownAfterCrashingReason", | 
 |       CrossProcessFrameConnector::ShownAfterCrashingReason:: | 
 |           kViewportIntersection, | 
 |       1); | 
 | } | 
 |  | 
 | class SitePerProcessAndProcessPerSiteBrowserTest | 
 |     : public SitePerProcessBrowserTest { | 
 |  public: | 
 |   SitePerProcessAndProcessPerSiteBrowserTest() {} | 
 |  | 
 |   SitePerProcessAndProcessPerSiteBrowserTest( | 
 |       const SitePerProcessAndProcessPerSiteBrowserTest&) = delete; | 
 |   SitePerProcessAndProcessPerSiteBrowserTest& operator=( | 
 |       const SitePerProcessAndProcessPerSiteBrowserTest&) = delete; | 
 |  | 
 |  protected: | 
 |   void SetUpCommandLine(base::CommandLine* command_line) override { | 
 |     SitePerProcessBrowserTestBase::SetUpCommandLine(command_line); | 
 |     command_line->AppendSwitch(switches::kProcessPerSite); | 
 |   } | 
 | }; | 
 |  | 
 | // Verify that when --site-per-process is combined with --process-per-site, a | 
 | // cross-site, browser-initiated navigation with a generated page transition | 
 | // does not stay in the old SiteInstance.  See https://crbug.com/825411. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessAndProcessPerSiteBrowserTest, | 
 |                        GeneratedTransitionsSwapProcesses) { | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("foo.com", "/title1.html"))); | 
 |   scoped_refptr<SiteInstance> foo_site_instance( | 
 |       web_contents()->GetSiteInstance()); | 
 |  | 
 |   // Navigate cross-site via a generated transition.  This would normally | 
 |   // happen for search queries. | 
 |   TestNavigationObserver observer(web_contents()); | 
 |   NavigationController::LoadURLParams params( | 
 |       embedded_test_server()->GetURL("bar.com", "/title2.html")); | 
 |   params.transition_type = ui::PAGE_TRANSITION_GENERATED; | 
 |   web_contents()->GetController().LoadURLWithParams(params); | 
 |   observer.Wait(); | 
 |  | 
 |   // Ensure the original SiteInstance wasn't reused. | 
 |   EXPECT_NE(foo_site_instance, web_contents()->GetSiteInstance()); | 
 |  | 
 |   // Ensure the new page can access cookies without getting killed. | 
 |   EXPECT_TRUE(ExecJs(web_contents(), "document.cookie = 'foo=bar';")); | 
 |   EXPECT_EQ("foo=bar", EvalJs(web_contents(), "document.cookie;")); | 
 | } | 
 |  | 
 | namespace { | 
 |  | 
 | // Helper for waiting until next same-document navigation commits in | 
 | // |web_contents|. | 
 | class SameDocumentCommitObserver : public WebContentsObserver { | 
 |  public: | 
 |   explicit SameDocumentCommitObserver(WebContents* web_contents) | 
 |       : WebContentsObserver(web_contents) { | 
 |     EXPECT_TRUE(web_contents); | 
 |   } | 
 |  | 
 |   SameDocumentCommitObserver(const SameDocumentCommitObserver&) = delete; | 
 |   SameDocumentCommitObserver& operator=(const SameDocumentCommitObserver&) = | 
 |       delete; | 
 |  | 
 |   void Wait() { run_loop_.Run(); } | 
 |  | 
 |   const GURL& last_committed_url() { return last_committed_url_; } | 
 |  | 
 |  private: | 
 |   void DidFinishNavigation(NavigationHandle* navigation_handle) override { | 
 |     if (navigation_handle->IsSameDocument()) { | 
 |       last_committed_url_ = navigation_handle->GetURL(); | 
 |       run_loop_.Quit(); | 
 |     } | 
 |   } | 
 |  | 
 |   GURL last_committed_url_; | 
 |   base::RunLoop run_loop_; | 
 | }; | 
 |  | 
 | }  // namespace | 
 |  | 
 | // Ensure that a same-document navigation does not cancel an ongoing | 
 | // cross-process navigation.  See https://crbug.com/825677. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        ReplaceStateDoesNotCancelCrossSiteNavigation) { | 
 |   GURL url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url)); | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // Give the page a beforeunload handler that does a replaceState.  Do this | 
 |   // from setTimeout so that the navigation that triggers beforeunload is | 
 |   // already started when the replaceState happens. | 
 |   EXPECT_TRUE(ExecJs(root, | 
 |                      "window.onbeforeunload = function (e) {" | 
 |                      "  setTimeout(() => {" | 
 |                      "    history.replaceState({}, 'footitle', 'foo');" | 
 |                      "  }, 0);" | 
 |                      "};\n")); | 
 |  | 
 |   GURL url2 = embedded_test_server()->GetURL("b.com", "/title1.html"); | 
 |   TestNavigationManager cross_site_navigation(web_contents(), url2); | 
 |   SameDocumentCommitObserver replace_state_observer(web_contents()); | 
 |  | 
 |   // Start a cross-site navigation.  Using a renderer-initiated navigation | 
 |   // rather than a browser-initiated one is important here, since | 
 |   // https://crbug.com/825677 was triggered only when replaceState ran while | 
 |   // having a user gesture, which will be the case here since ExecJs | 
 |   // runs with a user gesture. | 
 |   EXPECT_TRUE(ExecJs(root, JsReplace("location.href = $1", url2))); | 
 |   EXPECT_TRUE(cross_site_navigation.WaitForRequestStart()); | 
 |  | 
 |   // Now wait for the replaceState to commit while the cross-process navigation | 
 |   // is paused. | 
 |   replace_state_observer.Wait(); | 
 |   GURL replace_state_url = embedded_test_server()->GetURL("a.com", "/foo"); | 
 |   EXPECT_EQ(replace_state_url, replace_state_observer.last_committed_url()); | 
 |  | 
 |   // The cross-process navigation should not be canceled after the | 
 |   // replaceState. | 
 |   ASSERT_TRUE(root->IsLoading()); | 
 |   ASSERT_TRUE(root->navigation_request()); | 
 |  | 
 |   // Resume and finish the cross-process navigation. | 
 |   cross_site_navigation.ResumeNavigation(); | 
 |   ASSERT_TRUE(cross_site_navigation.WaitForNavigationFinished()); | 
 |   EXPECT_TRUE(cross_site_navigation.was_successful()); | 
 |   EXPECT_EQ(url2, web_contents()->GetLastCommittedURL()); | 
 | } | 
 |  | 
 | // Test that a pending frame policy, such as an updated sandbox attribute, does | 
 | // not take effect after a same-document navigation.  See | 
 | // https://crbug.com/849311. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        SameDocumentNavigationDoesNotCommitPendingFramePolicy) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   FrameTreeNode* subframe = root->child_at(0); | 
 |  | 
 |   // The subframe should not be sandboxed. | 
 |   EXPECT_EQ(network::mojom::WebSandboxFlags::kNone, | 
 |             subframe->pending_frame_policy().sandbox_flags); | 
 |   EXPECT_EQ(network::mojom::WebSandboxFlags::kNone, | 
 |             subframe->effective_frame_policy().sandbox_flags); | 
 |  | 
 |   // Set the "sandbox" attribute on the subframe; pending policy should update. | 
 |   EXPECT_TRUE(ExecJs( | 
 |       root, "document.querySelector('iframe').sandbox = 'allow-scripts';")); | 
 |   // "allow-scripts" resets both SandboxFlags::Scripts and | 
 |   // SandboxFlags::AutomaticFeatures bits per blink::ParseSandboxPolicy(). | 
 |   network::mojom::WebSandboxFlags expected_flags = | 
 |       network::mojom::WebSandboxFlags::kAll & | 
 |       ~network::mojom::WebSandboxFlags::kScripts & | 
 |       ~network::mojom::WebSandboxFlags::kAutomaticFeatures; | 
 |   EXPECT_EQ(expected_flags, subframe->pending_frame_policy().sandbox_flags); | 
 |   EXPECT_EQ(network::mojom::WebSandboxFlags::kNone, | 
 |             subframe->effective_frame_policy().sandbox_flags); | 
 |  | 
 |   // Commit a same-document navigation with replaceState.  The new sandbox | 
 |   // flags should still be pending but not effective. | 
 |   SameDocumentCommitObserver replace_state_observer(web_contents()); | 
 |   EXPECT_TRUE(ExecJs(subframe, "history.replaceState({}, 'footitle', 'foo');")); | 
 |   replace_state_observer.Wait(); | 
 |  | 
 |   EXPECT_EQ(expected_flags, subframe->pending_frame_policy().sandbox_flags); | 
 |   EXPECT_EQ(network::mojom::WebSandboxFlags::kNone, | 
 |             subframe->effective_frame_policy().sandbox_flags); | 
 |  | 
 |   // Also try a same-document navigation to a fragment, which also shouldn't | 
 |   // commit the pending sandbox flags. | 
 |   GURL fragment_url = GURL(subframe->current_url().spec() + "#foo"); | 
 |   { | 
 |     SameDocumentCommitObserver fragment_observer(web_contents()); | 
 |     EXPECT_TRUE(ExecJs(subframe, JsReplace("location.href=$1", fragment_url))); | 
 |     fragment_observer.Wait(); | 
 |     EXPECT_EQ(fragment_url, subframe->current_url()); | 
 |   } | 
 |  | 
 |   EXPECT_EQ(expected_flags, subframe->pending_frame_policy().sandbox_flags); | 
 |   EXPECT_EQ(network::mojom::WebSandboxFlags::kNone, | 
 |             subframe->effective_frame_policy().sandbox_flags); | 
 | } | 
 |  | 
 | // Ensure that when two cross-site frames have subframes with unique origins, | 
 | // and those subframes create blob URLs and navigate to them, the blob URLs end | 
 | // up in different processes. See https://crbug.com/863623. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        TwoBlobURLsWithNullOriginDontShareProcess) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/navigation_controller/page_with_data_iframe.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   FrameTreeNode* subframe = root->child_at(0); | 
 |  | 
 |   // Create a blob URL in the subframe, and navigate to it. | 
 |   TestNavigationObserver observer(shell()->web_contents()); | 
 |   std::string blob_script = | 
 |       "var blob = new Blob(['foo'], {type : 'text/html'});" | 
 |       "var url = URL.createObjectURL(blob);" | 
 |       "location = url;"; | 
 |   EXPECT_TRUE(ExecJs(subframe, blob_script)); | 
 |   observer.Wait(); | 
 |   RenderFrameHostImpl* subframe_rfh = subframe->current_frame_host(); | 
 |   EXPECT_TRUE(subframe_rfh->GetLastCommittedURL().SchemeIsBlob()); | 
 |  | 
 |   // Open a cross-site popup and repeat these steps. | 
 |   GURL popup_url(embedded_test_server()->GetURL( | 
 |       "b.com", "/navigation_controller/page_with_data_iframe.html")); | 
 |   Shell* new_shell = OpenPopup(root, popup_url, ""); | 
 |   FrameTreeNode* popup_root = | 
 |       static_cast<WebContentsImpl*>(new_shell->web_contents()) | 
 |           ->GetPrimaryFrameTree() | 
 |           .root(); | 
 |   FrameTreeNode* popup_subframe = popup_root->child_at(0); | 
 |  | 
 |   TestNavigationObserver popup_observer(new_shell->web_contents()); | 
 |   EXPECT_TRUE(ExecJs(popup_subframe, blob_script)); | 
 |   popup_observer.Wait(); | 
 |   RenderFrameHostImpl* popup_subframe_rfh = | 
 |       popup_subframe->current_frame_host(); | 
 |   EXPECT_TRUE(popup_subframe_rfh->GetLastCommittedURL().SchemeIsBlob()); | 
 |  | 
 |   // Ensure that the two blob subframes don't share a process or SiteInstance. | 
 |   EXPECT_NE(subframe->current_frame_host()->GetSiteInstance(), | 
 |             popup_subframe->current_frame_host()->GetSiteInstance()); | 
 |   EXPECT_NE( | 
 |       subframe->current_frame_host()->GetSiteInstance()->GetProcess(), | 
 |       popup_subframe->current_frame_host()->GetSiteInstance()->GetProcess()); | 
 |   EXPECT_NE( | 
 |       subframe->current_frame_host()->GetSiteInstance()->GetSiteURL(), | 
 |       popup_subframe->current_frame_host()->GetSiteInstance()->GetSiteURL()); | 
 | } | 
 |  | 
 | // Ensure that when a process is about to be destroyed after the last active | 
 | // frame in it goes away, an attempt to reuse a proxy in that process doesn't | 
 | // result in a crash.  See https://crbug.com/794625. | 
 | // TODO(crbug.com/42050611): This is flaky on Fuchsia because the | 
 | // MessagePort is not cleared on the other side, resulting in Zircon killing the | 
 | // process. See the comment referencing the same bug in | 
 | // //mojo/core/channel_fuchsia.cc | 
 | #if BUILDFLAG(IS_FUCHSIA) | 
 | #define MAYBE_RenderFrameProxyNotRecreatedDuringProcessShutdown \ | 
 |   DISABLED_RenderFrameProxyNotRecreatedDuringProcessShutdown | 
 | #else | 
 | #define MAYBE_RenderFrameProxyNotRecreatedDuringProcessShutdown \ | 
 |   RenderFrameProxyNotRecreatedDuringProcessShutdown | 
 | #endif | 
 | IN_PROC_BROWSER_TEST_P( | 
 |     SitePerProcessBrowserTest, | 
 |     MAYBE_RenderFrameProxyNotRecreatedDuringProcessShutdown) { | 
 |   DisableBackForwardCacheForTesting( | 
 |       web_contents(), content::BackForwardCache::TEST_REQUIRES_NO_CACHING); | 
 |   GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   GURL popup_url(embedded_test_server()->GetURL( | 
 |       "b.com", "/title1.html")); | 
 |   Shell* new_shell = OpenPopup(root, popup_url, "foo"); | 
 |   FrameTreeNode* popup_root = | 
 |       static_cast<WebContentsImpl*>(new_shell->web_contents()) | 
 |           ->GetPrimaryFrameTree() | 
 |           .root(); | 
 |   auto* rfh = popup_root->current_frame_host(); | 
 |  | 
 |   // Disable the unload timer to prevent flakiness. | 
 |   rfh->DisableUnloadTimerForTesting(); | 
 |  | 
 |   // This will be used to monitor that b.com process exits cleanly. | 
 |   RenderProcessHostWatcher b_process_observer( | 
 |       popup_root->current_frame_host()->GetProcess(), | 
 |       RenderProcessHostWatcher::WATCH_FOR_HOST_DESTRUCTION); | 
 |  | 
 |   // In the first tab, install a postMessage handler to navigate the popup to a | 
 |   // hung b.com URL once the first message is received. | 
 |   GURL hung_b_url(embedded_test_server()->GetURL("b.com", "/hung")); | 
 |   TestNavigationManager manager(new_shell->web_contents(), hung_b_url); | 
 |   EXPECT_TRUE(ExecJs(shell(), JsReplace(R"( | 
 |       window.done = false; | 
 |       window.onmessage = () => { | 
 |         if (!window.done) { | 
 |           window.open($1, 'foo'); | 
 |           window.done = true; | 
 |         } | 
 |       };)", | 
 |                                         hung_b_url))); | 
 |  | 
 |   // In the popup, install a pagehide handler to send a lot of postMessages to | 
 |   // the opener.  This keeps the MessageLoop in the b.com process busy after | 
 |   // navigating away from the current document.  In https://crbug.com/794625, | 
 |   // this was needed so that a subsequent IPC to recreate a proxy arrives | 
 |   // before the process fully shuts down. | 
 |   EXPECT_TRUE(ExecJs(new_shell, R"( | 
 |       window.onpagehide = () => { | 
 |         for (var i=0; i<10000; i++) | 
 |           opener.postMessage('hi','*'); | 
 |       })")); | 
 |  | 
 |   // Navigate popup to a.com.  This unloads the last active frame in the b.com | 
 |   // process, and hence initiates process shutdown. | 
 |   TestFrameNavigationObserver commit_observer(popup_root); | 
 |   GURL another_a_url(embedded_test_server()->GetURL("a.com", "/title3.html")); | 
 |   EXPECT_TRUE(ExecJs(new_shell, JsReplace("location = $1", another_a_url))); | 
 |   commit_observer.WaitForCommit(); | 
 |  | 
 |   // At this point, popup's original RFH is pending deletion. | 
 |   EXPECT_TRUE(rfh->IsPendingDeletion()); | 
 |  | 
 |   // When the opener receives a postMessage from the popup's pagehide handler, | 
 |   // it should start a navigation back to b.com.  Wait for it.  This navigation | 
 |   // creates a speculative RFH which reuses the proxy that was created as part | 
 |   // of navigating from |popup_url| to |another_a_url|. | 
 |   EXPECT_TRUE(manager.WaitForRequestStart()); | 
 |  | 
 |   // Cancel the started navigation (to /hung) in the popup and make sure the | 
 |   // b.com renderer process exits cleanly without a crash.  In | 
 |   // https://crbug.com/794625, the crash was caused by trying to recreate the | 
 |   // reused proxy, which had been incorrectly set as non-live. | 
 |   popup_root->ResetNavigationRequest( | 
 |       NavigationDiscardReason::kExplicitCancellation); | 
 |   b_process_observer.Wait(); | 
 |   EXPECT_TRUE(b_process_observer.did_exit_normally()); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        CommitTimeoutForHungRenderer) { | 
 |   // Navigate first tab to a.com. | 
 |   GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), a_url)); | 
 |   RenderProcessHost* a_process = | 
 |       shell()->web_contents()->GetPrimaryMainFrame()->GetProcess(); | 
 |  | 
 |   // Open b.com in a second tab.  Using a renderer-initiated navigation is | 
 |   // important to leave a.com and b.com SiteInstances in the same | 
 |   // BrowsingInstance (so the b.com -> a.com navigation in the next test step | 
 |   // will reuse the process associated with the first a.com tab). | 
 |   GURL b_url(embedded_test_server()->GetURL("b.com", "/title2.html")); | 
 |   Shell* new_shell = OpenPopup(shell()->web_contents(), b_url, "newtab"); | 
 |   WebContents* new_contents = new_shell->web_contents(); | 
 |   EXPECT_TRUE(WaitForLoadStop(new_contents)); | 
 |   RenderProcessHost* b_process = | 
 |       new_contents->GetPrimaryMainFrame()->GetProcess(); | 
 |   EXPECT_NE(a_process, b_process); | 
 |  | 
 |   // Hang the first tab's renderer. | 
 |   const char* kHungScript = "setTimeout(function() { for (;;) {}; }, 0);"; | 
 |   EXPECT_TRUE(ExecJs(shell()->web_contents(), kHungScript)); | 
 |  | 
 |   // Attempt to navigate the second tab to a.com.  This will attempt to reuse | 
 |   // the hung process. | 
 |   NavigationRequest::SetCommitTimeoutForTesting(base::Milliseconds(100)); | 
 |   GURL hung_url(embedded_test_server()->GetURL("a.com", "/title3.html")); | 
 |   UnresponsiveRendererObserver unresponsive_renderer_observer(new_contents); | 
 |   EXPECT_TRUE( | 
 |       ExecJs(new_contents, JsReplace("window.location = $1", hung_url))); | 
 |  | 
 |   // Verify that we will be notified about the unresponsive renderer.  Before | 
 |   // changes in https://crrev.com/c/1089797, the test would hang here forever. | 
 |   RenderProcessHost* hung_process = unresponsive_renderer_observer.Wait(); | 
 |   EXPECT_EQ(hung_process, a_process); | 
 |  | 
 |   // Reset the timeout. | 
 |   NavigationRequest::SetCommitTimeoutForTesting(base::TimeDelta()); | 
 | } | 
 |  | 
 | // This is a regression test for https://crbug.com/881812 which complained that | 
 | // the hung renderer dialog used to undesirably show up for background tabs | 
 | // (typically during session restore when many navigations would be happening in | 
 | // backgrounded processes). | 
 | // TODO(crbug.com/40196588): Flaky on Mac and Windows. | 
 | #if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) | 
 | #define MAYBE_NoCommitTimeoutForInvisibleWebContents \ | 
 |   DISABLED_NoCommitTimeoutForInvisibleWebContents | 
 | #else | 
 | #define MAYBE_NoCommitTimeoutForInvisibleWebContents \ | 
 |   NoCommitTimeoutForInvisibleWebContents | 
 | #endif | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        MAYBE_NoCommitTimeoutForInvisibleWebContents) { | 
 |   // Navigate first tab to a.com. | 
 |   GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), a_url)); | 
 |   RenderProcessHost* a_process = | 
 |       shell()->web_contents()->GetPrimaryMainFrame()->GetProcess(); | 
 |  | 
 |   // Open b.com in a second tab.  Using a renderer-initiated navigation is | 
 |   // important to leave a.com and b.com SiteInstances in the same | 
 |   // BrowsingInstance (so the b.com -> a.com navigation in the next test step | 
 |   // will reuse the process associated with the first a.com tab). | 
 |   GURL b_url(embedded_test_server()->GetURL("b.com", "/title2.html")); | 
 |   Shell* new_shell = OpenPopup(shell()->web_contents(), b_url, "newtab"); | 
 |   WebContents* new_contents = new_shell->web_contents(); | 
 |   EXPECT_TRUE(WaitForLoadStop(new_contents)); | 
 |   RenderProcessHost* b_process = | 
 |       new_contents->GetPrimaryMainFrame()->GetProcess(); | 
 |   EXPECT_NE(a_process, b_process); | 
 |  | 
 |   // Hang the first tab's renderer. | 
 |   const char* kHungScript = "setTimeout(function() { for (;;) {}; }, 0);"; | 
 |   EXPECT_TRUE(ExecJs(shell()->web_contents(), kHungScript)); | 
 |  | 
 |   // Hide the second tab.  This should prevent reporting of hangs in this tab | 
 |   // (see https://crbug.com/881812). | 
 |   new_contents->WasHidden(); | 
 |   EXPECT_EQ(Visibility::HIDDEN, new_contents->GetVisibility()); | 
 |  | 
 |   // Attempt to navigate the second tab to a.com.  This will attempt to reuse | 
 |   // the hung process. | 
 |   base::TimeDelta kTimeout = base::Milliseconds(100); | 
 |   NavigationRequest::SetCommitTimeoutForTesting(kTimeout); | 
 |   GURL hung_url(embedded_test_server()->GetURL("a.com", "/title3.html")); | 
 |   UnresponsiveRendererObserver unresponsive_renderer_observer(new_contents); | 
 |   EXPECT_TRUE( | 
 |       ExecJs(new_contents, JsReplace("window.location = $1", hung_url))); | 
 |  | 
 |   // Verify that we will not be notified about the unresponsive renderer. | 
 |   // Before changes in https://crrev.com/c/1089797, the test would get notified | 
 |   // and therefore |hung_process| would be non-null. | 
 |   RenderProcessHost* hung_process = | 
 |       unresponsive_renderer_observer.Wait(kTimeout * 10); | 
 |   EXPECT_FALSE(hung_process); | 
 |  | 
 |   // Reset the timeout. | 
 |   NavigationRequest::SetCommitTimeoutForTesting(base::TimeDelta()); | 
 | } | 
 |  | 
 | // Tests that an inner WebContents will reattach to its outer WebContents after | 
 | // a navigation that causes a process swap. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, ProcessSwapOnInnerContents) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(a)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   FrameTreeNode* child_frame = | 
 |       web_contents()->GetPrimaryFrameTree().root()->child_at(0); | 
 |   WebContentsImpl* inner_contents = | 
 |       static_cast<WebContentsImpl*>(CreateAndAttachInnerContents( | 
 |           ToRenderFrameHost(child_frame).render_frame_host())); | 
 |   FrameTreeNode* inner_contents_root = | 
 |       inner_contents->GetPrimaryFrameTree().root(); | 
 |   RenderFrameProxyHost* outer_proxy = | 
 |       inner_contents_root->render_manager()->GetProxyToOuterDelegate(); | 
 |   CrossProcessFrameConnector* outer_connector = | 
 |       outer_proxy->cross_process_frame_connector(); | 
 |   EXPECT_NE(nullptr, outer_connector->get_view_for_testing()); | 
 |  | 
 |   GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(inner_contents_root, a_url)); | 
 |   SiteInstance* a_site_instance = | 
 |       inner_contents->GetPrimaryMainFrame()->GetSiteInstance(); | 
 |   RenderProcessHost* a_process = a_site_instance->GetProcess(); | 
 |   RenderWidgetHostViewChildFrame* a_view = | 
 |       outer_connector->get_view_for_testing(); | 
 |  | 
 |   GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(inner_contents_root, b_url)); | 
 |   SiteInstance* b_site_instance = | 
 |       inner_contents->GetPrimaryMainFrame()->GetSiteInstance(); | 
 |   RenderProcessHost* b_process = b_site_instance->GetProcess(); | 
 |   RenderWidgetHostViewChildFrame* b_view = | 
 |       outer_connector->get_view_for_testing(); | 
 |  | 
 |   // Ensure that the SiteInstances have changed, we've completed a process swap | 
 |   // and reattached the inner WebContents creating a new RenderWidgetHostView. | 
 |   EXPECT_NE(a_site_instance, b_site_instance); | 
 |   EXPECT_NE(a_process, b_process); | 
 |   EXPECT_NE(nullptr, a_view); | 
 |   EXPECT_NE(nullptr, b_view); | 
 |   EXPECT_NE(a_view, b_view); | 
 | } | 
 |  | 
 | // This test ensures that WebContentsImpl::FocusOwningWebContents() focuses an | 
 | // inner WebContents when it is given an OOPIF's RenderWidgetHost inside that | 
 | // inner WebContents.  This setup isn't currently supported in Chrome | 
 | // (requiring issue 614463), but it can happen in embedders.  See | 
 | // https://crbug.com/1026056. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, FocusInnerContentsFromOOPIF) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(a)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   // Set up and attach an artificial inner WebContents. | 
 |   FrameTreeNode* child_frame = | 
 |       web_contents()->GetPrimaryFrameTree().root()->child_at(0); | 
 |   WebContentsImpl* inner_contents = | 
 |       static_cast<WebContentsImpl*>(CreateAndAttachInnerContents( | 
 |           ToRenderFrameHost(child_frame).render_frame_host())); | 
 |   FrameTreeNode* inner_contents_root = | 
 |       inner_contents->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // Navigate inner WebContents to b.com, and then navigate a subframe on that | 
 |   // page to c.com. | 
 |   GURL b_url(embedded_test_server()->GetURL( | 
 |       "b.com", "/cross_site_iframe_factory.html?b(b)")); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(inner_contents_root, b_url)); | 
 |   GURL c_url(embedded_test_server()->GetURL("c.com", "/title1.html")); | 
 |   FrameTreeNode* inner_child = inner_contents_root->child_at(0); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(inner_child, c_url)); | 
 |  | 
 |   // Because |inner_contents| was set up without kGuestScheme, it can actually | 
 |   // have OOPIFs.  Ensure that the subframe is in an OOPIF. | 
 |   EXPECT_NE(inner_contents_root->current_frame_host()->GetSiteInstance(), | 
 |             inner_child->current_frame_host()->GetSiteInstance()); | 
 |   EXPECT_TRUE(inner_child->current_frame_host()->IsCrossProcessSubframe()); | 
 |  | 
 |   // Make sure the outer WebContents is focused to start with. | 
 |   web_contents()->Focus(); | 
 |   web_contents()->SetAsFocusedWebContentsIfNecessary(); | 
 |   EXPECT_EQ(web_contents(), web_contents()->GetFocusedWebContents()); | 
 |  | 
 |   // Focus the inner WebContents as if an event were received and dispatched | 
 |   // directly on the |inner_child|'s RenderWidgetHost, and ensure that this | 
 |   // took effect. | 
 |   inner_contents->FocusOwningWebContents( | 
 |       inner_child->current_frame_host()->GetRenderWidgetHost()); | 
 |   EXPECT_EQ(inner_contents, web_contents()->GetFocusedWebContents()); | 
 | } | 
 |  | 
 | // Check that a web frame can't navigate a remote subframe to a file: URL.  The | 
 | // frame should stay at the old URL, and the navigation attempt should produce | 
 | // a console error message.  See https://crbug.com/894399. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        FileURLBlockedWithConsoleErrorInRemoteFrameNavigation) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   FrameTreeNode* child = | 
 |       web_contents()->GetPrimaryFrameTree().root()->child_at(0); | 
 |   GURL original_frame_url(child->current_frame_host()->GetLastCommittedURL()); | 
 |   EXPECT_EQ("b.com", original_frame_url.host()); | 
 |  | 
 |   WebContentsConsoleObserver console_observer(web_contents()); | 
 |   console_observer.SetPattern("Not allowed to load local resource: file:*"); | 
 |  | 
 |   GURL file_url("file:///"); | 
 |   EXPECT_TRUE( | 
 |       ExecJs(web_contents(), | 
 |              JsReplace("document.querySelector('iframe').src = $1", file_url))); | 
 |   ASSERT_TRUE(console_observer.Wait()); | 
 |  | 
 |   // The iframe should've stayed at the original URL. | 
 |   EXPECT_EQ(original_frame_url, | 
 |             child->current_frame_host()->GetLastCommittedURL()); | 
 | } | 
 |  | 
 | // Touchscreen DoubleTapZoom is only supported on Android & ChromeOS at present. | 
 | #if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID) | 
 | // A test ContentBrowserClient implementation which enforces | 
 | // WebPreferences' |double_tap_to_zoom_enabled| to be true. | 
 | class DoubleTapZoomContentBrowserClient | 
 |     : public ContentBrowserTestContentBrowserClient { | 
 |  public: | 
 |   DoubleTapZoomContentBrowserClient() = default; | 
 |  | 
 |   DoubleTapZoomContentBrowserClient(const DoubleTapZoomContentBrowserClient&) = | 
 |       delete; | 
 |   DoubleTapZoomContentBrowserClient& operator=( | 
 |       const DoubleTapZoomContentBrowserClient&) = delete; | 
 |  | 
 |   void OverrideWebPreferences( | 
 |       content::WebContents* web_contents, | 
 |       SiteInstance& main_frame_site, | 
 |       blink::web_pref::WebPreferences* web_prefs) override { | 
 |     web_prefs->double_tap_to_zoom_enabled = true; | 
 |   } | 
 | }; | 
 |  | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        TouchscreenAnimateDoubleTapZoomInOOPIF) { | 
 |   // Install a client forcing double-tap zoom to be enabled. | 
 |   DoubleTapZoomContentBrowserClient content_browser_client; | 
 |   web_contents()->OnWebPreferencesChanged(); | 
 |  | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   ASSERT_EQ(1u, root->child_count()); | 
 |   FrameTreeNode* child_b = root->child_at(0); | 
 |   ASSERT_TRUE(child_b); | 
 |  | 
 |   RenderFrameSubmissionObserver observer_a(root); | 
 |   // We need to observe a root frame submission to pick up the initial page | 
 |   // scale factor. | 
 |   observer_a.WaitForAnyFrameSubmission(); | 
 |   float original_page_scale = | 
 |       observer_a.LastRenderFrameMetadata().page_scale_factor; | 
 |  | 
 |   // Must do this before it's safe to use the coordinate transform functions. | 
 |   WaitForHitTestData(child_b->current_frame_host()); | 
 |  | 
 |   // Select a tap point inside the OOPIF. | 
 |   gfx::PointF tap_position = | 
 |       child_b->current_frame_host() | 
 |           ->GetRenderWidgetHost() | 
 |           ->GetView() | 
 |           ->TransformPointToRootCoordSpaceF(gfx::PointF(10, 10)); | 
 |  | 
 |   // Generate a double-tap. | 
 |   static constexpr char kActionsTemplate[] = R"HTML( | 
 |       [{ | 
 |         "source" : "touch", | 
 |         "actions" : [ | 
 |           { "name": "pointerDown", "x": %f, "y": %f}, | 
 |           { "name": "pointerUp"}, | 
 |           { "name": "pause", "duration": 50 }, | 
 |           { "name": "pointerDown", "x": %f, "y": %f}, | 
 |           { "name": "pointerUp"} | 
 |         ] | 
 |       }] | 
 |   )HTML"; | 
 |   std::string double_tap_actions_json = | 
 |       base::StringPrintf(kActionsTemplate, tap_position.x(), tap_position.y(), | 
 |                          tap_position.x(), tap_position.y()); | 
 |   auto parsed_json = | 
 |       base::JSONReader::ReadAndReturnValueWithError(double_tap_actions_json); | 
 |   ASSERT_TRUE(parsed_json.has_value()) << parsed_json.error().message; | 
 |   ActionsParser actions_parser(std::move(*parsed_json)); | 
 |  | 
 |   ASSERT_TRUE(actions_parser.Parse()); | 
 |   auto synthetic_gesture_doubletap = std::make_unique<SyntheticPointerAction>( | 
 |       actions_parser.pointer_action_params()); | 
 |  | 
 |   // Queue the event and wait for it to be acked. | 
 |   InputEventAckWaiter ack_waiter( | 
 |       child_b->current_frame_host()->GetRenderWidgetHost(), | 
 |       blink::WebInputEvent::Type::kGestureDoubleTap); | 
 |   auto* host = static_cast<RenderWidgetHostImpl*>( | 
 |       root->current_frame_host()->GetRenderWidgetHost()); | 
 |   host->QueueSyntheticGesture( | 
 |       std::move(synthetic_gesture_doubletap), | 
 |       base::BindOnce([](SyntheticGesture::Result result) { | 
 |         EXPECT_EQ(SyntheticGesture::GESTURE_FINISHED, result); | 
 |       })); | 
 |   // Waiting for the ack on the child frame ensures the event actually routed | 
 |   // through the oopif. | 
 |   ack_waiter.Wait(); | 
 |  | 
 |   // Wait for page scale to change. We'll assume the OOPIF is scaled up by | 
 |   // at least 10%. | 
 |   float target_scale = 1.1f * original_page_scale; | 
 |   float new_page_scale = original_page_scale; | 
 |   do { | 
 |     observer_a.WaitForAnyFrameSubmission(); | 
 |     new_page_scale = observer_a.LastRenderFrameMetadata().page_scale_factor; | 
 |   } while (new_page_scale < target_scale); | 
 | } | 
 | #endif  // BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID) | 
 |  | 
 | class CrossProcessNavigationObjectElementTest | 
 |     : public SitePerProcessBrowserTestBase, | 
 |       public testing::WithParamInterface< | 
 |           std::tuple<std::string, std::string, std::string>> {}; | 
 |  | 
 | // This test verifies the correctness of rendering fallback in <object> when the | 
 | // a cross-origin navigation leads to a 404 error. Assuming the page's origin | 
 | // is "a.com", the test cases are: | 
 | // 1- Navigating an <object> from "a.com" to invalid "b.com" resource. In this | 
 | //    case the load fails for a provisional frame and at that time there is no | 
 | //    proxy to parent. | 
 | // 2- Navigating an <object> from "b.com" to invalid "b.com". Since navigation | 
 | //    is not cross-origin the failure happens for a non-provisional frame. | 
 | // 3- Navigation an <object> from "b.com" to invalid "c.com". The load fails for | 
 | //    a provisional frame, and at that time there is a proxy to parent. | 
 | IN_PROC_BROWSER_TEST_P(CrossProcessNavigationObjectElementTest, FallbackShown) { | 
 |   const GURL main_url = embedded_test_server()->GetURL( | 
 |       base::StringPrintf("%s.com", std::get<0>(GetParam()).c_str()), | 
 |       "/page_with_object_fallback.html"); | 
 |   const GURL object_valid_url = embedded_test_server()->GetURL( | 
 |       base::StringPrintf("%s.com", std::get<1>(GetParam()).c_str()), | 
 |       "/title1.html"); | 
 |   const GURL object_invalid_url = embedded_test_server()->GetURL( | 
 |       base::StringPrintf("%s.com", std::get<2>(GetParam()).c_str()), | 
 |       "/does-not-exist-throws-404.html"); | 
 |  | 
 |   ASSERT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   // Load the contents of <object> (first navigation which is to a valid | 
 |   // existing resource) and wait for 'load' event on <object>. | 
 |   ASSERT_EQ("OBJECT_LOAD", | 
 |             EvalJs(web_contents(), JsReplace("setUrl($1);", object_valid_url))); | 
 |  | 
 |   // Verify fallback content is not shown. | 
 |   ASSERT_EQ(false, EvalJs(web_contents(), "fallbackVisible()")); | 
 |  | 
 |   // Navigate the <object>'s frame to invalid origin. Make sure we do not report | 
 |   // the 'load' event (the 404 content loads inside the <object>'s frame and the | 
 |   // 'load' event might fire before fallback is detected). | 
 |   ASSERT_EQ(true, EvalJs(web_contents(), JsReplace("setUrl($1);" | 
 |                                                    "notifyWhenFallbackShown();", | 
 |                                                    object_invalid_url))); | 
 | } | 
 |  | 
 | INSTANTIATE_TEST_SUITE_P(SitePerProcess, | 
 |                          CrossProcessNavigationObjectElementTest, | 
 |                          testing::Values(std::make_tuple("a", "a", "b"), | 
 |                                          std::make_tuple("a", "b", "b"), | 
 |                                          std::make_tuple("a", "b", "c"))); | 
 |  | 
 | #if !BUILDFLAG(IS_ANDROID) | 
 | // This test verifies that after occluding a WebContents the RAF inside a | 
 | // cross-process child frame is throttled. | 
 | // Disabled due to flakiness. crbug.com/1293207 | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        DISABLED_OccludedRenderWidgetThrottlesRAF) { | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   FrameTreeNode* subframe = root->child_at(0); | 
 |   GURL page_with_raf_counter = | 
 |       embedded_test_server()->GetURL("a.com", "/page_with_raf_counter.html"); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(subframe, page_with_raf_counter)); | 
 |  | 
 |   // Initially page is visible - wait some time and then ensure a good number of | 
 |   // rafs have been generated. On Mac the number of RAFs that occur in 500ms is | 
 |   // quite low, see https://crbug.com/1098715. | 
 |   auto allow_time_for_rafs = []() { | 
 |     base::RunLoop run_loop; | 
 |     base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( | 
 |         FROM_HERE, run_loop.QuitClosure(), base::Milliseconds(1000)); | 
 |     run_loop.Run(); | 
 |   }; | 
 |  | 
 |   ASSERT_TRUE(ExecJs(subframe, "reset_count();")); | 
 |   allow_time_for_rafs(); | 
 |   int32_t default_raf_count = EvalJs(subframe, "raf_count").ExtractInt(); | 
 |   // On a 60 fps we should expect more than 30 counts - however purely for | 
 |   // sanity checking and avoiding unnecessary flakes adding a comparison for a | 
 |   // much lower value. This verifies that we did get *some* rAFs. | 
 |   EXPECT_GT(default_raf_count, 5); | 
 |   web_contents()->WasOccluded(); | 
 |   ASSERT_TRUE(ExecJs(subframe, "reset_count();")); | 
 |   allow_time_for_rafs(); | 
 |   int32_t raf_count = EvalJs(subframe, "raf_count").ExtractInt(); | 
 |   // If the frame is throttled, we should expect 0 rAFs. | 
 |   EXPECT_EQ(raf_count, 0); | 
 |   // Sanity-check: unoccluding will reverse the effect. | 
 |   web_contents()->WasShown(); | 
 |   ASSERT_TRUE(ExecJs(subframe, "reset_count();")); | 
 |   allow_time_for_rafs(); | 
 |   raf_count = EvalJs(subframe, "raf_count").ExtractInt(); | 
 |   EXPECT_GT(raf_count, 5); | 
 | } | 
 | #endif | 
 |  | 
 | // Test that a renderer locked to origin A will be terminated if it tries to | 
 | // commit a navigation to origin B.  See also https://crbug.com/770239. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        CommittedOriginIncompatibleWithOriginLock) { | 
 |   GURL start_url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), start_url)); | 
 |   FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) | 
 |                             ->GetPrimaryFrameTree() | 
 |                             .root(); | 
 |  | 
 |   GURL another_url(embedded_test_server()->GetURL("a.com", "/title2.html")); | 
 |   const GURL bad_url = GURL("https://b.com"); | 
 |  | 
 |   // Sanity check the process lock logic. | 
 |   auto process_lock = | 
 |       root->current_frame_host()->GetProcess()->GetProcessLock(); | 
 |   IsolationContext isolation_context( | 
 |       shell()->web_contents()->GetBrowserContext()); | 
 |   ProcessLock start_url_lock = ProcessLock::FromSiteInfo( | 
 |       SiteInfo::CreateForTesting(isolation_context, start_url)); | 
 |   ProcessLock another_url_lock = ProcessLock::FromSiteInfo( | 
 |       SiteInfo::CreateForTesting(isolation_context, another_url)); | 
 |   ProcessLock bad_url_lock = ProcessLock::FromSiteInfo( | 
 |       SiteInfo::CreateForTesting(isolation_context, bad_url)); | 
 |   EXPECT_EQ(start_url_lock, process_lock); | 
 |   EXPECT_EQ(another_url_lock, process_lock); | 
 |   EXPECT_NE(bad_url_lock, process_lock); | 
 |  | 
 |   // Leave the commit URL alone, so the URL checks will pass, but change the | 
 |   // origin to one that does not match the origin lock of the process. | 
 |   PwnCommitIPC(shell()->web_contents(), another_url, another_url, | 
 |                url::Origin::Create(bad_url)); | 
 |   EXPECT_TRUE( | 
 |       BeginNavigateToURLFromRenderer(shell()->web_contents(), another_url)); | 
 |  | 
 |   // Due to the origin lock mismatch, the render process should be killed when | 
 |   // it tries to commit. | 
 |   RenderProcessHostBadIpcMessageWaiter kill_waiter( | 
 |       root->current_frame_host()->GetProcess()); | 
 |   EXPECT_EQ(bad_message::RFH_INVALID_ORIGIN_ON_COMMIT, kill_waiter.Wait()); | 
 | } | 
 |  | 
 | // This test verifies that plugin elements containing cross-process-frames do | 
 | // not become unresponsive during style changes. (see https://crbug.com/781880). | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        PluginElementResponsiveInCrossProcessNavigations) { | 
 |   GURL main_frame_url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   ASSERT_TRUE(NavigateToURL(shell(), main_frame_url)); | 
 |   GURL cross_origin(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |   std::string msg = | 
 |       EvalJs(shell(), JsReplace("var object = document.createElement('object');" | 
 |                                 "document.body.appendChild(object);" | 
 |                                 "object.data = $1;" | 
 |                                 "object.type='text/html';" | 
 |                                 "object.notify = true;" | 
 |                                 "new Promise(resolve => {" | 
 |                                 "  object.onload = () => {" | 
 |                                 "    if (!object.notify) return;" | 
 |                                 "    object.notify = false;" | 
 |                                 "    resolve('done');" | 
 |                                 "  };" | 
 |                                 "});", | 
 |                                 cross_origin)) | 
 |           .ExtractString(); | 
 |   ASSERT_EQ("done", msg); | 
 |   // To track the frame's visibility an EmbeddedContentView is needed. The | 
 |   // following steps make sure the visibility is tracked properly on the browser | 
 |   // side. | 
 |   auto* frame_connector = web_contents() | 
 |                               ->GetPrimaryFrameTree() | 
 |                               .root() | 
 |                               ->child_at(0) | 
 |                               ->render_manager() | 
 |                               ->GetProxyToParent() | 
 |                               ->cross_process_frame_connector(); | 
 |   ASSERT_FALSE(frame_connector->IsHidden()); | 
 |   ASSERT_TRUE(ExecJs( | 
 |       shell(), "document.querySelector('object').style.display = 'none';")); | 
 |   EXPECT_TRUE( | 
 |       base::test::RunUntil([&]() { return frame_connector->IsHidden(); })); | 
 |   ASSERT_TRUE(ExecJs( | 
 |       shell(), "document.querySelector('object').style.display = 'block';")); | 
 |   EXPECT_TRUE( | 
 |       base::test::RunUntil([&]() { return !frame_connector->IsHidden(); })); | 
 | } | 
 |  | 
 | // Pending navigations must be canceled when a frame becomes pending deletion. | 
 | // | 
 | // 1) Initial state: A(B). | 
 | // 2) Navigation from B to C. Pause when the speculative RFH is created. | 
 | // 3) Deletion of B. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        NavigationCommitInIframePendingDeletionAB) { | 
 |   GURL url_a(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b)")); | 
 |   GURL url_c(embedded_test_server()->GetURL("c.com", "/title1.html")); | 
 |  | 
 |   // 1) Initial state: A(B). | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImpl* rfh_a = web_contents()->GetPrimaryMainFrame(); | 
 |   RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host(); | 
 |  | 
 |   // RFH B has an unload handler. | 
 |   rfh_b->DoNotDeleteForTesting(); | 
 |   EXPECT_TRUE(ExecJs(rfh_b, "onunload=function(){}")); | 
 |  | 
 |   // 2) Navigation from B to C. The navigation will be paused | 
 |   // when the speculative RFH is created. | 
 |   TestNavigationManager navigation_observer(web_contents(), url_c); | 
 |   EXPECT_TRUE(ExecJs(rfh_b, JsReplace("location.href=$1;", url_c))); | 
 |   navigation_observer.WaitForSpeculativeRenderFrameHostCreation(); | 
 |   RenderFrameHostImpl* rfh_c = | 
 |       rfh_b->frame_tree_node()->render_manager()->speculative_frame_host(); | 
 |  | 
 |   EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kActive, | 
 |             rfh_a->lifecycle_state()); | 
 |   EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kActive, | 
 |             rfh_b->lifecycle_state()); | 
 |   EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kSpeculative, | 
 |             rfh_c->lifecycle_state()); | 
 |  | 
 |   // 3) Deletion of B. The unload handler takes times to execute. | 
 |   RenderFrameDeletedObserver delete_b(rfh_b), delete_c(rfh_c); | 
 |   EXPECT_TRUE( | 
 |       ExecJs(rfh_a, JsReplace("document.querySelector('iframe').remove();"))); | 
 |   EXPECT_FALSE(delete_b.deleted()); | 
 |   EXPECT_TRUE(delete_c.deleted());  // The speculative RFH is deleted. | 
 |   EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kActive, | 
 |             rfh_a->lifecycle_state()); | 
 |   EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kRunningUnloadHandlers, | 
 |             rfh_b->lifecycle_state()); | 
 |  | 
 |   // The navigation has been canceled. | 
 |   ASSERT_TRUE(navigation_observer.WaitForNavigationFinished()); | 
 |   EXPECT_FALSE(navigation_observer.was_successful()); | 
 |  | 
 |   // |rfh_b| will complete its deletion at some point: | 
 |   EXPECT_FALSE(delete_b.deleted()); | 
 |   rfh_b->DetachForTesting(); | 
 |   EXPECT_TRUE(delete_b.deleted()); | 
 | } | 
 |  | 
 | // Pending navigations must be canceled when a frame becomes pending deletion. | 
 | // | 
 | // 1) Initial state: A(B(C)). | 
 | // 2) Navigation from C to D. Pause when the speculative RFH is created. | 
 | // 3) Deletion of B. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        NavigationCommitInIframePendingDeletionABC) { | 
 |   GURL url_a(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b(c))")); | 
 |   GURL url_d(embedded_test_server()->GetURL("d.com", "/title1.html")); | 
 |  | 
 |   // 1) Initial state: A(B(C)). | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImpl* rfh_a = web_contents()->GetPrimaryMainFrame(); | 
 |   RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host(); | 
 |   RenderFrameHostImpl* rfh_c = rfh_b->child_at(0)->current_frame_host(); | 
 |  | 
 |   // Leave rfh_c in pending deletion state. | 
 |   LeaveInPendingDeletionState(rfh_c); | 
 |  | 
 |   // 2) Navigation from C to D. The navigation will be paused | 
 |   // when the speculative RFH is created. | 
 |   TestNavigationManager navigation_observer(web_contents(), url_d); | 
 |   EXPECT_TRUE(ExecJs(rfh_c, JsReplace("location.href=$1;", url_d))); | 
 |   navigation_observer.WaitForSpeculativeRenderFrameHostCreation(); | 
 |   RenderFrameHostImpl* rfh_d = | 
 |       rfh_c->frame_tree_node()->render_manager()->speculative_frame_host(); | 
 |  | 
 |   EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kActive, | 
 |             rfh_a->lifecycle_state()); | 
 |   EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kActive, | 
 |             rfh_b->lifecycle_state()); | 
 |   EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kActive, | 
 |             rfh_c->lifecycle_state()); | 
 |   EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kSpeculative, | 
 |             rfh_d->lifecycle_state()); | 
 |  | 
 |   // 3) Deletion of D. The unload handler takes times to execute. | 
 |   RenderFrameDeletedObserver delete_b(rfh_b), delete_c(rfh_c), delete_d(rfh_d); | 
 |   EXPECT_TRUE( | 
 |       ExecJs(rfh_a, JsReplace("document.querySelector('iframe').remove();"))); | 
 |   EXPECT_FALSE(delete_b.deleted()); | 
 |   EXPECT_FALSE(delete_c.deleted()); | 
 |   EXPECT_TRUE(delete_d.deleted());  // The speculative RFH is deleted. | 
 |   EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kActive, | 
 |             rfh_a->lifecycle_state()); | 
 |   EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kReadyToBeDeleted, | 
 |             rfh_b->lifecycle_state()); | 
 |   EXPECT_EQ(RenderFrameHost::LifecycleState::kPendingDeletion, | 
 |             rfh_b->GetLifecycleState()); | 
 |   EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kRunningUnloadHandlers, | 
 |             rfh_c->lifecycle_state()); | 
 |   EXPECT_EQ(RenderFrameHost::LifecycleState::kPendingDeletion, | 
 |             rfh_c->GetLifecycleState()); | 
 |  | 
 |   // The navigation has been canceled. | 
 |   ASSERT_TRUE(navigation_observer.WaitForNavigationFinished()); | 
 |   EXPECT_FALSE(navigation_observer.was_successful()); | 
 |  | 
 |   // |rfh_b| and |rfh_c| will complete their deletion at some point: | 
 |   EXPECT_FALSE(delete_b.deleted()); | 
 |   EXPECT_FALSE(delete_c.deleted()); | 
 |   rfh_c->DetachForTesting(); | 
 |   EXPECT_TRUE(delete_b.deleted()); | 
 |   EXPECT_TRUE(delete_c.deleted()); | 
 | } | 
 |  | 
 | // A same document commit from the renderer process is received while the | 
 | // RenderFrameHost is pending deletion. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        SameDocumentCommitWhilePendingDeletion) { | 
 |   GURL url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url)); | 
 |   RenderFrameHostImpl* rfh_a = web_contents()->GetPrimaryMainFrame(); | 
 |   RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host(); | 
 |  | 
 |   // Frame B has a unload handler. The browser process needs to wait before | 
 |   // deleting it. | 
 |   EXPECT_TRUE(ExecJs(rfh_b, "onunload=function(){}")); | 
 |  | 
 |   RenderFrameDeletedObserver deleted_observer(rfh_b); | 
 |   DidStartNavigationObserver did_start_navigation_observer(web_contents()); | 
 |  | 
 |   // Start a same-document navigation on B. | 
 |   ExecuteScriptAsync(rfh_b, "location.href='#fragment'"); | 
 |  | 
 |   // Simulate A deleting B. | 
 |   // It starts before receiving the same-document navigation. The detach ACK is | 
 |   // received after. | 
 |   rfh_b->DetachFromProxy(); | 
 |   deleted_observer.WaitUntilDeleted(); | 
 |  | 
 |   // The navigation was ignored. | 
 |   EXPECT_FALSE(did_start_navigation_observer.observed()); | 
 | } | 
 |  | 
 | // An history navigation from the renderer process is received while the | 
 | // RenderFrameHost is pending deletion. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        HistoryNavigationWhilePendingDeletion) { | 
 |   GURL url_ab(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b)")); | 
 |   GURL url_c(embedded_test_server()->GetURL("c.com", "/title1.html")); | 
 |  | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_ab)); | 
 |   RenderFrameHostImpl* rfh_a = web_contents()->GetPrimaryMainFrame(); | 
 |   RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host(); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(rfh_b->frame_tree_node(), url_c)); | 
 |   RenderFrameHostImpl* rfh_c = rfh_a->child_at(0)->current_frame_host(); | 
 |  | 
 |   // Set a value in rfh_a that we'll check later to ensure we didn't | 
 |   // incorrectly reload it. | 
 |   EXPECT_TRUE(ExecJs(rfh_a, "window.foo='bar';")); | 
 |  | 
 |   // Frame C has a unload handler. The browser process needs to wait before | 
 |   // deleting it. | 
 |   EXPECT_TRUE(ExecJs(rfh_c, "onunload=function(){}")); | 
 |  | 
 |   RenderFrameDeletedObserver deleted_observer(rfh_c); | 
 |  | 
 |   // History navigation on C. | 
 |   ExecuteScriptAsync(rfh_c, "history.back();"); | 
 |  | 
 |   // Simulate A deleting C. | 
 |   // It starts before receiving the history navigation. The detach ACK is | 
 |   // received after. | 
 |   rfh_c->DetachFromProxy(); | 
 |   deleted_observer.WaitUntilDeleted(); | 
 |  | 
 |   // The NavigationController won't be able to find the subframe to navigate | 
 |   // since it was just detached, so it should cancel the history navigation and | 
 |   // not reload the main page.  Verify this by waiting for any pending | 
 |   // navigation (there shouldn't be any) and checking that JavaScript state in | 
 |   // rfh_a hasn't changed.  Note that because we've waited for rfh_c to be | 
 |   // deleted, we know that the browser process has already received an ack for | 
 |   // completion of its unload handler, and thus it has also processed the | 
 |   // preceding history.back() IPC. | 
 |   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); | 
 |   EXPECT_EQ("bar", EvalJs(rfh_a, "window.foo")); | 
 | } | 
 |  | 
 | // One frame navigates using window.open while it is pending deletion. The two | 
 | // frames lives in different processes. | 
 | // See https://crbug.com/932087. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        OpenUrlToRemoteFramePendingDeletion) { | 
 |   GURL url_ab(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b)")); | 
 |   GURL url_c(embedded_test_server()->GetURL("c.com", "/title1.html")); | 
 |  | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_ab)); | 
 |   RenderFrameHostImpl* rfh_a = web_contents()->GetPrimaryMainFrame(); | 
 |   RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host(); | 
 |  | 
 |   // Frame B has a unload handler. The browser process needs to wait before | 
 |   // deleting it. | 
 |   EXPECT_TRUE(ExecJs(rfh_b, "onunload=function(){}")); | 
 |   RenderFrameDeletedObserver deleted_observer(rfh_b); | 
 |  | 
 |   // window.open from A in B to url_c. | 
 |   DidStartNavigationObserver did_start_navigation_observer(web_contents()); | 
 |   EXPECT_TRUE(ExecJs(rfh_b, "window.name = 'name';")); | 
 |   ExecuteScriptAsync(rfh_a, JsReplace("window.open($1, 'name');", url_c)); | 
 |  | 
 |   // Simulate A deleting C. | 
 |   // It starts before receiving the navigation. The detach ACK is | 
 |   // received after. | 
 |   rfh_b->DetachFromProxy(); | 
 |   deleted_observer.WaitUntilDeleted(); | 
 |  | 
 |   EXPECT_FALSE(did_start_navigation_observer.observed()); | 
 | } | 
 |  | 
 | // Check that if a frame starts a navigation, and the frame's current process | 
 | // dies before the response for the navigation comes back, the response will | 
 | // not trigger a process kill and will be allowed to commit in a new process. | 
 | // See https://crbug.com/968259. | 
 | // Note: This test needs to do a browser-initiated navigation because doing | 
 | // a renderer-initiated navigation would lead to the navigation being canceled. | 
 | // This behavior change has been introduced when navigation moved to use Mojo | 
 | // IPCs and is documented here https://crbug.com/988368. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        ProcessDiesBeforeCrossSiteNavigationCompletes) { | 
 |   GURL first_url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), first_url)); | 
 |   scoped_refptr<SiteInstanceImpl> first_site_instance( | 
 |       web_contents()->GetPrimaryMainFrame()->GetSiteInstance()); | 
 |  | 
 |   // Start a cross-site navigation and proceed only up to the request start. | 
 |   GURL second_url(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |   TestNavigationManager delayer(web_contents(), second_url); | 
 |   web_contents()->GetController().LoadURL( | 
 |       second_url, Referrer(), ui::PageTransition::PAGE_TRANSITION_TYPED, | 
 |       std::string()); | 
 |   EXPECT_TRUE(delayer.WaitForRequestStart()); | 
 |  | 
 |   // Terminate the current a.com process. | 
 |   RenderProcessHost* first_process = | 
 |       web_contents()->GetPrimaryMainFrame()->GetProcess(); | 
 |   RenderProcessHostWatcher crash_observer( | 
 |       first_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); | 
 |   EXPECT_TRUE(first_process->Shutdown(0)); | 
 |   crash_observer.Wait(); | 
 |   EXPECT_FALSE(web_contents()->GetPrimaryMainFrame()->IsRenderFrameLive()); | 
 |  | 
 |   // Resume the cross-site navigation and ensure it commits in a new | 
 |   // SiteInstance and process. | 
 |   ASSERT_TRUE(delayer.WaitForNavigationFinished()); | 
 |   EXPECT_TRUE(web_contents()->GetPrimaryMainFrame()->IsRenderFrameLive()); | 
 |   EXPECT_NE(web_contents()->GetPrimaryMainFrame()->GetProcess(), first_process); | 
 |   EXPECT_NE(web_contents()->GetPrimaryMainFrame()->GetSiteInstance(), | 
 |             first_site_instance); | 
 |   EXPECT_EQ(second_url, | 
 |             web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL()); | 
 | } | 
 |  | 
 | enum class InnerWebContentsAttachChildFrameOriginType { | 
 |   kSameOriginAboutBlank, | 
 |   kSameOriginOther, | 
 |   kCrossOrigin | 
 | }; | 
 |  | 
 | class InnerWebContentsAttachTest | 
 |     : public SitePerProcessBrowserTestBase, | 
 |       public testing::WithParamInterface< | 
 |           std::tuple<InnerWebContentsAttachChildFrameOriginType, | 
 |                      bool /* original frame has beforeunload handlers */, | 
 |                      bool /* user proceeds with attaching */>> { | 
 |  public: | 
 |   InnerWebContentsAttachTest() {} | 
 |  | 
 |   InnerWebContentsAttachTest(const InnerWebContentsAttachTest&) = delete; | 
 |   InnerWebContentsAttachTest& operator=(const InnerWebContentsAttachTest&) = | 
 |       delete; | 
 |  | 
 |   ~InnerWebContentsAttachTest() override {} | 
 |  | 
 |  protected: | 
 |   // Helper class to initiate and conclude a frame preparation process for | 
 |   // attaching an inner WebContents. | 
 |   class PrepareFrameJob { | 
 |    public: | 
 |     PrepareFrameJob(RenderFrameHostImpl* original_render_frame_host, | 
 |                     bool proceed_through_beforeunload) { | 
 |       auto* web_contents = | 
 |           WebContents::FromRenderFrameHost(original_render_frame_host); | 
 |       // Need user gesture for 'beforeunload' to fire. | 
 |       PrepContentsForBeforeUnloadTest(web_contents); | 
 |       // Simulate user choosing to stay on the page after beforeunload fired. | 
 |       SetShouldProceedOnBeforeUnload(Shell::FromWebContents(web_contents), | 
 |                                      true /* always_proceed */, | 
 |                                      proceed_through_beforeunload); | 
 |       RenderFrameHost::PrepareForInnerWebContentsAttachCallback callback = | 
 |           base::BindOnce(&PrepareFrameJob::OnPrepare, base::Unretained(this)); | 
 |       original_render_frame_host->PrepareForInnerWebContentsAttach( | 
 |           std::move(callback)); | 
 |     } | 
 |  | 
 |     PrepareFrameJob(const PrepareFrameJob&) = delete; | 
 |     PrepareFrameJob& operator=(const PrepareFrameJob&) = delete; | 
 |  | 
 |     virtual ~PrepareFrameJob() {} | 
 |  | 
 |     void WaitForPreparedFrame() { | 
 |       if (did_call_prepare_) | 
 |         return; | 
 |       run_loop_.Run(); | 
 |     } | 
 |  | 
 |     RenderFrameHostImpl* prepared_frame() const { | 
 |       return new_render_frame_host_; | 
 |     } | 
 |  | 
 |    private: | 
 |     void OnPrepare(RenderFrameHost* render_frame_host) { | 
 |       did_call_prepare_ = true; | 
 |       new_render_frame_host_ = | 
 |           static_cast<RenderFrameHostImpl*>(render_frame_host); | 
 |       if (run_loop_.running()) | 
 |         run_loop_.Quit(); | 
 |     } | 
 |  | 
 |     bool did_call_prepare_ = false; | 
 |     raw_ptr<RenderFrameHostImpl> new_render_frame_host_ = nullptr; | 
 |     base::RunLoop run_loop_; | 
 |   }; | 
 | }; | 
 |  | 
 | // This is a test for the FrameTreeNode preparation process for various types | 
 | // of outer WebContents RenderFrameHosts; essentially when connecting two | 
 | // WebContents through a frame in a WebPage it is possible that the frame itself | 
 | // has a nontrivial document (other than about:blank) with a beforeunload | 
 | // handler, or even it is a cross-process frame. For such cases the frame first | 
 | // needs to be sanitized to be later consumed by the WebContents attaching API. | 
 | IN_PROC_BROWSER_TEST_P(InnerWebContentsAttachTest, PrepareFrame) { | 
 |   ASSERT_TRUE( | 
 |       NavigateToURL(shell(), embedded_test_server()->GetURL( | 
 |                                  "a.com", "/page_with_object_fallback.html"))); | 
 |   InnerWebContentsAttachChildFrameOriginType child_frame_origin_type = | 
 |       std::get<0>(GetParam()); | 
 |   bool test_beforeunload = std::get<1>(GetParam()); | 
 |   bool proceed_through_beforeunload = std::get<2>(GetParam()); | 
 |   GURL child_frame_url = | 
 |       child_frame_origin_type == | 
 |               InnerWebContentsAttachChildFrameOriginType::kSameOriginAboutBlank | 
 |           ? GURL(url::kAboutBlankURL) | 
 |           : child_frame_origin_type == | 
 |                     InnerWebContentsAttachChildFrameOriginType::kSameOriginOther | 
 |                 ? embedded_test_server()->GetURL("a.com", "/title1.html") | 
 |                 : embedded_test_server()->GetURL("b.com", "/title1.html"); | 
 |   SCOPED_TRACE(testing::Message() | 
 |                << " Child frame URL:" << child_frame_url.spec() | 
 |                << " 'beforeunload' modal shown: " << test_beforeunload | 
 |                << " proceed through'beforeunload':  " | 
 |                << proceed_through_beforeunload); | 
 |   auto* child_node = web_contents()->GetPrimaryFrameTree().root()->child_at(0); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(child_node, child_frame_url)); | 
 |   if (test_beforeunload) { | 
 |     EXPECT_TRUE(ExecJs(child_node, | 
 |                        "window.addEventListener('beforeunload', (e) => {" | 
 |                        "e.preventDefault(); return e; });")); | 
 |   } | 
 |   auto* original_child_frame = child_node->current_frame_host(); | 
 |   RenderFrameDeletedObserver original_child_frame_observer( | 
 |       original_child_frame); | 
 |   AppModalDialogWaiter dialog_waiter(shell()); | 
 |   PrepareFrameJob prepare_job(original_child_frame, | 
 |                               proceed_through_beforeunload); | 
 |   if (test_beforeunload) | 
 |     dialog_waiter.Wait(); | 
 |   prepare_job.WaitForPreparedFrame(); | 
 |   auto* new_render_frame_host = prepare_job.prepared_frame(); | 
 |   bool did_prepare_frame = new_render_frame_host; | 
 |   bool same_frame_used = (new_render_frame_host == original_child_frame); | 
 |   // If a frame was not prepared, then it has to be due to beforeunload being | 
 |   // dismissed. | 
 |   ASSERT_TRUE(did_prepare_frame || | 
 |               (test_beforeunload && !proceed_through_beforeunload)); | 
 |   // If the original frame is in the same SiteInstance as its parent, then it | 
 |   // can be reused; otherwise a new frame is expected here. | 
 |   bool is_same_origin = | 
 |       child_frame_origin_type != | 
 |       InnerWebContentsAttachChildFrameOriginType::kCrossOrigin; | 
 |   if (!is_same_origin && did_prepare_frame) { | 
 |     // For the cross-origin case we expect the original RenderFrameHost to go | 
 |     // away during preparation. | 
 |     original_child_frame_observer.WaitUntilDeleted(); | 
 |   } | 
 |   ASSERT_TRUE(!did_prepare_frame || (is_same_origin == same_frame_used)); | 
 |   ASSERT_TRUE(!did_prepare_frame || | 
 |               (original_child_frame_observer.deleted() != is_same_origin)); | 
 |   // Finally, try the WebContents attach API and make sure we are doing OK. | 
 |   if (new_render_frame_host) | 
 |     CreateAndAttachInnerContents(new_render_frame_host); | 
 | } | 
 |  | 
 | INSTANTIATE_TEST_SUITE_P( | 
 |     SitePerProcess, | 
 |     InnerWebContentsAttachTest, | 
 |     testing::Combine( | 
 |         testing::ValuesIn( | 
 |             {InnerWebContentsAttachChildFrameOriginType::kSameOriginAboutBlank, | 
 |              InnerWebContentsAttachChildFrameOriginType::kSameOriginOther, | 
 |              InnerWebContentsAttachChildFrameOriginType::kCrossOrigin}), | 
 |         testing::Bool(), | 
 |         testing::Bool())); | 
 |  | 
 | // This checks what process is used when an iframe is navigated to about:blank. | 
 | // The new document should be loaded in the process of its initiator. | 
 | // | 
 | // Test case: | 
 | // 1. Navigate to A1(B2). | 
 | // 2. B2 navigates itself to B3 = about:blank. Process B is used. | 
 | // 3. A1 makes B3 to navigate to A4 = about:blank. Process A is used. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        SameAndCrossProcessIframeAboutBlankNavigation) { | 
 |   // 1. Navigate to A1(B2). | 
 |   GURL a1_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), a1_url)); | 
 |   RenderFrameHostImpl* a1_rfh = web_contents()->GetPrimaryMainFrame(); | 
 |   RenderFrameHostImpl* b2_rfh = a1_rfh->child_at(0)->current_frame_host(); | 
 |  | 
 |   // 2. B2 navigates itself to B3 = about:blank. Process B is used. | 
 |   { | 
 |     scoped_refptr<SiteInstance> b2_site_instance = b2_rfh->GetSiteInstance(); | 
 |     TestNavigationManager navigation_manager(web_contents(), | 
 |                                              GURL("about:blank")); | 
 |     EXPECT_TRUE(ExecJs(b2_rfh, "location.href = 'about:blank';")); | 
 |     ASSERT_TRUE(navigation_manager.WaitForNavigationFinished()); | 
 |  | 
 |     RenderFrameHostImpl* b3_rfh = a1_rfh->child_at(0)->current_frame_host(); | 
 |     DCHECK_EQ(b3_rfh->GetSiteInstance(), b2_site_instance); | 
 |     DCHECK_NE(a1_rfh->GetProcess(), b3_rfh->GetProcess()); | 
 |   } | 
 |  | 
 |   // 3. A1 makes B3 to navigate to A4 = about:blank. Process A is used. | 
 |   { | 
 |     TestNavigationManager navigation_manager(web_contents(), | 
 |                                              GURL("about:blank")); | 
 |     EXPECT_TRUE(ExecJs(a1_rfh, R"( | 
 |       document.querySelector("iframe").src = "about:blank"; | 
 |     )")); | 
 |     ASSERT_TRUE(navigation_manager.WaitForNavigationFinished()); | 
 |  | 
 |     RenderFrameHostImpl* b4_rfh = a1_rfh->child_at(0)->current_frame_host(); | 
 |     DCHECK_EQ(a1_rfh->GetSiteInstance(), b4_rfh->GetSiteInstance()); | 
 |   } | 
 | } | 
 |  | 
 | // TODO(crbug.com/425866013): Fix and re-enable flaky test. | 
 | #if BUILDFLAG(IS_FUCHSIA) | 
 | #define MAYBE_AccessWindowProxyOfCrashedFrameAfterNavigation \ | 
 |   DISABLED_AccessWindowProxyOfCrashedFrameAfterNavigation | 
 | #else | 
 | #define MAYBE_AccessWindowProxyOfCrashedFrameAfterNavigation \ | 
 |   AccessWindowProxyOfCrashedFrameAfterNavigation | 
 | #endif | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        MAYBE_AccessWindowProxyOfCrashedFrameAfterNavigation) { | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("a.com", "/title1.html"))); | 
 |   const GURL cross_site_url = | 
 |       embedded_test_server()->GetURL("b.com", "/title1.html"); | 
 |   TestNavigationObserver observer(cross_site_url); | 
 |   observer.StartWatchingNewWebContents(); | 
 |   EXPECT_TRUE(ExecJs( | 
 |       shell(), JsReplace("openedWindow = window.open($1)", cross_site_url))); | 
 |   observer.WaitForNavigationFinished(); | 
 |  | 
 |   EXPECT_EQ(2u, Shell::windows().size()); | 
 |   CrashTab(Shell::windows()[1]->web_contents()); | 
 |  | 
 |   // When starting a navigation in a crashed frame, the navigation code | 
 |   // immediately swaps in the speculative RFH. | 
 |   EXPECT_TRUE( | 
 |       ExecJs(shell(), "openedWindow.location = 'data:text/html,content'")); | 
 |   // The early-swapped frame should not be scriptable from another frame--nor | 
 |   // should trying to script it result in a crash. | 
 |   std::string result = | 
 |       EvalJs(shell(), | 
 |              "try { openedWindow.document } catch (e) { e.toString(); }") | 
 |           .ExtractString(); | 
 |   EXPECT_THAT( | 
 |       result, | 
 |       ::testing::MatchesRegex( | 
 |           "SecurityError: Failed to read a named property 'document' from " | 
 |           "'Window': Blocked a frame with origin \"http://a.com:\\d+\" " | 
 |           "from accessing a cross-origin frame.")); | 
 | } | 
 |  | 
 | // Make sure that a popup with a cross site subframe can be closed from the | 
 | // subframe. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, CloseNoopenerWindow) { | 
 |   GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |  | 
 |   // Open a same site popup with a subframe using the noopener ref. | 
 |   GURL popup_url( | 
 |       embedded_test_server()->GetURL("a.com", "/page_with_blank_iframe.html")); | 
 |   ShellAddedObserver new_shell_observer; | 
 |   EXPECT_TRUE(ExecJs( | 
 |       shell(), | 
 |       JsReplace("popup = window.open($1,'_blank','noopener');", popup_url))); | 
 |   Shell* popup = new_shell_observer.GetShell(); | 
 |   WebContentsImpl* popup_web_contents = | 
 |       static_cast<WebContentsImpl*>(popup->web_contents()); | 
 |   FrameTreeNode* popup_root = popup_web_contents->GetPrimaryFrameTree().root(); | 
 |   EXPECT_TRUE(WaitForLoadStop(popup_web_contents)); | 
 |  | 
 |   // Navigate the popup subframe cross site to b.com. | 
 |   FrameTreeNode* child = popup_root->child_at(0); | 
 |   GURL cross_origin_url( | 
 |       embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(child, cross_origin_url)); | 
 |  | 
 |   // Check that the popup successfully closes from the subframe. | 
 |   WebContentsDestroyedWatcher destroyed_watcher(popup->web_contents()); | 
 |   EXPECT_TRUE(ExecJs(child, "window.parent.close()")); | 
 |   destroyed_watcher.Wait(); | 
 | } | 
 |  | 
 | // Check that initial navigations to renderer debug URLs mark the renderer | 
 | // process as used, so that future navigations to sites that require a | 
 | // dedicated process do not reuse that process. | 
 | IN_PROC_BROWSER_TEST_P( | 
 |     SitePerProcessBrowserTest, | 
 |     ProcessNotReusedAfterInitialNavigationToRendererDebugURL) { | 
 |   // Load a javascript URL, which is a renderer debug URL.  This navigation | 
 |   // won't commit, but the renderer process will synchronously process the | 
 |   // javascript URL and install an HTML document that contains "foo". | 
 |   GURL javascript_url("javascript:'foo'"); | 
 |   shell()->LoadURL(javascript_url); | 
 |   EXPECT_EQ("foo", EvalJs(shell(), "document.body.innerText")); | 
 |  | 
 |   RenderProcessHost* js_process = | 
 |       web_contents()->GetPrimaryMainFrame()->GetProcess(); | 
 |  | 
 |   // Because the javascript URL can run arbitrary scripts in the renderer | 
 |   // process, it is unsafe to reuse the renderer process later for navigations | 
 |   // to sites that require a dedicated process.  Ensure that this is the case. | 
 |   EXPECT_FALSE(js_process->IsUnused()); | 
 |  | 
 |   EXPECT_TRUE(NavigateToURL( | 
 |       shell(), embedded_test_server()->GetURL("a.com", "/title1.html"))); | 
 |   EXPECT_NE(js_process, web_contents()->GetPrimaryMainFrame()->GetProcess()); | 
 | } | 
 |  | 
 | namespace { | 
 |  | 
 | void CheckStickyUserActivationState(RenderFrameHostImpl* host, bool expected) { | 
 |   EXPECT_EQ(expected, host->HasStickyUserActivation()); | 
 |   EXPECT_EQ(expected, EvalJs(host, "navigator.userActivation.hasBeenActive", | 
 |                              EXECUTE_SCRIPT_NO_USER_GESTURE)); | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | // Test that a cross-site navigation in an iframe clears user activation. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        UserActivationAfterCrossSiteNavInIframe) { | 
 |   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 = web_contents()->GetPrimaryFrameTree().root(); | 
 |   FrameTreeNode* child = root->child_at(0); | 
 |  | 
 |   // Sanity check that there is no sticky user activation at first. | 
 |   CheckStickyUserActivationState(child->current_frame_host(), false); | 
 |  | 
 |   // Load cross-site page into iframe and verify there is still no sticky user | 
 |   // activation. | 
 |   GURL first_http_url(embedded_test_server()->GetURL("d.com", "/title1.html")); | 
 |   EXPECT_TRUE( | 
 |       NavigateToURLFromRendererWithoutUserGesture(child, first_http_url)); | 
 |   CheckStickyUserActivationState(child->current_frame_host(), false); | 
 |  | 
 |   // Give the child iframe user activation. | 
 |   EXPECT_TRUE(ExecJs(child, "// No-op script")); | 
 |   CheckStickyUserActivationState(child->current_frame_host(), true); | 
 |  | 
 |   // Perform another cross-site navigation in the iframe. | 
 |   GURL http_url(embedded_test_server()->GetURL("c.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURLFromRendererWithoutUserGesture(child, http_url)); | 
 |  | 
 |   // The cross-site navigation should have cleared the user activation. | 
 |   CheckStickyUserActivationState(child->current_frame_host(), false); | 
 |  | 
 |   // Ensure that a top-level navigation from the iframe cannot happen. | 
 |   EXPECT_TRUE(ExecJs(child->current_frame_host(), | 
 |                      JsReplace("window.open($1, $2)", http_url, "_top"), | 
 |                      EXECUTE_SCRIPT_NO_USER_GESTURE)); | 
 |   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); | 
 |   EXPECT_NE(http_url, shell()->web_contents()->GetLastCommittedURL()); | 
 | } | 
 |  | 
 | // Test that a same-site cross-origin navigation in an iframe keeps user | 
 | // activation. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        UserActivationAfterSameSiteNavInIframe) { | 
 |   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 = web_contents()->GetPrimaryFrameTree().root(); | 
 |   FrameTreeNode* child = root->child_at(0); | 
 |  | 
 |   // Sanity check that there is no sticky user activation at first. | 
 |   CheckStickyUserActivationState(child->current_frame_host(), false); | 
 |  | 
 |   // Load cross-origin same-site page into iframe and verify there is still no | 
 |   // sticky user activation. | 
 |   GURL first_http_url( | 
 |       embedded_test_server()->GetURL("subdomain.b.com", "/title1.html")); | 
 |   EXPECT_TRUE( | 
 |       NavigateToURLFromRendererWithoutUserGesture(child, first_http_url)); | 
 |   CheckStickyUserActivationState(child->current_frame_host(), false); | 
 |  | 
 |   // Give the child iframe user activation. | 
 |   EXPECT_TRUE(ExecJs(child, "// No-op script")); | 
 |   CheckStickyUserActivationState(child->current_frame_host(), true); | 
 |  | 
 |   // Perform another same-site navigation in the iframe. | 
 |   GURL http_url(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURLFromRendererWithoutUserGesture(child, http_url)); | 
 |  | 
 |   // The cross-origin same-site navigation should keep the sticky user | 
 |   // activation from the previous page. | 
 |   CheckStickyUserActivationState(child->current_frame_host(), true); | 
 |  | 
 |   // Ensure that a top-level navigation from the iframe can still happen. | 
 |   EXPECT_TRUE(ExecJs(child->current_frame_host(), | 
 |                      JsReplace("window.open($1, $2)", http_url, "_top"), | 
 |                      EXECUTE_SCRIPT_NO_USER_GESTURE)); | 
 |   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); | 
 |   EXPECT_EQ(http_url, shell()->web_contents()->GetLastCommittedURL()); | 
 | } | 
 |  | 
 | // Test that a same-origin navigation in an iframe keeps user activation. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        UserActivationAfterSameOriginNavInIframe) { | 
 |   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 = web_contents()->GetPrimaryFrameTree().root(); | 
 |   FrameTreeNode* child = root->child_at(0); | 
 |  | 
 |   // Sanity check that there is no sticky user activation at first. | 
 |   CheckStickyUserActivationState(child->current_frame_host(), false); | 
 |  | 
 |   // Load cross-site page into iframe and verify there is still no sticky user | 
 |   // activation. | 
 |   GURL first_http_url(embedded_test_server()->GetURL("c.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateIframeToURL(web_contents(), "child-0", first_http_url)); | 
 |   CheckStickyUserActivationState(child->current_frame_host(), false); | 
 |  | 
 |   // Give the child iframe user activation. | 
 |   EXPECT_TRUE(ExecJs(child, "// No-op script")); | 
 |   CheckStickyUserActivationState(child->current_frame_host(), true); | 
 |  | 
 |   // Load same-origin page into iframe. | 
 |   GURL http_url(embedded_test_server()->GetURL("c.com", "/title2.html")); | 
 |   EXPECT_TRUE(NavigateIframeToURL(web_contents(), "child-0", http_url)); | 
 |  | 
 |   // The same-origin navigation should keep the sticky user activation from the | 
 |   // previous page. | 
 |   CheckStickyUserActivationState(child->current_frame_host(), true); | 
 |  | 
 |   // Ensure that a top-level navigation from the iframe can still happen. | 
 |   EXPECT_TRUE(ExecJs(child->current_frame_host(), | 
 |                      JsReplace("window.open($1, $2)", http_url, "_top"), | 
 |                      EXECUTE_SCRIPT_NO_USER_GESTURE)); | 
 |   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); | 
 |   EXPECT_EQ(http_url, shell()->web_contents()->GetLastCommittedURL()); | 
 | } | 
 |  | 
 | class StickyActivationAcrossSameOriginNavBrowserTest | 
 |     : public SitePerProcessBrowserTest { | 
 |  public: | 
 |   StickyActivationAcrossSameOriginNavBrowserTest() { | 
 |     scoped_feature_list_.InitAndEnableFeature( | 
 |         blink::features::kStickyUserActivationAcrossSameOriginNavigation); | 
 |   } | 
 |  | 
 |  private: | 
 |   base::test::ScopedFeatureList scoped_feature_list_; | 
 | }; | 
 |  | 
 | // Test that a cross-site navigation in the top frame clears user activation. | 
 | IN_PROC_BROWSER_TEST_P(StickyActivationAcrossSameOriginNavBrowserTest, | 
 |                        UserActivationAfterCrossSiteNavInTopFrame) { | 
 |   GURL starting_url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), starting_url)); | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // Sanity check that there is no sticky user activation at first. | 
 |   CheckStickyUserActivationState(root->current_frame_host(), false); | 
 |  | 
 |   // Perform a cross-site navigation and verify there is still no sticky user | 
 |   // activation. | 
 |   GURL first_nav_url(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |   EXPECT_TRUE( | 
 |       NavigateToURLFromRendererWithoutUserGesture(shell(), first_nav_url)); | 
 |   CheckStickyUserActivationState(root->current_frame_host(), false); | 
 |  | 
 |   // Give the frame user activation. | 
 |   EXPECT_TRUE(ExecJs(root, "// No-op script")); | 
 |   CheckStickyUserActivationState(root->current_frame_host(), true); | 
 |  | 
 |   // Perform another cross-site navigation. | 
 |   GURL second_nav_url(embedded_test_server()->GetURL( | 
 |       "c.com", "/cross_site_iframe_factory.html?c(c)")); | 
 |   EXPECT_TRUE( | 
 |       NavigateToURLFromRendererWithoutUserGesture(shell(), second_nav_url)); | 
 |  | 
 |   // The navigation should have cleared the user activation. | 
 |   CheckStickyUserActivationState(root->current_frame_host(), false); | 
 |   CheckStickyUserActivationState(root->child_at(0)->current_frame_host(), | 
 |                                  false); | 
 | } | 
 |  | 
 | // Test that a same-site cross-origin navigation in the top frame clears user | 
 | // activation. | 
 | IN_PROC_BROWSER_TEST_P(StickyActivationAcrossSameOriginNavBrowserTest, | 
 |                        UserActivationAfterSameSiteNavInTopFrame) { | 
 |   GURL starting_url( | 
 |       embedded_test_server()->GetURL("sub1.a.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), starting_url)); | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // Sanity check that there is no sticky user activation at first. | 
 |   CheckStickyUserActivationState(root->current_frame_host(), false); | 
 |  | 
 |   // Perform a same-site cross-origin navigation and verify there is still no | 
 |   // sticky user activation. | 
 |   GURL first_nav_url( | 
 |       embedded_test_server()->GetURL("sub2.a.com", "/title1.html")); | 
 |   EXPECT_TRUE( | 
 |       NavigateToURLFromRendererWithoutUserGesture(shell(), first_nav_url)); | 
 |   CheckStickyUserActivationState(root->current_frame_host(), false); | 
 |  | 
 |   // Give the frame user activation. | 
 |   EXPECT_TRUE(ExecJs(root, "// No-op script")); | 
 |   CheckStickyUserActivationState(root->current_frame_host(), true); | 
 |  | 
 |   // Perform another same-site cross-origin navigation in the iframe. | 
 |   GURL second_nav_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(a)")); | 
 |   EXPECT_TRUE( | 
 |       NavigateToURLFromRendererWithoutUserGesture(shell(), second_nav_url)); | 
 |  | 
 |   // The navigation should have cleared the user activation. | 
 |   CheckStickyUserActivationState(root->current_frame_host(), false); | 
 |   CheckStickyUserActivationState(root->child_at(0)->current_frame_host(), | 
 |                                  false); | 
 | } | 
 |  | 
 | // Test that a same-origin navigation in the top frame keeps user activation. | 
 | IN_PROC_BROWSER_TEST_P(StickyActivationAcrossSameOriginNavBrowserTest, | 
 |                        UserActivationAfterSameOriginNavInTopFrame) { | 
 |   GURL starting_url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), starting_url)); | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |  | 
 |   // Sanity check that there is no sticky user activation at first. | 
 |   CheckStickyUserActivationState(root->current_frame_host(), false); | 
 |  | 
 |   // Perform a same-origin navigation and verify there is still no sticky user | 
 |   // activation. | 
 |   GURL first_nav_url(embedded_test_server()->GetURL("a.com", "/title2.html")); | 
 |   EXPECT_TRUE( | 
 |       NavigateToURLFromRendererWithoutUserGesture(shell(), first_nav_url)); | 
 |   CheckStickyUserActivationState(root->current_frame_host(), false); | 
 |  | 
 |   // Give the frame user activation. | 
 |   EXPECT_TRUE(ExecJs(root, "// No-op script")); | 
 |   CheckStickyUserActivationState(root->current_frame_host(), true); | 
 |  | 
 |   // Perform another same-origin navigation in the iframe. | 
 |   GURL second_nav_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(a)")); | 
 |   EXPECT_TRUE( | 
 |       NavigateToURLFromRendererWithoutUserGesture(shell(), second_nav_url)); | 
 |  | 
 |   // The navigation should keep the user activation at the top frame only. | 
 |   CheckStickyUserActivationState(root->current_frame_host(), true); | 
 |   CheckStickyUserActivationState(root->child_at(0)->current_frame_host(), | 
 |                                  false); | 
 | } | 
 |  | 
 | // Test which captures behavior of navigation to about:blank in a newly created | 
 | // WebContents when an initial SiteInstance is supplied as part of the creation. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, | 
 |                        AboutBlankInNewWindowWithInitialSiteInstance) { | 
 |   // Start by navigating to a page on a normal web site. | 
 |   EXPECT_TRUE( | 
 |       NavigateToURL(shell(), embedded_test_server()->GetURL("/empty.html"))); | 
 |  | 
 |   // Now do a browser-initiated navigation to about:blank in a new tab created | 
 |   // in the previous SiteInstance. The current behavior is for the navigation | 
 |   // to switch to a new SiteInstance, though there is no real requirement for | 
 |   // that. In the past the existing SiteInstance was used. | 
 |   WebContents::CreateParams new_contents_params( | 
 |       web_contents()->GetBrowserContext(), web_contents()->GetSiteInstance()); | 
 |   std::unique_ptr<WebContents> new_web_contents( | 
 |       WebContents::Create(new_contents_params)); | 
 |  | 
 |   EXPECT_TRUE(NavigateToURL(new_web_contents.get(), GURL(url::kAboutBlankURL))); | 
 |   EXPECT_NE(web_contents()->GetPrimaryMainFrame()->GetProcess(), | 
 |             new_web_contents->GetPrimaryMainFrame()->GetProcess()); | 
 | } | 
 |  | 
 | // Tests that verify the feature disabling process reuse. | 
 | class DisableProcessReusePolicyTest : public SitePerProcessBrowserTest { | 
 |  public: | 
 |   DisableProcessReusePolicyTest() { | 
 |     scoped_feature_list_.InitAndEnableFeature(features::kDisableProcessReuse); | 
 |   } | 
 |   ~DisableProcessReusePolicyTest() override = default; | 
 |  | 
 |   DisableProcessReusePolicyTest(const DisableProcessReusePolicyTest&) = delete; | 
 |   DisableProcessReusePolicyTest& operator=( | 
 |       const DisableProcessReusePolicyTest&) = delete; | 
 |  | 
 |  private: | 
 |   base::test::ScopedFeatureList scoped_feature_list_; | 
 | }; | 
 |  | 
 | // In two tabs with the same site, open a cross site iframe in each (same site | 
 | // for the iframes). Make sure these do not have the same process ID. | 
 | IN_PROC_BROWSER_TEST_P(DisableProcessReusePolicyTest, | 
 |                        DisableProcessReusePolicy) { | 
 |   GURL url( | 
 |       embedded_test_server()->GetURL("www.foo.com", "/page_with_iframe.html")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url)); | 
 |   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); | 
 |   FrameTreeNode* child = root->child_at(0); | 
 |  | 
 |   // Navigate the subframe cross site, and make sure it is an OOPIF. | 
 |   GURL cross_site_url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
 |   TestNavigationObserver observer(shell()->web_contents()); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(child, cross_site_url)); | 
 |   EXPECT_TRUE(child->current_frame_host()->IsCrossProcessSubframe()); | 
 |  | 
 |   // Open an new tab in a separate BrowsingInstance with the same url as the | 
 |   // first tab and open a subframe, also to |cross_site_url|. | 
 |   Shell* second_shell = CreateBrowser(); | 
 |   EXPECT_TRUE(NavigateToURL(second_shell, url)); | 
 |   FrameTreeNode* second_root = | 
 |       static_cast<WebContentsImpl*>(second_shell->web_contents()) | 
 |           ->GetPrimaryFrameTree() | 
 |           .root(); | 
 |   FrameTreeNode* second_child = second_root->child_at(0); | 
 |   EXPECT_TRUE(NavigateToURLFromRenderer(second_child, cross_site_url)); | 
 |   EXPECT_TRUE(second_child->current_frame_host()->IsCrossProcessSubframe()); | 
 |  | 
 |   scoped_refptr<SiteInstanceImpl> second_shell_instance = | 
 |       second_child->current_frame_host()->GetSiteInstance(); | 
 |   EXPECT_NE(ProcessReusePolicy::REUSE_PENDING_OR_COMMITTED_SITE_WORKER, | 
 |             second_shell_instance->process_reuse_policy()); | 
 |   EXPECT_NE(ProcessReusePolicy::REUSE_PENDING_OR_COMMITTED_SITE_SUBFRAME, | 
 |             second_shell_instance->process_reuse_policy()); | 
 |  | 
 |   EXPECT_NE(child->current_frame_host()->GetProcess(), | 
 |             second_child->current_frame_host()->GetProcess()); | 
 | } | 
 |  | 
 | class SitePerProcessWithMainFrameThresholdTestBase | 
 |     : public SitePerProcessBrowserTestBase { | 
 |  public: | 
 |   static constexpr size_t kDefaultThreshold = 3; | 
 |  | 
 |   explicit SitePerProcessWithMainFrameThresholdTestBase( | 
 |       size_t frame_threshold = kDefaultThreshold, | 
 |       size_t total_memory_threshold = 0) { | 
 |     base::FieldTrialParams params = { | 
 |         {"ProcessPerSiteMainFrameThreshold", | 
 |          base::StringPrintf("%zu", frame_threshold)}}; | 
 |     if (total_memory_threshold != 0) { | 
 |       params["ProcessPerSiteMainFrameTotalMemoryLimit"] = | 
 |           base::StringPrintf("%zu", total_memory_threshold); | 
 |     } | 
 |     scoped_feature_list_.InitAndEnableFeatureWithParameters( | 
 |         features::kProcessPerSiteUpToMainFrameThreshold, params); | 
 |   } | 
 |   ~SitePerProcessWithMainFrameThresholdTestBase() override = default; | 
 |  | 
 |   Shell* CreateShellAndNavigateToURL(const GURL& url) { | 
 |     const GURL kOtherUrl = | 
 |         embedded_test_server()->GetURL("bar.test", "/title1.html"); | 
 |  | 
 |     Shell* shell = CreateBrowser(); | 
 |     // Navigate to a different site first so that the new shell has  a non empty | 
 |     // site info before navigating to the target site. | 
 |     // TODO(crbug.com/40264958): Remove this workaround once we figure | 
 |     // out how to handle navigation from an empty site to a new site. | 
 |     CHECK(NavigateToURL(shell, kOtherUrl)); | 
 |     CHECK(NavigateToURL(shell, url)); | 
 |     return shell; | 
 |   } | 
 |  | 
 |  private: | 
 |   base::test::ScopedFeatureList scoped_feature_list_; | 
 | }; | 
 |  | 
 | class SitePerProcessWithMainFrameThresholdTest | 
 |     : public SitePerProcessWithMainFrameThresholdTestBase, | 
 |       public ::testing::WithParamInterface<std::string> { | 
 |  public: | 
 |   SitePerProcessWithMainFrameThresholdTest() = default; | 
 |   ~SitePerProcessWithMainFrameThresholdTest() override = default; | 
 | }; | 
 |  | 
 | // Tests that a RenderProcessHost is reused up to a certain threshold against | 
 | // number of main frames, if the corresponding SiteInstance requires a dedicated | 
 | // process. Subframes are irrelevant to the threshold. Once the number of main | 
 | // frame reaches to the threshold, a new RenderProcessHost should be created and | 
 | // the existing RenderProcessHost should not be reused. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessWithMainFrameThresholdTest, | 
 |                        ReuseProcessUpToThreshold) { | 
 |   const GURL kUrl = | 
 |       embedded_test_server()->GetURL("foo.test", "/page_with_iframe.html"); | 
 |   const GURL kOtherUrl = | 
 |       embedded_test_server()->GetURL("bar.test", "/title1.html"); | 
 |  | 
 |   ASSERT_TRUE(NavigateToURL(shell(), kUrl)); | 
 |   RenderFrameHostImpl* main_frame_in_main_shell = | 
 |       static_cast<WebContentsImpl*>(shell()->web_contents()) | 
 |           ->GetPrimaryMainFrame(); | 
 |   RenderFrameHostImpl* subframe_in_main_shell = | 
 |       main_frame_in_main_shell->child_at(0)->current_frame_host(); | 
 |   ASSERT_EQ(main_frame_in_main_shell->GetProcess(), | 
 |             subframe_in_main_shell->GetProcess()); | 
 |  | 
 |   std::vector<Shell*> shells; | 
 |   for (size_t i = 0; i < kDefaultThreshold - 1; ++i) { | 
 |     Shell* new_shell = CreateShellAndNavigateToURL(kUrl); | 
 |     RenderFrameHostImpl* new_frame = | 
 |         static_cast<WebContentsImpl*>(new_shell->web_contents()) | 
 |             ->GetPrimaryMainFrame(); | 
 |     // Currently the reuse policy is only applied for sites that require a | 
 |     // dedicated process, and if this not the case, the two main frames won't | 
 |     // share a process due to being under the process limit. | 
 |     if (main_frame_in_main_shell->GetSiteInstance() | 
 |             ->RequiresDedicatedProcess()) { | 
 |       ASSERT_EQ(main_frame_in_main_shell->GetProcess(), | 
 |                 new_frame->GetProcess()); | 
 |     } else { | 
 |       ASSERT_NE(main_frame_in_main_shell->GetProcess(), | 
 |                 new_frame->GetProcess()); | 
 |     } | 
 |     shells.emplace_back(new_shell); | 
 |   } | 
 |  | 
 |   Shell* non_shared_shell = CreateBrowser(); | 
 |   // TODO(crbug.com/40264958): Remove this workaround once we figure | 
 |   // out how to handle navigation from an empty site to a new site. | 
 |   ASSERT_TRUE(NavigateToURL(non_shared_shell, kOtherUrl)); | 
 |   ASSERT_TRUE(NavigateToURL(non_shared_shell, kUrl)); | 
 |   RenderFrameHostImpl* main_frame_in_non_shared_frame = | 
 |       static_cast<WebContentsImpl*>(non_shared_shell->web_contents()) | 
 |           ->GetPrimaryMainFrame(); | 
 |   ASSERT_NE(main_frame_in_main_shell->GetProcess(), | 
 |             main_frame_in_non_shared_frame->GetProcess()); | 
 |   shells.emplace_back(non_shared_shell); | 
 |  | 
 |   for (auto*& shell : shells) { | 
 |     shell->Close(); | 
 |   } | 
 | } | 
 |  | 
 | // Tests that renderer process is not reused when it hangs: | 
 | // 1. For OOP iframe in a different tab; | 
 | // 2. For main frame in a different tab. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessWithMainFrameThresholdTest, | 
 |                        DoNotReuseRenderProcessAfterHung) { | 
 |   const GURL kUrl_a_b(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b)")); | 
 |  | 
 |   const GURL kUrl_c_b(embedded_test_server()->GetURL( | 
 |       "c.com", "/cross_site_iframe_factory.html?c(b)")); | 
 |  | 
 |   const GURL kUrl_b(embedded_test_server()->GetURL("b.com", "/title1.html")); | 
 |  | 
 |   // Ensure the reuse of processes for same-site URLs, to test | 
 |   // that the process is not reused when it becomes unresponsive. | 
 |   RenderProcessHost::SetMaxRendererProcessCount(1); | 
 |  | 
 |   ASSERT_TRUE(NavigateToURL(shell(), kUrl_a_b)); | 
 |   RenderFrameHostImpl* main_frame = | 
 |       static_cast<WebContentsImpl*>(shell()->web_contents()) | 
 |           ->GetPrimaryMainFrame(); | 
 |   RenderFrameHostImpl* subframe = main_frame->child_at(0)->current_frame_host(); | 
 |   RenderProcessHost* main_frame_process = main_frame->GetProcess(); | 
 |   RenderProcessHost* b_subframe_process = subframe->GetProcess(); | 
 |   ASSERT_NE(main_frame_process, b_subframe_process); | 
 |  | 
 |   // Hang b.com process with OOP iframe. | 
 |   { | 
 |     UnresponsiveRendererObserver unresponsive_renderer_observer( | 
 |         shell()->web_contents()); | 
 |  | 
 |     // This is to simulate renderer hung event. Class | 
 |     // SimulateUnresponsiveRenderer does not work here, because it hits only | 
 |     // WebContents, while we need widget to know that it is unresponsive. | 
 |     static_cast<RenderWidgetHostImpl*>(subframe->GetRenderWidgetHost()) | 
 |         ->OnInputEventAckTimeout(base::TimeTicks::Now() + | 
 |                                  input::kHungRendererDelay); | 
 |  | 
 |     RenderProcessHost* hung_process = unresponsive_renderer_observer.Wait(); | 
 |     EXPECT_EQ(hung_process, b_subframe_process); | 
 |   } | 
 |  | 
 |   // 1. Navigate to url with b.com iframe, for which process is unresponsive. | 
 |   Shell* cb_shell = CreateShellAndNavigateToURL(kUrl_c_b); | 
 |   RenderFrameHostImpl* cb_main_frame = | 
 |       static_cast<WebContentsImpl*>(cb_shell->web_contents()) | 
 |           ->GetPrimaryMainFrame(); | 
 |  | 
 |   RenderFrameHostImpl* cb_subframe = | 
 |       cb_main_frame->child_at(0)->current_frame_host(); | 
 |  | 
 |   // Check that b.com iframe is not reusing existing unresponsive process with | 
 |   // b.com. | 
 |   ASSERT_NE(b_subframe_process, cb_subframe->GetProcess()); | 
 |  | 
 |   // 2. Navigate main frame to b.com, for which process is unresponsive. | 
 |   Shell* b_shell = CreateShellAndNavigateToURL(kUrl_b); | 
 |   RenderFrameHostImpl* b_main_frame = | 
 |       static_cast<WebContentsImpl*>(b_shell->web_contents()) | 
 |           ->GetPrimaryMainFrame(); | 
 |  | 
 |   // Check that b.com main frame is not reusing existing unresponsive process | 
 |   // with b.com. | 
 |   ASSERT_NE(b_subframe_process, b_main_frame->GetProcess()); | 
 | } | 
 |  | 
 | // Test fixture that enables kProcessPerSiteUpToMainFrameThreshold and sets up | 
 | // a SitePerProcessWithMainFrameThresholdAndSiteRestrictionBrowserClient to | 
 | // restrict the sites for which ProcessPerSite is used. | 
 | class SitePerProcessWithMainFrameThresholdAndSiteRestrictionTest | 
 |     : public SitePerProcessWithMainFrameThresholdTest { | 
 |  public: | 
 |   SitePerProcessWithMainFrameThresholdAndSiteRestrictionTest() { | 
 |     // Initialize both features in a single call | 
 |     scoped_feature_list_.InitAndEnableFeature( | 
 |         features::kProcessPerSiteUpToMainFrameThreshold); | 
 |   } | 
 |  | 
 |   void SetUpOnMainThread() override { | 
 |     SitePerProcessWithMainFrameThresholdTest::SetUpOnMainThread(); | 
 |     test_client_ = std::make_unique< | 
 |         SitePerProcessWithMainFrameThresholdAndSiteRestrictionBrowserClient>(); | 
 |   } | 
 |  | 
 |  private: | 
 |   base::test::ScopedFeatureList scoped_feature_list_; | 
 |   std::unique_ptr< | 
 |       SitePerProcessWithMainFrameThresholdAndSiteRestrictionBrowserClient> | 
 |       test_client_; | 
 | }; | 
 |  | 
 | // Verify that ShouldReuseExistingProcessForNewMainFrameSiteInstance is honored | 
 | // when deciding whether to reuse a process for a main frame navigation under | 
 | // the threshold, provided the controlling feature flag is enabled. | 
 | IN_PROC_BROWSER_TEST_P( | 
 |     SitePerProcessWithMainFrameThresholdAndSiteRestrictionTest, | 
 |     RestrictedToURLWithContentClient) { | 
 |   GURL foo_url = embedded_test_server()->GetURL("foo.com", "/title1.html"); | 
 |   GURL bar_url = embedded_test_server()->GetURL("bar.com", "/title2.html"); | 
 |  | 
 |   auto* shell_foo1 = CreateShellAndNavigateToURL(foo_url); | 
 |   RenderProcessHost* rph_foo1 = | 
 |       shell_foo1->web_contents()->GetPrimaryMainFrame()->GetProcess(); | 
 |  | 
 |   auto* shell_foo2 = CreateShellAndNavigateToURL(foo_url); | 
 |   RenderProcessHost* rph_foo2 = | 
 |       shell_foo2->web_contents()->GetPrimaryMainFrame()->GetProcess(); | 
 |  | 
 |   // Verify foo.com reuse processes. | 
 |   EXPECT_EQ(rph_foo1, rph_foo2); | 
 |  | 
 |   auto* shell_bar1 = CreateShellAndNavigateToURL(bar_url); | 
 |   RenderProcessHost* rph_bar1 = | 
 |       shell_bar1->web_contents()->GetPrimaryMainFrame()->GetProcess(); | 
 |  | 
 |   auto* shell_bar2 = CreateShellAndNavigateToURL(bar_url); | 
 |   RenderProcessHost* rph_bar2 = | 
 |       shell_bar2->web_contents()->GetPrimaryMainFrame()->GetProcess(); | 
 |  | 
 |   // Verify bar.com did not reuse processes. | 
 |   EXPECT_NE(rph_bar1, rph_bar2); | 
 |  | 
 |   // Verify foo.com and bar.com are in different processes. | 
 |   EXPECT_NE(rph_foo1, rph_bar1); | 
 |   EXPECT_NE(rph_foo1, rph_bar2); | 
 | } | 
 |  | 
 | // Verify that ShouldReuseExistingProcessForNewMainFrameSiteInstance's | 
 | // path-specific logic, using the original_url, correctly assigns different | 
 | // processes to main frame navigations on the same domain but with different | 
 | // paths, under the kProcessPerSiteUpToMainFrameThreshold policy. | 
 | IN_PROC_BROWSER_TEST_P( | 
 |     SitePerProcessWithMainFrameThresholdAndSiteRestrictionTest, | 
 |     PathSpecificOriginalUrlReuse) { | 
 |   GURL foo_url_path = embedded_test_server()->GetURL("foo.com", "/title1.html"); | 
 |   GURL foo_url_path_noreuse = | 
 |       embedded_test_server()->GetURL("foo.com", "/title2.html"); | 
 |   // Navigate to foo.com/title1.html (matches client rule for reuse) however | 
 |   // foo.com/title2.html should not be reused since they have different paths. | 
 |   auto* shell_foo_url_path = CreateShellAndNavigateToURL(foo_url_path); | 
 |   RenderProcessHost* rph_foo_url_path = | 
 |       shell_foo_url_path->web_contents()->GetPrimaryMainFrame()->GetProcess(); | 
 |  | 
 |   auto* shell_foo_url_path_noreuse = | 
 |       CreateShellAndNavigateToURL(foo_url_path_noreuse); | 
 |   RenderProcessHost* rph_foo_url_path_noreuse = | 
 |       shell_foo_url_path_noreuse->web_contents() | 
 |           ->GetPrimaryMainFrame() | 
 |           ->GetProcess(); | 
 |  | 
 |   EXPECT_NE(rph_foo_url_path, rph_foo_url_path_noreuse); | 
 | } | 
 |  | 
 | // A test fixture that provides an upper limit of 4 bytes, so should fail the | 
 | // assignment of another outermost main frame into the process. | 
 | class SitePerProcessWithMainFrameThresholdWithTotalLimitTest | 
 |     : public SitePerProcessWithMainFrameThresholdTestBase, | 
 |       public ::testing::WithParamInterface<std::string> { | 
 |  public: | 
 |   SitePerProcessWithMainFrameThresholdWithTotalLimitTest() | 
 |       : SitePerProcessWithMainFrameThresholdTestBase( | 
 |             /*frame_threshold=*/10, | 
 |             /*total_memory_threshold=*/9) {} | 
 |   ~SitePerProcessWithMainFrameThresholdWithTotalLimitTest() override = default; | 
 | }; | 
 |  | 
 | class RendererHostInterceptor | 
 |     : public mojom::RendererHostInterceptorForTesting { | 
 |  public: | 
 |   explicit RendererHostInterceptor(RenderProcessHostImpl* process_host) | 
 |       : swapped_impl_(process_host->renderer_host_receiver_for_testing(), | 
 |                       this) {} | 
 |   mojom::RendererHost* GetForwardingInterface() override { | 
 |     return swapped_impl_.old_impl(); | 
 |   } | 
 |  | 
 | #if BUILDFLAG(IS_ANDROID) | 
 |   void SetPrivateMemoryFootprint( | 
 |       uint64_t private_memory_footprint_bytes) override { | 
 |     // Drop this message from the renderer. | 
 |   } | 
 | #endif | 
 |  | 
 |  private: | 
 |   mojo::test::ScopedSwapImplForTesting<mojom::RendererHost> swapped_impl_; | 
 | }; | 
 |  | 
 | // Tests that a RenderProcessHost is not reused when the private memory | 
 | // footprint of the process exceeds a certain amount. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessWithMainFrameThresholdWithTotalLimitTest, | 
 |                        ExcessiveAllocation) { | 
 |   const GURL kUrl = | 
 |       embedded_test_server()->GetURL("foo.test", "/page_with_iframe.html"); | 
 |  | 
 |   base::HistogramTester histograms; | 
 |   ASSERT_TRUE(NavigateToURL(shell(), kUrl)); | 
 |   RenderFrameHostImpl* main_frame_in_main_shell = | 
 |       static_cast<WebContentsImpl*>(shell()->web_contents()) | 
 |           ->GetPrimaryMainFrame(); | 
 |   RenderFrameHostImpl* subframe_in_main_shell = | 
 |       main_frame_in_main_shell->child_at(0)->current_frame_host(); | 
 |   ASSERT_EQ(main_frame_in_main_shell->GetProcess(), | 
 |             subframe_in_main_shell->GetProcess()); | 
 |  | 
 |   Shell* new_shell = CreateShellAndNavigateToURL(kUrl); | 
 |   RenderFrameHostImpl* new_frame = | 
 |       static_cast<WebContentsImpl*>(new_shell->web_contents()) | 
 |           ->GetPrimaryMainFrame(); | 
 |   ASSERT_NE(main_frame_in_main_shell->GetProcess(), new_frame->GetProcess()); | 
 |   new_shell->Close(); | 
 |  | 
 |   // Verify that we hit a limit histogram. | 
 |   histograms.ExpectTotalCount( | 
 |       "BrowserRenderProcessHost.ProcessPerSiteMainFrameLimit", 1); | 
 |   histograms.ExpectBucketCount( | 
 |       "BrowserRenderProcessHost.ProcessPerSiteMainFrameLimit", 1, 1); | 
 | } | 
 |  | 
 | // Tests that opening a fourth tab will put it over the limit and will allocate | 
 | // a new process. We allocate 3 main frames that are 2 bytes each. Placing | 
 | // a fourth would exceeded the limit of 9. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessWithMainFrameThresholdWithTotalLimitTest, | 
 |                        AllowedAllocation) { | 
 |   const GURL kUrl = | 
 |       embedded_test_server()->GetURL("foo.test", "/page_with_iframe.html"); | 
 |  | 
 |   base::HistogramTester histograms; | 
 |   ASSERT_TRUE(NavigateToURL(shell(), kUrl)); | 
 |   RenderFrameHostImpl* main_frame_in_main_shell = | 
 |       static_cast<WebContentsImpl*>(shell()->web_contents()) | 
 |           ->GetPrimaryMainFrame(); | 
 |   RenderFrameHostImpl* subframe_in_main_shell = | 
 |       main_frame_in_main_shell->child_at(0)->current_frame_host(); | 
 |   ASSERT_EQ(main_frame_in_main_shell->GetProcess(), | 
 |             subframe_in_main_shell->GetProcess()); | 
 |  | 
 |   auto* process_host = static_cast<RenderProcessHostImpl*>( | 
 |       main_frame_in_main_shell->GetProcess()); | 
 |   RendererHostInterceptor interceptor(process_host); | 
 |   process_host->SetPrivateMemoryFootprintForTesting(2); | 
 |  | 
 |   std::vector<Shell*> shells; | 
 |   for (size_t i = 0; i < 2; ++i) { | 
 |     Shell* new_shell = CreateShellAndNavigateToURL(kUrl); | 
 |     RenderFrameHostImpl* new_frame = | 
 |         static_cast<WebContentsImpl*>(new_shell->web_contents()) | 
 |             ->GetPrimaryMainFrame(); | 
 |     // Currently the reuse policy is only applied for sites that require a | 
 |     // dedicated process, and if this not the case, the two main frames won't | 
 |     // share a process due to being under the process limit. | 
 |     if (main_frame_in_main_shell->GetSiteInstance() | 
 |             ->RequiresDedicatedProcess()) { | 
 |       ASSERT_EQ(main_frame_in_main_shell->GetProcess(), | 
 |                 new_frame->GetProcess()); | 
 |     } else { | 
 |       ASSERT_NE(main_frame_in_main_shell->GetProcess(), | 
 |                 new_frame->GetProcess()); | 
 |     } | 
 |     process_host->SetPrivateMemoryFootprintForTesting(2 * (i + 2)); | 
 |     shells.emplace_back(new_shell); | 
 |   } | 
 |   EXPECT_EQ( | 
 |       6u, main_frame_in_main_shell->GetProcess()->GetPrivateMemoryFootprint()); | 
 |  | 
 |   // The 4th outermostmain frame will not fit. | 
 |   // The expected size of a frame will be 2, with a scale factor of 1.5 | 
 |   // 6 + (2 * 1.5) > 9 so the check should fail. | 
 |   Shell* fourth_shell = CreateShellAndNavigateToURL(kUrl); | 
 |   RenderFrameHostImpl* fourth_frame = | 
 |       static_cast<WebContentsImpl*>(fourth_shell->web_contents()) | 
 |           ->GetPrimaryMainFrame(); | 
 |   ASSERT_NE(main_frame_in_main_shell->GetProcess(), fourth_frame->GetProcess()); | 
 |   shells.emplace_back(fourth_shell); | 
 |   for (auto*& shell : shells) { | 
 |     shell->Close(); | 
 |   } | 
 |   // Verify that we hit a limit histogram. | 
 |   histograms.ExpectTotalCount( | 
 |       "BrowserRenderProcessHost.ProcessPerSiteMainFrameLimit", 1); | 
 |   histograms.ExpectBucketCount( | 
 |       "BrowserRenderProcessHost.ProcessPerSiteMainFrameLimit", 3, 1); | 
 | } | 
 |  | 
 | // Tests that opening a new tab from an existing page via ctrl-click reuses a | 
 | // process when both pages are the same-site. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessWithMainFrameThresholdTest, | 
 |                        ReuseProcessOpenTabByCtrlClickLink) { | 
 |   const GURL kUrl = embedded_test_server()->GetURL( | 
 |       "foo.test", "/ctrl-click-subframe-link.html"); | 
 |   ASSERT_TRUE(NavigateToURL(shell(), kUrl)); | 
 |   RenderFrameHostImpl* main_frame = | 
 |       static_cast<WebContentsImpl*>(shell()->web_contents()) | 
 |           ->GetPrimaryMainFrame(); | 
 |   ShellAddedObserver new_shell_observer; | 
 |   ASSERT_TRUE(ExecJs(main_frame, | 
 |                      "window.domAutomationController.send(ctrlClickLink());")); | 
 |   Shell* popup = new_shell_observer.GetShell(); | 
 |   ASSERT_EQ(main_frame->GetProcess(), | 
 |             static_cast<WebContentsImpl*>(popup->web_contents()) | 
 |                 ->GetPrimaryMainFrame() | 
 |                 ->GetProcess()); | 
 | } | 
 |  | 
 | // Tests that opening a new tab from an existing page via window.open reuses a | 
 | // process when both pages are the same-site. | 
 | // TODO(crbug.com/40264958): Change this test to use 'noopener' once we | 
 | // figure out how to handle navigation from an empty site to a new site. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessWithMainFrameThresholdTest, | 
 |                        ReuseProcessWithOpener) { | 
 |   const GURL kUrl = embedded_test_server()->GetURL("foo.test", "/title1.html"); | 
 |   ASSERT_TRUE(NavigateToURL(shell(), kUrl)); | 
 |   RenderFrameHostImpl* main_frame = | 
 |       static_cast<WebContentsImpl*>(shell()->web_contents()) | 
 |           ->GetPrimaryMainFrame(); | 
 |   ShellAddedObserver new_shell_observer; | 
 |   ASSERT_TRUE( | 
 |       ExecJs(main_frame, "popup = window.open('/title1.html', '_blank');")); | 
 |   Shell* popup = new_shell_observer.GetShell(); | 
 |   ASSERT_EQ(main_frame->GetProcess(), | 
 |             static_cast<WebContentsImpl*>(popup->web_contents()) | 
 |                 ->GetPrimaryMainFrame() | 
 |                 ->GetProcess()); | 
 | } | 
 |  | 
 | class SitePerProcessWithMainFrameThresholdLocalhostTest | 
 |     : public SitePerProcessWithMainFrameThresholdTestBase, | 
 |       public ::testing::WithParamInterface<bool> { | 
 |  public: | 
 |   SitePerProcessWithMainFrameThresholdLocalhostTest() { | 
 |     scoped_feature_list_.InitAndEnableFeatureWithParameters( | 
 |         features::kProcessPerSiteUpToMainFrameThreshold, | 
 |         {{"ProcessPerSiteMainFrameThreshold", | 
 |           base::StringPrintf("%zu", kDefaultThreshold)}, | 
 |          {"ProcessPerSiteMainFrameAllowIPAndLocalhost", | 
 |           base::ToString(IsLocalhostAllowed())}}); | 
 |   } | 
 |   ~SitePerProcessWithMainFrameThresholdLocalhostTest() override = default; | 
 |  | 
 |   bool IsLocalhostAllowed() { return GetParam(); } | 
 |  | 
 |  private: | 
 |   base::test::ScopedFeatureList scoped_feature_list_; | 
 | }; | 
 |  | 
 | // Tests that process reuse is allowed or disallowed for localhost based on a | 
 | // feature parameter. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessWithMainFrameThresholdLocalhostTest, | 
 |                        AllowReuseLocalHost) { | 
 |   const GURL kUrl = embedded_test_server()->GetURL("localhost", "/title1.html"); | 
 |   ASSERT_TRUE(net::IsLocalHostname(kUrl.host())); | 
 |  | 
 |   ASSERT_TRUE(NavigateToURL(shell(), kUrl)); | 
 |   Shell* second_shell = CreateShellAndNavigateToURL(kUrl); | 
 |  | 
 |   RenderFrameHostImpl* main_frame = | 
 |       static_cast<WebContentsImpl*>(shell()->web_contents()) | 
 |           ->GetPrimaryMainFrame(); | 
 |   RenderFrameHostImpl* second_frame = | 
 |       static_cast<WebContentsImpl*>(second_shell->web_contents()) | 
 |           ->GetPrimaryMainFrame(); | 
 |   if (IsLocalhostAllowed()) { | 
 |     ASSERT_EQ(main_frame->GetProcess(), second_frame->GetProcess()); | 
 |   } else { | 
 |     ASSERT_NE(main_frame->GetProcess(), second_frame->GetProcess()); | 
 |   } | 
 | } | 
 |  | 
 | class SitePerProcessWithMainFrameThresholdDevToolsTest | 
 |     : public SitePerProcessWithMainFrameThresholdTestBase, | 
 |       public TestDevToolsProtocolClient { | 
 |  public: | 
 |   SitePerProcessWithMainFrameThresholdDevToolsTest() = default; | 
 |   ~SitePerProcessWithMainFrameThresholdDevToolsTest() override = default; | 
 |  | 
 |   void TearDown() override { | 
 |     DetachProtocolClient(); | 
 |     SitePerProcessWithMainFrameThresholdTestBase::TearDown(); | 
 |   } | 
 | }; | 
 |  | 
 | // Tests that process reuse is diallowed when DevTools is attached to the | 
 | // renderer process. | 
 | IN_PROC_BROWSER_TEST_F(SitePerProcessWithMainFrameThresholdDevToolsTest, | 
 |                        DevToolsAttached) { | 
 |   const GURL kUrl = embedded_test_server()->GetURL("foo.test", "/title1.html"); | 
 |  | 
 |   ASSERT_TRUE(NavigateToURL(shell(), kUrl)); | 
 |  | 
 |   AttachToWebContents(shell()->web_contents()); | 
 |   set_agent_host_can_close(); | 
 |  | 
 |   Shell* second_shell = CreateShellAndNavigateToURL(kUrl); | 
 |   RenderFrameHostImpl* main_frame = | 
 |       static_cast<WebContentsImpl*>(shell()->web_contents()) | 
 |           ->GetPrimaryMainFrame(); | 
 |   RenderFrameHostImpl* second_frame = | 
 |       static_cast<WebContentsImpl*>(second_shell->web_contents()) | 
 |           ->GetPrimaryMainFrame(); | 
 |   ASSERT_NE(main_frame->GetProcess(), second_frame->GetProcess()); | 
 | } | 
 |  | 
 | // Helper class to enable subframe process reuse thresholds and set the total | 
 | // allowed memory limit to 8 bytes. | 
 | class SitePerProcessWithSubframeProcessReuseThresholdsTest | 
 |     : public SitePerProcessBrowserTestBase, | 
 |       public ::testing::WithParamInterface<std::string> { | 
 |  public: | 
 |   SitePerProcessWithSubframeProcessReuseThresholdsTest() { | 
 |     size_t total_memory_limit = 8; | 
 |     base::FieldTrialParams params = { | 
 |         {"SubframeProcessReuseMemoryThreshold", | 
 |          base::StringPrintf("%zu", total_memory_limit)}}; | 
 |     scoped_feature_list_.InitAndEnableFeatureWithParameters( | 
 |         features::kSubframeProcessReuseThresholds, params); | 
 |   } | 
 |   ~SitePerProcessWithSubframeProcessReuseThresholdsTest() override = default; | 
 |  | 
 |  private: | 
 |   base::test::ScopedFeatureList scoped_feature_list_; | 
 | }; | 
 |  | 
 | // Verify that a subframe will only reuse an existing process if adding | 
 | // another subframe to that process won't exceed the memory threshold. | 
 | IN_PROC_BROWSER_TEST_P(SitePerProcessWithSubframeProcessReuseThresholdsTest, | 
 |                        SubframeReuseRespectsMemoryThreshold) { | 
 |   base::HistogramTester histograms; | 
 |  | 
 |   // Start with a simple a(b) page. | 
 |   GURL main_url(embedded_test_server()->GetURL( | 
 |       "a.com", "/cross_site_iframe_factory.html?a(b)")); | 
 |   EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
 |   RenderFrameHostImpl* main_frame1 = | 
 |       static_cast<WebContentsImpl*>(shell()->web_contents()) | 
 |           ->GetPrimaryMainFrame(); | 
 |   RenderFrameHostImpl* subframe1 = | 
 |       main_frame1->child_at(0)->current_frame_host(); | 
 |   auto* subframe_process = | 
 |       static_cast<RenderProcessHostImpl*>(subframe1->GetProcess()); | 
 |   ASSERT_NE(main_frame1->GetProcess(), subframe_process); | 
 |  | 
 |   // Ignore private memory footprint updates from the renderer, and pretend | 
 |   // that the subframe process's PMF is currently 5 bytes. | 
 |   RendererHostInterceptor interceptor(subframe_process); | 
 |   subframe_process->SetPrivateMemoryFootprintForTesting(5); | 
 |  | 
 |   // Create an unrelated tab and navigate it to a(b). | 
 |   Shell* shell2 = CreateBrowser(); | 
 |   EXPECT_TRUE(NavigateToURL(shell2, main_url)); | 
 |   RenderFrameHostImpl* main_frame2 = | 
 |       static_cast<WebContentsImpl*>(shell2->web_contents()) | 
 |           ->GetPrimaryMainFrame(); | 
 |   RenderFrameHostImpl* subframe2 = | 
 |       main_frame2->child_at(0)->current_frame_host(); | 
 |   ASSERT_NE(main_frame2->GetProcess(), subframe2->GetProcess()); | 
 |  | 
 |   // The new b.com subframe should reuse the available b.com process from the | 
 |   // first tab. This is because the process uses 5 bytes of memory, which is | 
 |   // below the reuse threshold of 8 bytes. | 
 |   EXPECT_EQ(subframe2->GetProcess(), subframe_process); | 
 |  | 
 |   // Update the subframe process's PMF to 10, pretending that the second | 
 |   // subframe also takes up 5 bytes. | 
 |   subframe_process->SetPrivateMemoryFootprintForTesting(10); | 
 |  | 
 |   // Create a third tab and navigate it to a(b). | 
 |   Shell* shell3 = CreateBrowser(); | 
 |   EXPECT_TRUE(NavigateToURL(shell3, main_url)); | 
 |   RenderFrameHostImpl* main_frame3 = | 
 |       static_cast<WebContentsImpl*>(shell3->web_contents()) | 
 |           ->GetPrimaryMainFrame(); | 
 |   RenderFrameHostImpl* subframe3 = | 
 |       main_frame3->child_at(0)->current_frame_host(); | 
 |   ASSERT_NE(main_frame3->GetProcess(), subframe3->GetProcess()); | 
 |  | 
 |   // This time, the new b.com subframe should not reuse the available b.com | 
 |   // process from the first two tabs. This is because the process is consuming | 
 |   // 10 bytes of memory, which is above the reuse threshold of 8 bytes. | 
 |   EXPECT_NE(subframe3->GetProcess(), subframe_process); | 
 |  | 
 |   // Check that the histogram was recorded when the memory threshold was | 
 |   // exceeded for `subframe_process`. At that time, the process should've had | 
 |   // two total frames. | 
 |   histograms.ExpectTotalCount( | 
 |       "BrowserRenderProcessHost.SubframeProcessReuseThreshold.TotalFrames", 1); | 
 |   histograms.ExpectBucketCount( | 
 |       "BrowserRenderProcessHost.SubframeProcessReuseThreshold.TotalFrames", 2, | 
 |       1); | 
 | } | 
 |  | 
 | INSTANTIATE_TEST_SUITE_P(All, | 
 |                          RequestDelayingSitePerProcessBrowserTest, | 
 |                          testing::ValuesIn(RenderDocumentFeatureLevelValues())); | 
 | #if BUILDFLAG(IS_ANDROID) | 
 | INSTANTIATE_TEST_SUITE_P(All, | 
 |                          SitePerProcessAndroidImeTest, | 
 |                          testing::ValuesIn(RenderDocumentFeatureLevelValues())); | 
 | INSTANTIATE_TEST_SUITE_P(All, | 
 |                          AndroidInputBrowserTest, | 
 |                          testing::ValuesIn(RenderDocumentFeatureLevelValues())); | 
 | INSTANTIATE_TEST_SUITE_P(All, | 
 |                          SitePerProcessBrowserTestWithSubframePriority, | 
 |                          testing::ValuesIn(RenderDocumentFeatureLevelValues())); | 
 | #endif  // BUILDFLAG(IS_ANDROID) | 
 | INSTANTIATE_TEST_SUITE_P(All, | 
 |                          SitePerProcessAndProcessPerSiteBrowserTest, | 
 |                          testing::ValuesIn(RenderDocumentFeatureLevelValues())); | 
 | INSTANTIATE_TEST_SUITE_P(All, | 
 |                          SitePerProcessAutoplayBrowserTest, | 
 |                          testing::ValuesIn(RenderDocumentFeatureLevelValues())); | 
 | INSTANTIATE_TEST_SUITE_P(All, | 
 |                          SitePerProcessBrowserTest, | 
 |                          testing::ValuesIn(RenderDocumentFeatureLevelValues())); | 
 | INSTANTIATE_TEST_SUITE_P(All, | 
 |                          SitePerProcessNoSharingBrowserTest, | 
 |                          testing::ValuesIn(RenderDocumentFeatureLevelValues())); | 
 | INSTANTIATE_TEST_SUITE_P(All, | 
 |                          SitePerProcessBrowserTestWithoutSpeculativeRFHDelay, | 
 |                          testing::ValuesIn(RenderDocumentFeatureLevelValues())); | 
 | INSTANTIATE_TEST_SUITE_P(All, | 
 |                          SitePerProcessBrowserTouchActionTest, | 
 |                          testing::ValuesIn(RenderDocumentFeatureLevelValues())); | 
 | INSTANTIATE_TEST_SUITE_P(All, | 
 |                          SitePerProcessIgnoreCertErrorsBrowserTest, | 
 |                          testing::ValuesIn(RenderDocumentFeatureLevelValues())); | 
 | INSTANTIATE_TEST_SUITE_P(All, | 
 |                          DisableProcessReusePolicyTest, | 
 |                          testing::ValuesIn(RenderDocumentFeatureLevelValues())); | 
 | INSTANTIATE_TEST_SUITE_P(All, | 
 |                          SitePerProcessWithMainFrameThresholdTest, | 
 |                          testing::ValuesIn(RenderDocumentFeatureLevelValues())); | 
 | INSTANTIATE_TEST_SUITE_P(All, | 
 |                          SitePerProcessWithMainFrameThresholdWithTotalLimitTest, | 
 |                          testing::ValuesIn(RenderDocumentFeatureLevelValues())); | 
 | #if BUILDFLAG(IS_ANDROID) | 
 | INSTANTIATE_TEST_SUITE_P(All, | 
 |                          TouchSelectionControllerClientAndroidSiteIsolationTest, | 
 |                          testing::ValuesIn(RenderDocumentFeatureLevelValues())); | 
 | #endif  // BUILDFLAG(IS_ANDROID) | 
 | INSTANTIATE_TEST_SUITE_P(All, | 
 |                          SitePerProcessBrowserTestWithLeakDetector, | 
 |                          testing::ValuesIn(RenderDocumentFeatureLevelValues())); | 
 |  | 
 | INSTANTIATE_TEST_SUITE_P(All, | 
 |                          SitePerProcessWithMainFrameThresholdLocalhostTest, | 
 |                          testing::Bool()); | 
 |  | 
 | INSTANTIATE_TEST_SUITE_P(All, | 
 |                          SitePerProcessWithSubframeProcessReuseThresholdsTest, | 
 |                          testing::ValuesIn(RenderDocumentFeatureLevelValues())); | 
 |  | 
 | INSTANTIATE_TEST_SUITE_P( | 
 |     All, | 
 |     SitePerProcessWithMainFrameThresholdAndSiteRestrictionTest, | 
 |     testing::ValuesIn(RenderDocumentFeatureLevelValues())); | 
 | INSTANTIATE_TEST_SUITE_P(All, | 
 |                          StickyActivationAcrossSameOriginNavBrowserTest, | 
 |                          testing::ValuesIn(RenderDocumentFeatureLevelValues())); | 
 |  | 
 | }  // namespace content |