chromium/content/browser/site_per_process_unload_browsertest.cc

// 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 <list>
#include <memory>
#include <string>
#include <utility>
#include <vector>

#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/weak_ptr.h"
#include "base/ranges/algorithm.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/test/run_until.h"
#include "base/test/scoped_run_loop_timeout.h"
#include "base/test/test_timeouts.h"
#include "base/test/with_feature_override.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "content/browser/fenced_frame/fenced_frame_url_mapping.h"
#include "content/browser/renderer_host/cross_process_frame_connector.h"
#include "content/browser/renderer_host/frame_tree.h"
#include "content/browser/renderer_host/navigation_controller_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_widget_host_view_child_frame.h"
#include "content/browser/site_per_process_browsertest.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/content_navigation_policy.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/common/isolated_world_ids.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_utils.h"
#include "content/public/test/content_mock_cert_verifier.h"
#include "content/shell/browser/shell.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "content/test/render_document_feature.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 "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

ElementsAre;
WhenSorted;

namespace content {

namespace {

void AddPagehideHandler(const ToRenderFrameHost& target, const char* message) {}

}  // namespace

// Tests that there are no crashes if a subframe is detached in its pagehide
// handler. See https://crbug.com/590054.
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, DetachInPagehideHandler) {}

// Tests that trying to navigate in the pagehide handler doesn't crash the
// browser.
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, NavigateInPagehideHandler) {}

// Verifies that when navigating an OOPIF to same site and then canceling
// navigation from beforeunload handler popup will not remove the
// RemoteFrameView from OOPIF's owner element in the parent process. This test
// uses OOPIF visibility to make sure RemoteFrameView exists after beforeunload
// is handled.
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest,
                       CanceledBeforeUnloadShouldNotClearRemoteFrameView) {}

// Ensure that after a main frame with an OOPIF is navigated cross-site, the
// pagehide handler in the OOPIF sees correct main frame origin, namely the old
// and not the new origin.  See https://crbug.com/825283.
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest,
                       ParentOriginDoesNotChangeInPagehideHandler) {}

// Verify that when the last active frame in a process is going away as part of
// OnUnload, the mojo::AgentSchedulingGroupHost::DidUnloadRenderFrame is
// received prior to the process starting to shut down, ensuring that any
// related unload work also happens before shutdown. See
// https://crbug.com/867274 and https://crbug.com/794625.
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest,
                       UnloadACKArrivesPriorToProcessShutdownRequest) {}

// This is a regression test for https://crbug.com/891423 in which tabs showing
// beforeunload dialogs stalled navigation and triggered the "hung process"
// dialog.
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest,
                       NoCommitTimeoutWithBeforeUnloadDialog) {}

// Test that pagehide handlers in iframes are run, even when the removed subtree
// is complicated with nested iframes in different processes.
//     A1                         A1
//    / \                        / \
//   B1  D  --- Navigate --->   E   D
//  / \
// C1  C2
// |   |
// B2  A2
//     |
//     C3
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, PagehideHandlerSubframes) {}

// Check that unload handlers in iframe don't prevents the main frame to be
// deleted after a timeout.
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, SlowUnloadHandlerInIframe) {}

// Navigate from A(B(A(B)) to C. Check the pagehide handler are executed,
// executed in the right order and the processes for A and B are removed.
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, PagehideHandlerABAB) {}

// Start with A(B(C)), navigate C to D and then B to E. By emulating a slow
// unload handler in B,C and D, the end result is C is in pending deletion in B
// and B is in pending deletion in A.
//   (1)     (2)     (3)
//|       |       |       |
//|   A   |  A    |   A   |
//|   |   |  |    |    \  |
//|   B   |  B    |  B  E |
//|   |   |   \   |   \   |
//|   C   | C  D  | C  D  |
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, UnloadNestedPendingDeletion) {}

// A set of nested frames A1(B1(A2)) are pending deletion because of a
// navigation. This tests what happens if only A2 has a pagehide handler.
// If B1's mojom::FrameHost::Detach is called before A2, it should not destroy
// itself and its children, but rather wait for A2.
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, PartialPagehideHandler) {}

// Test RenderFrameHostImpl::PendingDeletionCheckCompletedOnSubtree.
//
// After a navigation commit, some children with no pagehide handler may be
// eligible for immediate deletion. Several configurations are tested:
//
// Before navigation commit
//
//              0               |  N  : No pagehide handler
//   ‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑      | [N] : Pagehide handler
//  |  |  |  |  |   |     |     |
// [1] 2 [3] 5  7   9     12    |
//        |  |  |  / \   / \    |
//        4 [6] 8 10 11 13 [14] |
//
// After navigation commit (expected)
//
//              0               |  N  : No pagehide handler
//   ---------------------      | [N] : Pagehide handler
//  |     |  |            |     |
// [1]   [3] 5            12    |
//           |             \    |
//          [6]            [14] |
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest,
                       PendingDeletionCheckCompletedOnSubtree) {}

// When an iframe is detached, check that pagehide handlers execute in all of
// its child frames. Start from A(B(C)) and delete B from A.
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest,
                       DetachedIframePagehideHandlerABC) {}

#if defined(ADDRESS_SANITIZER) || defined(THREAD_SANITIZER) || \
    !defined(NDEBUG) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_ANDROID)
// Too slow under sanitizers and debug builds, even with increased timeout:
// https://crbug.com/1096612
// Disabled for Linux/Android due to failures: https://crbug.com/1494811
#define MAYBE_DetachedIframePagehideHandlerABCB
#else
#define MAYBE_DetachedIframePagehideHandlerABCB
#endif

// When an iframe is detached, check that pagehide handlers execute in all of
// its child frames. Start from A(B1(C(B2))) and delete B1 from A.
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest,
                       MAYBE_DetachedIframePagehideHandlerABCB) {}

// When an iframe is detached, check that pagehide handlers execute in all of
// its child frames. Start from A1(A2(B)), delete A2 from itself.
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest,
                       DetachedIframePagehideHandlerAAB) {}

// Tests that running layout from an pagehide handler inside teardown of the
// RenderWidget (inside WidgetMsg_Close) can succeed.
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest,
                       RendererInitiatedWindowCloseWithPagehide) {}

// Regression test for https://crbug.com/960006.
//
// 1. Navigate to a1(a2(b3),c4),
// 2. b3 has a slow unload handler.
// 3. a2 navigates same process.
// 4. When the new document is loaded, a message is sent to c4 to check it
//    cannot see b3 anymore, even if b3 is still unloading.
IN_PROC_BROWSER_TEST_P(
    SitePerProcessBrowserTest,
    IsDetachedSubframeObservableDuringUnloadHandlerSameProcess) {}

// Regression test for https://crbug.com/960006.
//
// 1. Navigate to a1(a2(b3),c4),
// 2. b3 has a slow unload handler.
// 3. a2 navigates cross process.
// 4. When the new document is loaded, a message is sent to c4 to check it
//    cannot see b3 anymore, even if b3 is still unloading.
//
// Note: This test is the same as the above, except it uses a cross-process
// navigation at step 3.
IN_PROC_BROWSER_TEST_P(
    SitePerProcessBrowserTest,
    IsDetachedSubframeObservableDuringUnloadHandlerCrossProcess) {}

// Regression test. https://crbug.com/963330
// 1. Start from A1(B2,C3)
// 2. B2 is the "focused frame", is deleted and starts unloading.
// 3. C3 commits a new navigation before B2 has completed its unload.
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, FocusedFrameUnload) {}

// Test the unload timeout is effective.
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, UnloadTimeout) {}

// Test that an unloading child can PostMessage its cross-process parent.
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest,
                       UnloadPostMessageToParentCrossProcess) {}

// Test that an unloading child can PostMessage its same-process parent.
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest,
                       UnloadPostMessageToParentSameProcess) {}

// Related to issue https://crbug.com/950625.
//
// 1. Start from A1(B1)
// 2. Navigate A1 to A3, same-process.
// 3. A1 requests the browser to detach B1, but this message is dropped.
// 4. The browser must be resilient and detach B1 when A3 commits.
// TODO(crbug.com/40914915): Fix flakes and re-enable test.
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest,
                       DISABLED_SameProcessNavigationResilientToDetachDropped) {}

#if BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
// See crbug.com/1275848.
#define MAYBE_NestedSubframeWithPagehideHandler
#else
#define MAYBE_NestedSubframeWithPagehideHandler
#endif
// After a same-origin iframe navigation, check that grandchild iframe are
// properly deleted and their pagehide handler executed.
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest,
                       MAYBE_NestedSubframeWithPagehideHandler) {}

// Some tests need an https server because third-party cookies are used, and
// SameSite=None cookies must be Secure. This is a separate fixture due to
// use the ContentMockCertVerifier.
class SitePerProcessSSLBrowserTest : public SitePerProcessBrowserTest {};

// Pagehide handlers should be able to do things that might require for instance
// the RenderFrameHostImpl to stay alive.
// - use console.log (handled via RFHI::DidAddMessageToConsole).
// - use history.replaceState (handled via RFHI::OnUpdateState).
// - use document.cookie
// - use localStorage
//
// Test case:
//  1. Start on A1(B2). B2 has a pagehide handler.
//  2. Go to A3.
//  3. Go back to A4(B5).
//
// TODO(crbug.com/41457585): history.replaceState is broken in OOPIFs.
//
// This test is similar to PagehideHandlersArePowerfulGrandChild, but with a
// different frame hierarchy.
//
// TODO(crbug.com/40283595): investigate test flakes and re-enable test.
IN_PROC_BROWSER_TEST_P(SitePerProcessSSLBrowserTest,
                       DISABLED_PagehideHandlersArePowerful) {}

// Pagehide handlers should be able to do things that might require for instance
// the RenderFrameHostImpl to stay alive.
// - use console.log (handled via RFHI::DidAddMessageToConsole).
// - use history.replaceState (handled via RFHI::OnUpdateState).
// - use document.cookie
// - use localStorage
//
// Test case:
//  1. Start on A1(B2(C3)). C3 has an unload handler.
//  2. Go to A4.
//  3. Go back to A5(B6(C7)).
//
// TODO(crbug.com/41457585): history.replaceState is broken in OOPIFs.
//
// This test is similar to PagehideHandlersArePowerful, but with a different
// frame hierarchy.
//
// TODO(crbug.com/40283595): investigate test flakes and re-enable test.
IN_PROC_BROWSER_TEST_P(SitePerProcessSSLBrowserTest,
                       DISABLED_PagehideHandlersArePowerfulGrandChild) {}

// Execute a pagehide handler from the initial empty document.
//
// Start from A1(B2(B3)).
// B3 is the initial empty document created by B2. A pagehide handler is added
// to B3. A1 deletes B2.
IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest,
                       UnloadInInitialEmptyDocument) {}

INSTANTIATE_TEST_SUITE_P();

// This test sets up a main frame which has an OOPIF. The main frame commits a
// same-site navigation. The test then stops at the stage where the unload
// handler of the OOPIF is running and the main frame RenderFrameHost's
// `DocumentAssociatedData` is retrieved from the OOPIF. The test shows that
// the `DocumentAssociatedData` is different from the one before navigation if
// RenderDocument feature is not enabled for all frames. One place we have seen
// this issue is in Protected Audience auctions. Please see crbug.com/1422301.
IN_PROC_BROWSER_TEST_P(
    SitePerProcessBrowserTest,
    MainFrameDocumentAssociatedDataChangesOnSameSiteNavigation) {}

}  // namespace content