
Permissions Policy code is being migrated out of Blink, to the Network service. The code depends on these flags so it needs to be moved. Bug: 382291442 Change-Id: I4e2bbb4d4c49392f0e845096cc8c62ae9ae9fb20 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6282334 Reviewed-by: Dave Tapuska <dtapuska@chromium.org> Reviewed-by: Ari Chivukula <arichiv@chromium.org> Reviewed-by: Owen Min <zmin@chromium.org> Commit-Queue: Sandor «Alex» Major <sandormajor@chromium.org> Reviewed-by: Maks Orlovich <morlovich@chromium.org> Cr-Commit-Position: refs/heads/main@{#1422574}
3704 lines
151 KiB
C++
3704 lines
151 KiB
C++
// Copyright 2018 The Chromium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "content/browser/back_forward_cache_browsertest.h"
|
|
|
|
#include <climits>
|
|
#include <optional>
|
|
#include <string_view>
|
|
#include <unordered_map>
|
|
|
|
#include "base/command_line.h"
|
|
#include "base/containers/contains.h"
|
|
#include "base/functional/callback_helpers.h"
|
|
#include "base/location.h"
|
|
#include "base/memory/raw_ptr.h"
|
|
#include "base/metrics/metrics_hashes.h"
|
|
#include "base/run_loop.h"
|
|
#include "base/system/sys_info.h"
|
|
#include "base/task/common/task_annotator.h"
|
|
#include "base/task/single_thread_task_runner.h"
|
|
#include "base/test/bind.h"
|
|
#include "base/test/metrics/histogram_tester.h"
|
|
#include "base/test/test_timeouts.h"
|
|
#include "base/threading/thread_restrictions.h"
|
|
#include "base/time/time.h"
|
|
#include "base/trace_event/trace_log.h"
|
|
#include "build/build_config.h"
|
|
#include "build/chromecast_buildflags.h"
|
|
#include "components/ukm/test_ukm_recorder.h"
|
|
#include "content/browser/bad_message.h"
|
|
#include "content/browser/renderer_host/back_forward_cache_can_store_document_result.h"
|
|
#include "content/browser/renderer_host/back_forward_cache_disable.h"
|
|
#include "content/browser/renderer_host/back_forward_cache_impl.h"
|
|
#include "content/browser/renderer_host/frame_tree_node.h"
|
|
#include "content/browser/renderer_host/render_frame_host_impl.h"
|
|
#include "content/browser/renderer_host/should_swap_browsing_instance.h"
|
|
#include "content/browser/web_contents/web_contents_impl.h"
|
|
#include "content/common/content_navigation_policy.h"
|
|
#include "content/common/features.h"
|
|
#include "content/public/browser/back_forward_cache.h"
|
|
#include "content/public/browser/document_service.h"
|
|
#include "content/public/browser/global_routing_id.h"
|
|
#include "content/public/browser/web_contents.h"
|
|
#include "content/public/common/content_features.h"
|
|
#include "content/public/common/content_switches.h"
|
|
#include "content/public/common/result_codes.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/commit_message_delayer.h"
|
|
#include "content/public/test/content_browser_test_utils.h"
|
|
#include "content/public/test/fenced_frame_test_util.h"
|
|
#include "content/public/test/navigation_handle_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/text_input_test_utils.h"
|
|
#include "content/public/test/url_loader_interceptor.h"
|
|
#include "content/public/test/web_contents_observer_test_utils.h"
|
|
#include "content/shell/browser/shell.h"
|
|
#include "content/shell/browser/shell_javascript_dialog_manager.h"
|
|
#include "content/test/content_browser_test_utils_internal.h"
|
|
#include "media/base/media_switches.h"
|
|
#include "mojo/public/cpp/bindings/message.h"
|
|
#include "mojo/public/cpp/bindings/pending_remote.h"
|
|
#include "mojo/public/cpp/bindings/remote.h"
|
|
#include "net/base/filename_util.h"
|
|
#include "net/dns/mock_host_resolver.h"
|
|
#include "net/test/embedded_test_server/controllable_http_response.h"
|
|
#include "net/test/embedded_test_server/embedded_test_server.h"
|
|
#include "services/metrics/public/cpp/ukm_recorder.h"
|
|
#include "services/metrics/public/cpp/ukm_source_id.h"
|
|
#include "services/network/public/cpp/features.h"
|
|
#include "services/service_manager/public/cpp/interface_provider.h"
|
|
#include "third_party/blink/public/common/device_memory/approximated_device_memory.h"
|
|
#include "third_party/blink/public/common/features.h"
|
|
#include "third_party/blink/public/common/features_generated.h"
|
|
#include "third_party/blink/public/common/scheduler/web_scheduler_tracked_feature.h"
|
|
#include "third_party/blink/public/common/switches.h"
|
|
#include "third_party/blink/public/mojom/back_forward_cache_not_restored_reasons.mojom.h"
|
|
#include "third_party/blink/public/mojom/frame/back_forward_cache_controller.mojom.h"
|
|
#include "third_party/blink/public/mojom/frame/frame.mojom.h"
|
|
#include "third_party/blink/public/mojom/render_accessibility.mojom.h"
|
|
#include "third_party/blink/public/mojom/script_source_location.mojom.h"
|
|
|
|
// This file has too many tests.
|
|
//
|
|
// Before adding new tests to this file, consider if they will fit better into
|
|
// one of the other back_forward_cache_*_browsertest.cc files or if there are
|
|
// enough new tests to justify a new file.
|
|
|
|
using testing::_;
|
|
using testing::Each;
|
|
using testing::ElementsAre;
|
|
using testing::Not;
|
|
using testing::UnorderedElementsAreArray;
|
|
|
|
namespace content {
|
|
|
|
using NotRestoredReasons =
|
|
BackForwardCacheCanStoreDocumentResult::NotRestoredReasons;
|
|
using NotRestoredReason = BackForwardCacheMetrics::NotRestoredReason;
|
|
|
|
namespace {
|
|
|
|
class DOMContentLoadedObserver : public WebContentsObserver {
|
|
public:
|
|
explicit DOMContentLoadedObserver(RenderFrameHostImpl* render_frame_host)
|
|
: WebContentsObserver(
|
|
WebContents::FromRenderFrameHost(render_frame_host)),
|
|
render_frame_host_(render_frame_host) {}
|
|
|
|
void DOMContentLoaded(RenderFrameHost* render_frame_host) override {
|
|
if (render_frame_host_ == render_frame_host)
|
|
run_loop_.Quit();
|
|
}
|
|
|
|
[[nodiscard]] bool Wait() {
|
|
if (render_frame_host_->IsDOMContentLoaded())
|
|
run_loop_.Quit();
|
|
run_loop_.Run();
|
|
return render_frame_host_->IsDOMContentLoaded();
|
|
}
|
|
|
|
private:
|
|
raw_ptr<RenderFrameHostImpl> render_frame_host_;
|
|
base::RunLoop run_loop_;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
bool WaitForDOMContentLoaded(RenderFrameHostImpl* rfh) {
|
|
DOMContentLoadedObserver observer(rfh);
|
|
return observer.Wait();
|
|
}
|
|
|
|
EvalJsResult GetLocalStorage(RenderFrameHostImpl* rfh, std::string key) {
|
|
return EvalJs(rfh, JsReplace("localStorage.getItem($1)", key));
|
|
}
|
|
|
|
[[nodiscard]] bool WaitForLocalStorage(RenderFrameHostImpl* rfh,
|
|
std::string key,
|
|
std::string expected_value) {
|
|
auto value = EvalJs(rfh, JsReplace(R"(
|
|
new Promise((resolve) => {
|
|
let key = $1;
|
|
let expected_value = $2;
|
|
if (localStorage.getItem(key) == expected_value) {
|
|
resolve(localStorage.getItem(key));
|
|
return;
|
|
}
|
|
let listener = window.addEventListener("storage", e => {
|
|
if (e.storageArea == localStorage && e.key == key
|
|
&& e.newValue == expected_value) {
|
|
resolve(localStorage.getItem(key));
|
|
removeEventListener("storage", listener);
|
|
return;
|
|
}
|
|
});
|
|
});
|
|
)",
|
|
key, expected_value));
|
|
return value == expected_value;
|
|
}
|
|
|
|
BackForwardCacheBrowserTest::BackForwardCacheBrowserTest() = default;
|
|
|
|
BackForwardCacheBrowserTest::~BackForwardCacheBrowserTest() {
|
|
if (fail_for_unexpected_messages_while_cached_) {
|
|
// If this is triggered, see MojoInterfaceName in
|
|
// tools/metrics/histograms/metadata/navigation/enums.xml for which values
|
|
// correspond which messages.
|
|
std::vector<base::Bucket> samples = histogram_tester().GetAllSamples(
|
|
"BackForwardCache.UnexpectedRendererToBrowserMessage."
|
|
"InterfaceName");
|
|
// TODO(crbug.com/40244391): Remove this.
|
|
// This bucket corresponds to the LocalFrameHost interface. It is known to
|
|
// be flaky due calls to `LocalFrameHost::DidFocusFrame()` after entering
|
|
// BFCache. So we ignore it for now by removing it if it's present until we
|
|
// can fix the root cause.
|
|
// TODO(crbug.com/40925798): Remove this.
|
|
// As above but `LocalMainFrameHost::DidFirstVisuallyNonEmptyPaint()`.
|
|
std::erase_if(samples, [](base::Bucket bucket) {
|
|
return bucket.min ==
|
|
static_cast<base::HistogramBase::Sample32>(base::HashMetricName(
|
|
blink::mojom::LocalFrameHost::Name_)) ||
|
|
bucket.min ==
|
|
static_cast<base::HistogramBase::Sample32>(base::HashMetricName(
|
|
blink::mojom::LocalMainFrameHost::Name_));
|
|
});
|
|
|
|
EXPECT_THAT(samples, testing::ElementsAre());
|
|
}
|
|
}
|
|
|
|
void BackForwardCacheBrowserTest::NotifyNotRestoredReasons(
|
|
std::unique_ptr<BackForwardCacheCanStoreTreeResult> tree_result) {
|
|
tree_result_ = std::move(tree_result);
|
|
}
|
|
|
|
void BackForwardCacheBrowserTest::SetUpCommandLine(
|
|
base::CommandLine* command_line) {
|
|
mock_cert_verifier_.SetUpCommandLine(command_line);
|
|
|
|
command_line->AppendSwitch(switches::kUseFakeUIForMediaStream);
|
|
command_line->AppendSwitch(switches::kEnableExperimentalWebPlatformFeatures);
|
|
// TODO(sreejakshetty): Initialize ScopedFeatureLists from test constructor.
|
|
EnableFeatureAndSetParams(features::kBackForwardCacheTimeToLiveControl,
|
|
"time_to_live_seconds", "3600");
|
|
// Entry to the cache can be slow during testing and cause flakiness.
|
|
DisableFeature(features::kBackForwardCacheEntryTimeout);
|
|
EnableFeatureAndSetParams(features::kBackForwardCache,
|
|
"message_handling_when_cached", "log");
|
|
EnableFeatureAndSetParams(
|
|
blink::features::kLogUnexpectedIPCPostedToBackForwardCachedDocuments,
|
|
"delay_before_tracking_ms", "0");
|
|
// Allow unlimited network during tests. Override this if you want to test the
|
|
// network limiting.
|
|
EnableFeatureAndSetParams(blink::features::kLoadingTasksUnfreezable,
|
|
"max_buffered_bytes_per_process",
|
|
base::NumberToString(INT_MAX));
|
|
EnableFeatureAndSetParams(blink::features::kLoadingTasksUnfreezable,
|
|
"grace_period_to_finish_loading_in_seconds",
|
|
base::NumberToString(INT_MAX));
|
|
// Enable capturing not-restored-reasons tree.
|
|
EnableFeatureAndSetParams(
|
|
blink::features::kBackForwardCacheSendNotRestoredReasons, "", "");
|
|
|
|
// Do not trigger DumpWithoutCrashing() for JavaScript execution.
|
|
DisableFeature(blink::features::kBackForwardCacheDWCOnJavaScriptExecution);
|
|
#if BUILDFLAG(IS_ANDROID)
|
|
EnableFeatureAndSetParams(features::kBackForwardCache,
|
|
"process_binding_strength", "NORMAL");
|
|
#endif
|
|
// Allow BackForwardCache for all devices regardless of their memory.
|
|
DisableFeature(features::kBackForwardCacheMemoryControls);
|
|
// Disables BackForwardCache cache size overwritten by
|
|
// `content::kBackForwardCacheSize`, as many browser tests here assume
|
|
// specific or smaller cache size (e.g. 1) rather than 6.
|
|
DisableFeature(kBackForwardCacheSize);
|
|
|
|
// WebSQL is disabled by default as of M119 (crbug/695592). Enable feature
|
|
// in tests during deprecation trial and enterprise policy support.
|
|
EnableFeatureAndSetParams(blink::features::kWebSQLAccess, "", "");
|
|
|
|
SetupFeaturesAndParameters();
|
|
|
|
command_line->AppendSwitchASCII(
|
|
switches::kAutoplayPolicy,
|
|
switches::autoplay::kNoUserGestureRequiredPolicy);
|
|
// Unfortunately needed for one test on slow bots, TextInputStateUpdated,
|
|
// where deferred commits delays input too much.
|
|
command_line->AppendSwitch(blink::switches::kAllowPreCommitInput);
|
|
}
|
|
|
|
void BackForwardCacheBrowserTest::SetUpInProcessBrowserTestFixture() {
|
|
ContentBrowserTest::SetUpInProcessBrowserTestFixture();
|
|
mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
|
|
}
|
|
|
|
void BackForwardCacheBrowserTest::TearDownInProcessBrowserTestFixture() {
|
|
ContentBrowserTest::TearDownInProcessBrowserTestFixture();
|
|
mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
|
|
}
|
|
|
|
void BackForwardCacheBrowserTest::SetupFeaturesAndParameters() {
|
|
std::vector<base::test::FeatureRefAndParams> enabled_features;
|
|
|
|
for (const auto& [feature_ref, params] : features_with_params_) {
|
|
enabled_features.emplace_back(*feature_ref, params);
|
|
}
|
|
|
|
feature_list_.InitWithFeaturesAndParameters(enabled_features,
|
|
disabled_features_);
|
|
vmodule_switches_.InitWithSwitches("back_forward_cache_impl=1");
|
|
}
|
|
|
|
void BackForwardCacheBrowserTest::EnableFeatureAndSetParams(
|
|
const base::Feature& feature,
|
|
std::string param_name,
|
|
std::string param_value) {
|
|
features_with_params_[feature][param_name] = param_value;
|
|
}
|
|
|
|
void BackForwardCacheBrowserTest::DisableFeature(const base::Feature& feature) {
|
|
disabled_features_.push_back(feature);
|
|
}
|
|
|
|
void BackForwardCacheBrowserTest::SetUpOnMainThread() {
|
|
mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
|
|
host_resolver()->AddRule("*", "127.0.0.1");
|
|
// TestAutoSetUkmRecorder's constructor requires a sequenced context.
|
|
ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
|
|
histogram_tester_ = std::make_unique<base::HistogramTester>();
|
|
ContentBrowserTest::SetUpOnMainThread();
|
|
}
|
|
|
|
void BackForwardCacheBrowserTest::TearDownOnMainThread() {
|
|
ukm_recorder_.reset();
|
|
ContentBrowserTest::TearDownOnMainThread();
|
|
}
|
|
|
|
WebContentsImpl* BackForwardCacheBrowserTest::web_contents() const {
|
|
return static_cast<WebContentsImpl*>(shell()->web_contents());
|
|
}
|
|
|
|
RenderFrameHostImpl* BackForwardCacheBrowserTest::current_frame_host() {
|
|
return web_contents()->GetPrimaryFrameTree().root()->current_frame_host();
|
|
}
|
|
|
|
RenderFrameHostManager*
|
|
BackForwardCacheBrowserTest::render_frame_host_manager() {
|
|
return web_contents()->GetPrimaryFrameTree().root()->render_manager();
|
|
}
|
|
|
|
std::string BackForwardCacheBrowserTest::DepictFrameTree(FrameTreeNode* node) {
|
|
return visualizer_.DepictFrameTree(node);
|
|
}
|
|
|
|
bool BackForwardCacheBrowserTest::HistogramContainsIntValue(
|
|
base::HistogramBase::Sample32 sample,
|
|
std::vector<base::Bucket> histogram_values) {
|
|
return base::Contains(histogram_values, static_cast<int>(sample),
|
|
&base::Bucket::min);
|
|
}
|
|
|
|
void BackForwardCacheBrowserTest::EvictByJavaScript(RenderFrameHostImpl* rfh) {
|
|
// Run JavaScript on a page in the back-forward cache. The page should be
|
|
// evicted. As the frame is deleted, ExecJs returns false without executing.
|
|
// Run without user gesture to prevent UpdateUserActivationState message
|
|
// being sent back to browser.
|
|
EXPECT_FALSE(
|
|
ExecJs(rfh, "console.log('hi');", EXECUTE_SCRIPT_NO_USER_GESTURE));
|
|
}
|
|
|
|
void BackForwardCacheBrowserTest::StartRecordingEvents(
|
|
RenderFrameHostImpl* rfh) {
|
|
EXPECT_TRUE(ExecJs(rfh, R"(
|
|
window.testObservedEvents = [];
|
|
let event_list = [
|
|
'visibilitychange',
|
|
'pagehide',
|
|
'pageshow',
|
|
'freeze',
|
|
'resume',
|
|
];
|
|
for (event_name of event_list) {
|
|
let result = event_name;
|
|
window.addEventListener(event_name, event => {
|
|
if (event.persisted)
|
|
result += '.persisted';
|
|
window.testObservedEvents.push('window.' + result);
|
|
});
|
|
document.addEventListener(event_name,
|
|
() => window.testObservedEvents.push('document.' + result));
|
|
}
|
|
)"));
|
|
}
|
|
|
|
void BackForwardCacheBrowserTest::MatchEventList(RenderFrameHostImpl* rfh,
|
|
base::Value list,
|
|
base::Location location) {
|
|
EXPECT_EQ(list, EvalJs(rfh, "window.testObservedEvents"))
|
|
<< location.ToString();
|
|
}
|
|
|
|
// Creates a minimal HTTPS server, accessible through https_server().
|
|
// Returns a pointer to the server.
|
|
net::EmbeddedTestServer* BackForwardCacheBrowserTest::CreateHttpsServer() {
|
|
https_server_ = std::make_unique<net::EmbeddedTestServer>(
|
|
net::EmbeddedTestServer::TYPE_HTTPS);
|
|
https_server_->AddDefaultHandlers(GetTestDataFilePath());
|
|
https_server_->SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
|
|
return https_server();
|
|
}
|
|
|
|
net::EmbeddedTestServer* BackForwardCacheBrowserTest::https_server() {
|
|
return https_server_.get();
|
|
}
|
|
|
|
// Do not fail this test if a message from a renderer arrives at the browser
|
|
// for a cached page.
|
|
void BackForwardCacheBrowserTest::DoNotFailForUnexpectedMessagesWhileCached() {
|
|
fail_for_unexpected_messages_while_cached_ = false;
|
|
}
|
|
|
|
// Navigates to a page at |page_url| with an img element with src set to
|
|
// "image.png".
|
|
RenderFrameHostImpl* BackForwardCacheBrowserTest::NavigateToPageWithImage(
|
|
const GURL& page_url) {
|
|
EXPECT_TRUE(NavigateToURL(shell(), page_url));
|
|
RenderFrameHostImpl* rfh = current_frame_host();
|
|
// Wait for the document to load DOM to ensure that kLoading is not
|
|
// one of the reasons why the document wasn't cached.
|
|
EXPECT_TRUE(WaitForDOMContentLoaded(rfh));
|
|
|
|
EXPECT_TRUE(ExecJs(rfh, R"(
|
|
var image = document.createElement("img");
|
|
image.src = "image.png";
|
|
document.body.appendChild(image);
|
|
|
|
var image_load_status = new Promise((resolve, reject) => {
|
|
image.onload = () => { resolve("loaded"); }
|
|
image.onerror = () => { resolve("error"); }
|
|
});
|
|
)"));
|
|
return rfh;
|
|
}
|
|
|
|
void BackForwardCacheBrowserTest::AcquireKeyboardLock(
|
|
RenderFrameHostImpl* rfh) {
|
|
EXPECT_EQ(42, EvalJs(rfh, R"(
|
|
new Promise(resolve => {
|
|
navigator.keyboard.lock();
|
|
resolve(42);
|
|
});
|
|
)"));
|
|
}
|
|
|
|
void BackForwardCacheBrowserTest::ReleaseKeyboardLock(
|
|
RenderFrameHostImpl* rfh) {
|
|
EXPECT_EQ(42, EvalJs(rfh, R"(
|
|
new Promise(resolve => {
|
|
navigator.keyboard.unlock();
|
|
resolve(42);
|
|
});
|
|
)"));
|
|
}
|
|
|
|
void BackForwardCacheBrowserTest::NavigateAndBlock(GURL url,
|
|
int history_offset) {
|
|
// Block the navigation with an error.
|
|
std::unique_ptr<URLLoaderInterceptor> url_interceptor =
|
|
URLLoaderInterceptor::SetupRequestFailForURL(url,
|
|
net::ERR_BLOCKED_BY_CLIENT);
|
|
if (history_offset) {
|
|
shell()->GoBackOrForward(history_offset);
|
|
} else {
|
|
shell()->LoadURL(url);
|
|
}
|
|
WaitForLoadStop(web_contents());
|
|
ASSERT_EQ(current_frame_host()->GetLastCommittedURL(), url);
|
|
ASSERT_TRUE(current_frame_host()->IsErrorDocument());
|
|
}
|
|
|
|
ReasonsMatcher BackForwardCacheBrowserTest::MatchesNotRestoredReasons(
|
|
const std::optional<testing::Matcher<std::string>>& id,
|
|
const std::optional<testing::Matcher<std::string>>& name,
|
|
const std::optional<testing::Matcher<std::string>>& src,
|
|
const std::vector<BlockingDetailsReasonsMatcher>& reasons,
|
|
const std::optional<SameOriginMatcher>& same_origin_details) {
|
|
// TODO(crbug.com/41496143) Make this matcher display human-friendly messages.
|
|
return testing::Pointee(testing::AllOf(
|
|
id.has_value()
|
|
? testing::Field(
|
|
"id", &blink::mojom::BackForwardCacheNotRestoredReasons::id,
|
|
testing::Optional(id.value()))
|
|
: testing::Field(
|
|
"id", &blink::mojom::BackForwardCacheNotRestoredReasons::id,
|
|
std::optional<std::string>(std::nullopt)),
|
|
name.has_value()
|
|
? testing::Field(
|
|
"name", &blink::mojom::BackForwardCacheNotRestoredReasons::name,
|
|
testing::Optional(name.value()))
|
|
: testing::Field(
|
|
"name", &blink::mojom::BackForwardCacheNotRestoredReasons::name,
|
|
std::optional<std::string>(std::nullopt)),
|
|
src.has_value()
|
|
? testing::Field(
|
|
"src", &blink::mojom::BackForwardCacheNotRestoredReasons::src,
|
|
testing::Optional(src.value()))
|
|
: testing::Field(
|
|
"src", &blink::mojom::BackForwardCacheNotRestoredReasons::src,
|
|
std::optional<std::string>(std::nullopt)),
|
|
testing::Field("reasons",
|
|
&blink::mojom::BackForwardCacheNotRestoredReasons::reasons,
|
|
testing::UnorderedElementsAreArray(reasons)),
|
|
testing::Field(
|
|
"same_origin_details",
|
|
&blink::mojom::BackForwardCacheNotRestoredReasons::
|
|
same_origin_details,
|
|
same_origin_details.has_value()
|
|
? same_origin_details.value()
|
|
: testing::Property(
|
|
"is_null",
|
|
&blink::mojom::SameOriginBfcacheNotRestoredDetailsPtr::
|
|
is_null,
|
|
true))));
|
|
}
|
|
|
|
SameOriginMatcher BackForwardCacheBrowserTest::MatchesSameOriginDetails(
|
|
const testing::Matcher<GURL>& url,
|
|
const std::vector<ReasonsMatcher>& children) {
|
|
// TODO(crbug.com/41496143) Make this matcher display human-friendly messages.
|
|
return testing::Pointee(testing::AllOf(
|
|
testing::Field(
|
|
"url", &blink::mojom::SameOriginBfcacheNotRestoredDetails::url, url),
|
|
testing::Field(
|
|
"children",
|
|
&blink::mojom::SameOriginBfcacheNotRestoredDetails::children,
|
|
testing::ElementsAreArray(children))));
|
|
}
|
|
|
|
BlockingDetailsReasonsMatcher
|
|
BackForwardCacheBrowserTest::MatchesDetailedReason(
|
|
const testing::Matcher<std::string>& name,
|
|
const std::optional<SourceLocationMatcher>& source) {
|
|
// TODO(crbug.com/41496143) Make this matcher display human-friendly
|
|
// messages.
|
|
return testing::Pointee(testing::AllOf(
|
|
testing::Field("name", &blink::mojom::BFCacheBlockingDetailedReason::name,
|
|
name),
|
|
testing::Field(
|
|
"source", &blink::mojom::BFCacheBlockingDetailedReason::source,
|
|
source.has_value()
|
|
? source.value()
|
|
: testing::Property(
|
|
"is_null", &blink::mojom::ScriptSourceLocationPtr::is_null,
|
|
true))));
|
|
}
|
|
|
|
BlockingDetailsMatcher BackForwardCacheBrowserTest::MatchesBlockingDetails(
|
|
const std::optional<SourceLocationMatcher>& source) {
|
|
// TODO(crbug.com/41496143) Make this matcher display human-friendly messages.
|
|
return testing::Pointee(testing::Field(
|
|
"source", &blink::mojom::BlockingDetails::source,
|
|
source.has_value()
|
|
? source.value()
|
|
: testing::Property("is_null",
|
|
&blink::mojom::ScriptSourceLocationPtr::is_null,
|
|
true)));
|
|
}
|
|
|
|
SourceLocationMatcher BackForwardCacheBrowserTest::MatchesSourceLocation(
|
|
const testing::Matcher<GURL>& url,
|
|
const testing::Matcher<std::string>& function_name,
|
|
const testing::Matcher<uint64_t>& line_number,
|
|
const testing::Matcher<uint64_t>& column_number) {
|
|
// TODO(crbug.com/41496143) Make this matcher display human-friendly
|
|
// messages.
|
|
return testing::Pointee(testing::AllOf(
|
|
testing::Field("url", &blink::mojom::ScriptSourceLocation::url, url),
|
|
testing::Field("function_name",
|
|
&blink::mojom::ScriptSourceLocation::function_name,
|
|
function_name),
|
|
testing::Field("line_number",
|
|
&blink::mojom::ScriptSourceLocation::line_number,
|
|
line_number),
|
|
testing::Field("column_number",
|
|
&blink::mojom::ScriptSourceLocation::column_number,
|
|
column_number)));
|
|
}
|
|
|
|
void BackForwardCacheUnloadBrowserTest::SetUpCommandLine(
|
|
base::CommandLine* command_line) {
|
|
BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
|
|
scoped_feature_list_.InitAndEnableFeature(kBackForwardCacheUnloadAllowed);
|
|
}
|
|
|
|
std::initializer_list<RenderFrameHostImpl*> Elements(
|
|
std::initializer_list<RenderFrameHostImpl*> t) {
|
|
return t;
|
|
}
|
|
|
|
// Execute a custom callback when navigation is ready to commit. This is
|
|
// useful for simulating race conditions happening when a page enters the
|
|
// BackForwardCache and receive inflight messages sent when it wasn't frozen
|
|
// yet.
|
|
class ReadyToCommitNavigationCallback : public WebContentsObserver {
|
|
public:
|
|
ReadyToCommitNavigationCallback(
|
|
WebContents* content,
|
|
base::OnceCallback<void(NavigationHandle*)> callback)
|
|
: WebContentsObserver(content), callback_(std::move(callback)) {}
|
|
|
|
ReadyToCommitNavigationCallback(const ReadyToCommitNavigationCallback&) =
|
|
delete;
|
|
ReadyToCommitNavigationCallback& operator=(
|
|
const ReadyToCommitNavigationCallback&) = delete;
|
|
|
|
private:
|
|
// WebContentsObserver:
|
|
void ReadyToCommitNavigation(NavigationHandle* navigation_handle) override {
|
|
if (callback_)
|
|
std::move(callback_).Run(navigation_handle);
|
|
}
|
|
|
|
base::OnceCallback<void(NavigationHandle*)> callback_;
|
|
};
|
|
|
|
class FirstVisuallyNonEmptyPaintObserver : public WebContentsObserver {
|
|
public:
|
|
explicit FirstVisuallyNonEmptyPaintObserver(WebContents* contents)
|
|
: WebContentsObserver(contents) {}
|
|
void DidFirstVisuallyNonEmptyPaint() override {
|
|
if (observed_)
|
|
return;
|
|
observed_ = true;
|
|
run_loop_.Quit();
|
|
}
|
|
|
|
bool did_fire() const { return observed_; }
|
|
|
|
void Wait() { run_loop_.Run(); }
|
|
|
|
private:
|
|
bool observed_ = false;
|
|
base::RunLoop run_loop_{base::RunLoop::Type::kNestableTasksAllowed};
|
|
};
|
|
|
|
void WaitForFirstVisuallyNonEmptyPaint(WebContents* contents) {
|
|
if (contents->CompletedFirstVisuallyNonEmptyPaint())
|
|
return;
|
|
FirstVisuallyNonEmptyPaintObserver observer(contents);
|
|
observer.Wait();
|
|
}
|
|
|
|
class ThemeColorObserver : public WebContentsObserver {
|
|
public:
|
|
explicit ThemeColorObserver(WebContents* contents)
|
|
: WebContentsObserver(contents) {}
|
|
|
|
// Can only be called once.
|
|
[[nodiscard]] bool WaitUntilThemeColorChange() {
|
|
CHECK(!loop_);
|
|
loop_ = std::make_unique<base::RunLoop>();
|
|
if (observed_) {
|
|
return true;
|
|
}
|
|
loop_->Run();
|
|
return observed_;
|
|
}
|
|
|
|
void DidChangeThemeColor() override {
|
|
observed_ = true;
|
|
if (loop_) {
|
|
loop_->Quit();
|
|
}
|
|
}
|
|
|
|
bool did_fire() const { return observed_; }
|
|
|
|
private:
|
|
std::unique_ptr<base::RunLoop> loop_;
|
|
bool observed_ = false;
|
|
};
|
|
|
|
PageLifecycleStateManagerTestDelegate::PageLifecycleStateManagerTestDelegate(
|
|
PageLifecycleStateManager* manager)
|
|
: manager_(manager) {
|
|
manager->SetDelegateForTesting(this);
|
|
}
|
|
|
|
PageLifecycleStateManagerTestDelegate::
|
|
~PageLifecycleStateManagerTestDelegate() {
|
|
if (manager_)
|
|
manager_->SetDelegateForTesting(nullptr);
|
|
}
|
|
|
|
bool PageLifecycleStateManagerTestDelegate::WaitForInBackForwardCacheAck() {
|
|
DCHECK(manager_);
|
|
if (manager_->last_acknowledged_state().is_in_back_forward_cache) {
|
|
return true;
|
|
}
|
|
base::RunLoop loop;
|
|
store_in_back_forward_cache_ack_received_ = loop.QuitClosure();
|
|
loop.Run();
|
|
return manager_->last_acknowledged_state().is_in_back_forward_cache;
|
|
}
|
|
|
|
void PageLifecycleStateManagerTestDelegate::OnStoreInBackForwardCacheSent(
|
|
base::OnceClosure cb) {
|
|
store_in_back_forward_cache_sent_ = std::move(cb);
|
|
}
|
|
|
|
void PageLifecycleStateManagerTestDelegate::OnDisableJsEvictionSent(
|
|
base::OnceClosure cb) {
|
|
disable_eviction_sent_ = std::move(cb);
|
|
}
|
|
|
|
void PageLifecycleStateManagerTestDelegate::OnRestoreFromBackForwardCacheSent(
|
|
base::OnceClosure cb) {
|
|
restore_from_back_forward_cache_sent_ = std::move(cb);
|
|
}
|
|
|
|
void PageLifecycleStateManagerTestDelegate::OnLastAcknowledgedStateChanged(
|
|
const blink::mojom::PageLifecycleState& old_state,
|
|
const blink::mojom::PageLifecycleState& new_state) {
|
|
if (store_in_back_forward_cache_ack_received_ &&
|
|
new_state.is_in_back_forward_cache)
|
|
std::move(store_in_back_forward_cache_ack_received_).Run();
|
|
}
|
|
|
|
void PageLifecycleStateManagerTestDelegate::OnUpdateSentToRenderer(
|
|
const blink::mojom::PageLifecycleState& new_state) {
|
|
if (store_in_back_forward_cache_sent_ && new_state.is_in_back_forward_cache) {
|
|
std::move(store_in_back_forward_cache_sent_).Run();
|
|
}
|
|
|
|
if (disable_eviction_sent_ && new_state.eviction_enabled == false) {
|
|
std::move(disable_eviction_sent_).Run();
|
|
}
|
|
|
|
if (restore_from_back_forward_cache_sent_ &&
|
|
!new_state.is_in_back_forward_cache) {
|
|
std::move(restore_from_back_forward_cache_sent_).Run();
|
|
}
|
|
}
|
|
|
|
void PageLifecycleStateManagerTestDelegate::OnDeleted() {
|
|
manager_ = nullptr;
|
|
}
|
|
|
|
// Check the visible URL in the omnibox is properly updated when restoring a
|
|
// document from the BackForwardCache.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, VisibleURL) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// 1) Go to A.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
|
|
// 2) Go to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
// 3) Go back to A.
|
|
ASSERT_TRUE(HistoryGoBack(web_contents()));
|
|
EXPECT_EQ(url_a, web_contents()->GetVisibleURL());
|
|
|
|
// 4) Go forward to B.
|
|
ASSERT_TRUE(HistoryGoForward(web_contents()));
|
|
EXPECT_EQ(url_b, web_contents()->GetVisibleURL());
|
|
}
|
|
|
|
// Test only 1 document is kept in the at a time BackForwardCache.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
CacheSizeLimitedToOneDocumentPerTab) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
GURL url_c(embedded_test_server()->GetURL("c.com", "/title1.html"));
|
|
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
// BackForwardCache is empty.
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
// BackForwardCache contains only rfh_a.
|
|
RenderFrameHostImpl* rfh_b = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b);
|
|
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_c));
|
|
// BackForwardCache contains only rfh_b.
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
EXPECT_FALSE(delete_observer_rfh_b.deleted());
|
|
|
|
// If/when the cache size is increased, this can be tested iteratively, see
|
|
// deleted code in: https://crrev.com/c/1782902.
|
|
|
|
ASSERT_TRUE(HistoryGoToOffset(web_contents(), -2));
|
|
ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason::kCacheLimit},
|
|
{}, {}, {}, {}, FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, ResponseHeaders) {
|
|
CreateHttpsServer();
|
|
ASSERT_TRUE(https_server()->Start());
|
|
|
|
GURL url_a(https_server()->GetURL("a.test", "/set-header?X-Foo: bar"));
|
|
GURL url_b(https_server()->GetURL("b.test", "/title1.html"));
|
|
|
|
// 1) Navigate to A.
|
|
NavigationHandleObserver observer1(web_contents(), url_a);
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
EXPECT_TRUE(observer1.has_committed());
|
|
EXPECT_EQ("bar", observer1.GetNormalizedResponseHeader("x-foo"));
|
|
|
|
// 2) Navigate to B.
|
|
NavigationHandleObserver observer2(web_contents(), url_b);
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
RenderFrameHostImpl* rfh_b = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b);
|
|
EXPECT_FALSE(delete_observer_rfh_a.deleted());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
EXPECT_FALSE(rfh_b->IsInBackForwardCache());
|
|
EXPECT_TRUE(observer2.has_committed());
|
|
|
|
// 3) Go back to A.
|
|
NavigationHandleObserver observer3(web_contents(), url_a);
|
|
ASSERT_TRUE(HistoryGoBack(web_contents()));
|
|
EXPECT_FALSE(delete_observer_rfh_a.deleted());
|
|
EXPECT_FALSE(delete_observer_rfh_b.deleted());
|
|
EXPECT_EQ(rfh_a, current_frame_host());
|
|
EXPECT_FALSE(rfh_a->IsInBackForwardCache());
|
|
EXPECT_TRUE(rfh_b->IsInBackForwardCache());
|
|
EXPECT_TRUE(observer3.has_committed());
|
|
EXPECT_EQ("bar", observer3.GetNormalizedResponseHeader("x-foo"));
|
|
|
|
ExpectRestored(FROM_HERE);
|
|
}
|
|
|
|
void HighCacheSizeBackForwardCacheBrowserTest::SetUpCommandLine(
|
|
base::CommandLine* command_line) {
|
|
EnableFeatureAndSetParams(features::kBackForwardCache, "cache_size",
|
|
base::NumberToString(kBackForwardCacheSize));
|
|
BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
|
|
}
|
|
|
|
// Test documents are evicted from the BackForwardCache at some point.
|
|
IN_PROC_BROWSER_TEST_F(HighCacheSizeBackForwardCacheBrowserTest,
|
|
CacheEvictionWithIncreasedCacheSize) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a)); // BackForwardCache size is 0.
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b)); // BackForwardCache size is 1.
|
|
RenderFrameHostImpl* rfh_b = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b);
|
|
|
|
for (size_t i = 2; i < kBackForwardCacheSize; ++i) {
|
|
EXPECT_TRUE(NavigateToURL(shell(), i % 2 ? url_b : url_a));
|
|
// After |i+1| navigations, |i| documents went into the BackForwardCache.
|
|
// When |i| is greater than the BackForwardCache size limit, they are
|
|
// evicted:
|
|
EXPECT_EQ(i >= kBackForwardCacheSize + 1, delete_observer_rfh_a.deleted());
|
|
EXPECT_EQ(i >= kBackForwardCacheSize + 2, delete_observer_rfh_b.deleted());
|
|
}
|
|
}
|
|
|
|
// Tests that evicting a page in between the time the back/forward cache
|
|
// NavigationRequest restore was created and when the NavigationRequest actually
|
|
// starts after finishing beforeunload won't result in a crash.
|
|
// See https://crbug.com/1218114.
|
|
IN_PROC_BROWSER_TEST_F(HighCacheSizeBackForwardCacheBrowserTest,
|
|
EvictedWhileWaitingForBeforeUnload) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title2.html"));
|
|
GURL url_c(embedded_test_server()->GetURL("c.com", "/title3.html"));
|
|
|
|
// 1) Navigate to A.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImplWrapper rfh_a(current_frame_host());
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
RenderFrameHostImplWrapper rfh_b(current_frame_host());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
|
|
// 3) Navigate to C, which has a beforeunload handler that never finishes.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_c));
|
|
RenderFrameHostImplWrapper rfh_c(current_frame_host());
|
|
EXPECT_TRUE(ExecJs(rfh_c.get(), R"(
|
|
window.onbeforeunload = () => {
|
|
while (true) {}
|
|
}
|
|
)"));
|
|
// Both A & B are in the back/forward cache.
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
EXPECT_TRUE(rfh_b->IsInBackForwardCache());
|
|
|
|
// 4) Evict entry A. This will post a task that destroys all evicted entries
|
|
// when it runs (task #1).
|
|
DisableBFCacheForRFHForTesting(rfh_a->GetGlobalId());
|
|
EXPECT_FALSE(rfh_a.IsDestroyed());
|
|
EXPECT_TRUE(rfh_a->is_evicted_from_back_forward_cache());
|
|
|
|
// 5) Trigger a back navigation to B. This will create a BFCache restore
|
|
// navigation to B, but will wait for C's beforeunload handler to finish
|
|
// running before continuing.
|
|
// The BFCache entry will be evicted before the back navigation completes, so
|
|
// the old navigation will be reset and a new navigation will be restarted.
|
|
// This observer is waiting for the two navigation requests to complete.
|
|
TestNavigationObserver observer(web_contents(),
|
|
/* expected_number_of_navigations= */ 2,
|
|
MessageLoopRunner::QuitMode::IMMEDIATE,
|
|
/* ignore_uncommitted_navigations= */ false);
|
|
web_contents()->GetController().GoBack();
|
|
|
|
// 6) Post a task to run BeforeUnloadCompleted (task #2). This will continue
|
|
// the BFCache restore navigation to B from step 5, which is currently waiting
|
|
// for a BeforeUnloadCompleted call.
|
|
FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
|
|
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
|
|
FROM_HERE, base::BindLambdaForTesting([&]() {
|
|
root->navigator().BeforeUnloadCompleted(
|
|
root, /*proceed=*/true, base::TimeTicks::Now(),
|
|
/*for_legacy=*/false, /*showed_dialog=*/false);
|
|
}));
|
|
|
|
// 7) Evict entry B. This will post a task (task #3) to restart the navigation
|
|
// to B, and also another task (task #4) to destroy all evicted entries.
|
|
DisableBFCacheForRFHForTesting(rfh_b->GetGlobalId());
|
|
EXPECT_FALSE(rfh_b.IsDestroyed());
|
|
EXPECT_TRUE(rfh_b->is_evicted_from_back_forward_cache());
|
|
|
|
// 8) Wait until the back navigation to B finishes. This will run posted tasks
|
|
// in order. So:
|
|
// - Task #1 from step 4 will run and destroy all evicted entries. As both the
|
|
// entries for A & B have been evicted, they are both destroyed.
|
|
// - Task #2 from step 6 will run and continue the back/forward cache restore
|
|
// NavigationRequest to B. However, it would notice that the entry for B is
|
|
// now gone, and should handle it gracefully.
|
|
// - Task #3 from step 7 to restart navigation to B runs, and should create a
|
|
// NavigationRequest to replace the previous NavigationRequest to B.
|
|
// - Task #4 from step 7 to destroy evicted entries runs and won't destroy
|
|
// any entry since there's no longer any entry in the back/forward cache.
|
|
observer.Wait();
|
|
EXPECT_EQ(web_contents()->GetLastCommittedURL(), url_b);
|
|
ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason::
|
|
kDisableForRenderFrameHostCalled},
|
|
{}, {}, {RenderFrameHostDisabledForTestingReason()}, {},
|
|
FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
SubframeWithOngoingNavigationNotCached) {
|
|
net::test_server::ControllableHttpResponse response(embedded_test_server(),
|
|
"/hung");
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
// Navigate to a page with an iframe.
|
|
TestNavigationObserver navigation_observer1(web_contents());
|
|
GURL main_url(embedded_test_server()->GetURL(
|
|
"a.com", "/back_forward_cache/page_with_hung_iframe.html"));
|
|
shell()->LoadURL(main_url);
|
|
navigation_observer1.WaitForNavigationFinished();
|
|
|
|
RenderFrameHostImpl* main_frame = current_frame_host();
|
|
RenderFrameDeletedObserver frame_deleted_observer(main_frame);
|
|
response.WaitForRequest();
|
|
|
|
// Navigate away.
|
|
TestNavigationObserver navigation_observer2(web_contents());
|
|
shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
navigation_observer2.WaitForNavigationFinished();
|
|
|
|
// The page with the unsupported feature should be deleted (not cached).
|
|
frame_deleted_observer.WaitUntilDeleted();
|
|
}
|
|
|
|
// Only HTTP/HTTPS main document can enter the BackForwardCache.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, CacheHTTPDocumentOnly) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
ASSERT_TRUE(CreateHttpsServer()->Start());
|
|
|
|
GURL http_url(embedded_test_server()->GetURL("a.test", "/title1.html"));
|
|
GURL https_url(https_server()->GetURL("a.test", "/title1.html"));
|
|
GURL file_url = net::FilePathToFileURL(GetTestFilePath("", "title1.html"));
|
|
GURL data_url = GURL("data:text/html,");
|
|
GURL blank_url = GURL(url::kAboutBlankURL);
|
|
GURL webui_url = GetWebUIURL("gpu");
|
|
|
|
enum { STORED, DELETED };
|
|
struct {
|
|
int expectation;
|
|
GURL url;
|
|
} test_cases[] = {
|
|
// Only document with HTTP/HTTPS URLs are allowed to enter the
|
|
// BackForwardCache.
|
|
{STORED, http_url},
|
|
{STORED, https_url},
|
|
|
|
// Others aren't allowed.
|
|
{DELETED, file_url},
|
|
{DELETED, data_url},
|
|
{DELETED, webui_url},
|
|
{DELETED, blank_url},
|
|
};
|
|
|
|
char hostname[] = "a.unique";
|
|
for (auto& test_case : test_cases) {
|
|
SCOPED_TRACE(testing::Message()
|
|
<< std::endl
|
|
<< "expectation = " << test_case.expectation << std::endl
|
|
<< "url = " << test_case.url << std::endl);
|
|
|
|
// 1) Navigate to.
|
|
EXPECT_TRUE(NavigateToURL(shell(), test_case.url));
|
|
RenderFrameHostImplWrapper rfh(current_frame_host());
|
|
|
|
// 2) Navigate away.
|
|
hostname[0]++;
|
|
GURL reset_url(embedded_test_server()->GetURL(hostname, "/title1.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), reset_url));
|
|
|
|
if (test_case.expectation == STORED) {
|
|
EXPECT_FALSE(rfh.IsRenderFrameDeleted());
|
|
EXPECT_TRUE(rfh->IsInBackForwardCache());
|
|
continue;
|
|
}
|
|
|
|
if (rfh.get() == current_frame_host()) {
|
|
// If the RenderFrameHost is reused, it won't be deleted, so don't wait
|
|
// for deletion. Just check that it's not saved in the back-forward cache.
|
|
EXPECT_FALSE(rfh.IsRenderFrameDeleted());
|
|
EXPECT_FALSE(rfh->IsInBackForwardCache());
|
|
continue;
|
|
}
|
|
|
|
// When the RenderFrameHost is not reused and it's not stored in the
|
|
// back-forward cache, it will eventually be deleted.
|
|
ASSERT_TRUE(rfh.WaitUntilRenderFrameDeleted());
|
|
}
|
|
}
|
|
|
|
// Regression test for https://crbug.com/993337.
|
|
//
|
|
// A note about sharing BrowsingInstances and the BackForwardCache:
|
|
//
|
|
// We should never keep around more than one main frame that belongs to the same
|
|
// BrowsingInstance. When swapping two pages, when one is stored in the
|
|
// back-forward cache or one is restored from it, the current code expects the
|
|
// two to live in different BrowsingInstances.
|
|
//
|
|
// History navigation can recreate a page with the same BrowsingInstance as the
|
|
// one stored in the back-forward cache. This case must to be handled. When it
|
|
// happens, the back-forward cache page is evicted.
|
|
//
|
|
// Since cache eviction is asynchronous, it's is possible for two main frames
|
|
// belonging to the same BrowsingInstance to be alive for a brief period of time
|
|
// (the new page being navigated to, and a page in the cache, until it is
|
|
// destroyed asynchronously via eviction).
|
|
//
|
|
// The test below tests that the brief period of time where two main frames are
|
|
// alive in the same BrowsingInstance does not cause anything to blow up.
|
|
|
|
// TODO(crbug.com/1127979, crbug.com/1446206): Flaky on Linux, Windows and
|
|
// ChromeOS, iOS, and Mac.
|
|
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_CHROMEOS) || \
|
|
BUILDFLAG(IS_MAC) || BUILDFLAG(IS_IOS)
|
|
#define MAYBE_NavigateToTwoPagesOnSameSite DISABLED_NavigateToTwoPagesOnSameSite
|
|
#else
|
|
#define MAYBE_NavigateToTwoPagesOnSameSite NavigateToTwoPagesOnSameSite
|
|
#endif
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
MAYBE_NavigateToTwoPagesOnSameSite) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a1(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_a2(embedded_test_server()->GetURL("a.com", "/title2.html"));
|
|
GURL url_b3(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// 1) Navigate to A1.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a1));
|
|
|
|
// 2) Navigate to A2.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a2));
|
|
RenderFrameHostImpl* rfh_a2 = current_frame_host();
|
|
RenderFrameDeletedObserver delete_rfh_a2(current_frame_host());
|
|
|
|
// 3) Navigate to B3.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b3));
|
|
EXPECT_TRUE(rfh_a2->IsInBackForwardCache());
|
|
RenderFrameHostImpl* rfh_b3 = current_frame_host();
|
|
|
|
// 4) Do a history navigation back to A1.
|
|
ASSERT_TRUE(HistoryGoToIndex(web_contents(), 0));
|
|
EXPECT_TRUE(rfh_b3->IsInBackForwardCache());
|
|
|
|
// Note that the frame for A1 gets created before A2 is deleted from the
|
|
// cache, so there will be a brief period where two the main frames (A1 and
|
|
// A2) are alive in the same BrowsingInstance/SiteInstance, at the same time.
|
|
// That is the scenario this test is covering. This used to cause a CHECK,
|
|
// because the two main frames shared a single RenderViewHost (no longer the
|
|
// case after https://crrev.com/c/1833616).
|
|
|
|
// A2 should be evicted from the cache and asynchronously deleted, due to the
|
|
// cache size limit (B3 took its place in the cache).
|
|
delete_rfh_a2.WaitUntilDeleted();
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
NavigateToTwoPagesOnSameSiteWithSubframes) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
// This test covers the same scenario as NavigateToTwoPagesOnSameSite, except
|
|
// the pages contain subframes:
|
|
// A1(B) -> A2(B(C)) -> D3 -> A1(B)
|
|
//
|
|
// The subframes shouldn't make a difference, so the expected behavior is the
|
|
// same as NavigateToTwoPagesOnSameSite.
|
|
GURL url_a1(embedded_test_server()->GetURL(
|
|
"a.com", "/cross_site_iframe_factory.html?a(b)"));
|
|
GURL url_a2(embedded_test_server()->GetURL(
|
|
"a.com", "/cross_site_iframe_factory.html?a(b(c))"));
|
|
GURL url_d3(embedded_test_server()->GetURL("d.com", "/title1.html"));
|
|
|
|
// 1) Navigate to A1(B).
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a1));
|
|
|
|
// 2) Navigate to A2(B(C)).
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a2));
|
|
RenderFrameHostImpl* rfh_a2 = current_frame_host();
|
|
RenderFrameDeletedObserver delete_rfh_a2(current_frame_host());
|
|
|
|
// 3) Navigate to D3.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_d3));
|
|
EXPECT_TRUE(rfh_a2->IsInBackForwardCache());
|
|
RenderFrameHostImpl* rfh_d3 = current_frame_host();
|
|
|
|
// 4) Do a history navigation back to A1(B).
|
|
ASSERT_TRUE(HistoryGoToIndex(web_contents(), 0));
|
|
|
|
// D3 takes A2(B(C))'s place in the cache.
|
|
EXPECT_TRUE(rfh_d3->IsInBackForwardCache());
|
|
delete_rfh_a2.WaitUntilDeleted();
|
|
}
|
|
|
|
// Sub-frame doesn't transition from LifecycleStateImpl::kInBackForwardCache to
|
|
// LifecycleStateImpl::kRunningUnloadHandlers even when the sub-frame having
|
|
// unload handlers is being evicted from BackForwardCache.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheUnloadBrowserTest,
|
|
SubframeWithUnloadHandler) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL main_url(embedded_test_server()->GetURL(
|
|
"a.com", "/cross_site_iframe_factory.html?a.com(a.com)"));
|
|
GURL child_url = embedded_test_server()->GetURL(
|
|
"a.com", "/cross_site_iframe_factory.html?a.com()");
|
|
GURL url_2(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
|
|
// 1) Navigate to |main_url|.
|
|
EXPECT_TRUE(NavigateToURL(shell(), main_url));
|
|
RenderFrameHostImpl* main_rfh = current_frame_host();
|
|
ASSERT_EQ(1U, main_rfh->child_count());
|
|
RenderFrameHostImpl* child_rfh = main_rfh->child_at(0)->current_frame_host();
|
|
RenderFrameDeletedObserver main_rfh_observer(main_rfh),
|
|
child_rfh_observer(child_rfh);
|
|
|
|
// 2) Add an unload handler to the child RFH.
|
|
EXPECT_TRUE(ExecJs(child_rfh, "window.onunload = () => {} "));
|
|
|
|
// 3) Navigate to |url_2|.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_2));
|
|
|
|
// 4) The previous main RFH and child RFH should be in the back-forward
|
|
// cache.
|
|
EXPECT_FALSE(main_rfh_observer.deleted());
|
|
EXPECT_FALSE(child_rfh_observer.deleted());
|
|
EXPECT_TRUE(main_rfh->IsInBackForwardCache());
|
|
EXPECT_TRUE(child_rfh->IsInBackForwardCache());
|
|
|
|
// Destruction of bfcached page happens after shutdown and it should not
|
|
// trigger unload handlers and be destroyed directly.
|
|
}
|
|
|
|
// Do a same document navigation and make sure we do not fire the
|
|
// DidFirstVisuallyNonEmptyPaint again
|
|
IN_PROC_BROWSER_TEST_F(
|
|
BackForwardCacheBrowserTest,
|
|
DoesNotFireDidFirstVisuallyNonEmptyPaintForSameDocumentNavigation) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a_1(embedded_test_server()->GetURL(
|
|
"a.com", "/accessibility/html/a-name.html"));
|
|
GURL url_a_2(embedded_test_server()->GetURL(
|
|
"a.com", "/accessibility/html/a-name.html#id"));
|
|
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a_1));
|
|
WaitForFirstVisuallyNonEmptyPaint(shell()->web_contents());
|
|
|
|
FirstVisuallyNonEmptyPaintObserver observer(web_contents());
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a_2));
|
|
// Make sure the bfcache restore code does not fire the event during commit
|
|
// navigation.
|
|
EXPECT_FALSE(observer.did_fire());
|
|
EXPECT_TRUE(web_contents()->CompletedFirstVisuallyNonEmptyPaint());
|
|
}
|
|
|
|
// Make sure we fire DidFirstVisuallyNonEmptyPaint when restoring from bf-cache.
|
|
// TODO(crbug.com/327195951): Re-enable this test
|
|
#if BUILDFLAG(IS_ANDROID)
|
|
#define MAYBE_FiresDidFirstVisuallyNonEmptyPaintWhenRestoredFromCache \
|
|
DISABLED_FiresDidFirstVisuallyNonEmptyPaintWhenRestoredFromCache
|
|
#else
|
|
#define MAYBE_FiresDidFirstVisuallyNonEmptyPaintWhenRestoredFromCache \
|
|
FiresDidFirstVisuallyNonEmptyPaintWhenRestoredFromCache
|
|
#endif
|
|
IN_PROC_BROWSER_TEST_F(
|
|
BackForwardCacheBrowserTest,
|
|
MAYBE_FiresDidFirstVisuallyNonEmptyPaintWhenRestoredFromCache) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// 1) Navigate to A.
|
|
ASSERT_TRUE(NavigateToURL(shell(), url_a));
|
|
WaitForFirstVisuallyNonEmptyPaint(shell()->web_contents());
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
|
|
// 2) Navigate to B.
|
|
ASSERT_TRUE(NavigateToURL(shell(), url_b));
|
|
ASSERT_FALSE(delete_observer_rfh_a.deleted());
|
|
ASSERT_TRUE(rfh_a->IsInBackForwardCache());
|
|
WaitForFirstVisuallyNonEmptyPaint(shell()->web_contents());
|
|
|
|
// 3) Navigate to back to A.
|
|
FirstVisuallyNonEmptyPaintObserver observer(web_contents());
|
|
ASSERT_TRUE(HistoryGoBack(web_contents()));
|
|
// Make sure the bfcache restore code does fire the event during commit
|
|
// navigation.
|
|
EXPECT_TRUE(web_contents()->CompletedFirstVisuallyNonEmptyPaint());
|
|
EXPECT_TRUE(observer.did_fire());
|
|
}
|
|
#if BUILDFLAG(IS_ANDROID)
|
|
#define MAYBE_SetsThemeColorWhenRestoredFromCache \
|
|
DISABLED_SetsThemeColorWhenRestoredFromCache
|
|
#else
|
|
#define MAYBE_SetsThemeColorWhenRestoredFromCache \
|
|
SetsThemeColorWhenRestoredFromCache
|
|
#endif
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
MAYBE_SetsThemeColorWhenRestoredFromCache) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a(embedded_test_server()->GetURL("a.com", "/theme_color.html"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
ASSERT_TRUE(NavigateToURL(shell(), url_a));
|
|
WaitForFirstVisuallyNonEmptyPaint(web_contents());
|
|
RenderFrameHostImplWrapper rfh_a(current_frame_host());
|
|
EXPECT_EQ(web_contents()->GetThemeColor(), 0xFFFF0000u);
|
|
|
|
ASSERT_TRUE(NavigateToURL(shell(), url_b));
|
|
WaitForFirstVisuallyNonEmptyPaint(web_contents());
|
|
ASSERT_TRUE(rfh_a->IsInBackForwardCache());
|
|
EXPECT_EQ(web_contents()->GetThemeColor(), std::nullopt);
|
|
|
|
ThemeColorObserver observer(web_contents());
|
|
ASSERT_TRUE(HistoryGoBack(web_contents()));
|
|
ASSERT_TRUE(observer.WaitUntilThemeColorChange());
|
|
EXPECT_EQ(web_contents()->GetThemeColor(), 0xFFFF0000u);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
ContentsMimeTypeWhenRestoredFromCache) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// Navigate to A.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
EXPECT_EQ(web_contents()->GetContentsMimeType(), "text/html");
|
|
|
|
// Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
ASSERT_FALSE(delete_observer_rfh_a.deleted());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
|
|
// Go back to A, which restores A from bfcache. ContentsMimeType should be
|
|
// restored as well.
|
|
ASSERT_TRUE(HistoryGoBack(web_contents()));
|
|
EXPECT_EQ(rfh_a, current_frame_host());
|
|
ExpectRestored(FROM_HERE);
|
|
EXPECT_EQ(web_contents()->GetContentsMimeType(), "text/html");
|
|
}
|
|
|
|
// Check BackForwardCache is enabled and works for devices with very low memory.
|
|
// Navigate from A -> B and go back.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
BackForwardCacheEnabledOnLowMemoryDevices) {
|
|
// Set device physical memory to 10 MB.
|
|
blink::ApproximatedDeviceMemory::SetPhysicalMemoryMBForTesting(10);
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// 1) Navigate to A.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(current_frame_host());
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
|
|
// 2) Navigate to B. A should be in BackForwardCache.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
RenderFrameHostImpl* rfh_b = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b);
|
|
EXPECT_FALSE(delete_observer_rfh_a.deleted());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
|
|
// 3) Go back to A. B should be in BackForwardCache.
|
|
ASSERT_TRUE(HistoryGoBack(web_contents()));
|
|
EXPECT_FALSE(delete_observer_rfh_b.deleted());
|
|
EXPECT_TRUE(rfh_b->IsInBackForwardCache());
|
|
}
|
|
|
|
// Test for functionality of memory controls in back-forward cache for low
|
|
// memory devices.
|
|
class BackForwardCacheBrowserTestForLowMemoryDevices
|
|
: public BackForwardCacheBrowserTest {
|
|
protected:
|
|
void SetUpCommandLine(base::CommandLine* command_line) override {
|
|
BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
|
|
|
|
// Set the value of memory threshold more than the physical memory and check
|
|
// if back-forward cache is disabled or not.
|
|
std::string memory_threshold =
|
|
base::NumberToString(base::SysInfo::AmountOfPhysicalMemoryMB() + 1);
|
|
scoped_feature_list_.InitWithFeaturesAndParameters(
|
|
{{features::kBackForwardCacheMemoryControls,
|
|
{{"memory_threshold_for_back_forward_cache_in_mb",
|
|
memory_threshold}}},
|
|
{blink::features::kLoadingTasksUnfreezable, {}}},
|
|
{});
|
|
}
|
|
|
|
private:
|
|
base::test::ScopedFeatureList scoped_feature_list_;
|
|
};
|
|
|
|
// Ensure that the BackForwardCache trial is not activated as expected on
|
|
// low-memory devices.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestForLowMemoryDevices,
|
|
DisableBFCacheForLowEndDevices) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// Ensure that the BackForwardCache trial starts inactive.
|
|
EXPECT_FALSE(base::FieldTrialList::IsTrialActive(
|
|
base::FeatureList::GetFieldTrial(features::kBackForwardCache)
|
|
->trial_name()));
|
|
|
|
EXPECT_FALSE(IsBackForwardCacheEnabled());
|
|
|
|
// Ensure that we do not activate the BackForwardCache trial when querying
|
|
// bfcache status.
|
|
EXPECT_FALSE(base::FieldTrialList::IsTrialActive(
|
|
base::FeatureList::GetFieldTrial(features::kBackForwardCache)
|
|
->trial_name()));
|
|
|
|
// 1) Navigate to A.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
// 3) A shouldn't be stored in back-forward cache because the physical
|
|
// memory is less than the memory threshold.
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
|
|
// 4) Go back to check the
|
|
// NotRestoredReasons.kBackForwardCacheDisabledByLowMemory is recorded when
|
|
// the memory is less than the threshold value.
|
|
ASSERT_TRUE(HistoryGoBack(web_contents()));
|
|
|
|
ExpectNotRestored(
|
|
{
|
|
BackForwardCacheMetrics::NotRestoredReason::kBackForwardCacheDisabled,
|
|
BackForwardCacheMetrics::NotRestoredReason::
|
|
kBackForwardCacheDisabledByLowMemory,
|
|
},
|
|
{}, {}, {}, {}, FROM_HERE);
|
|
|
|
// Ensure that the BackForwardCache trial still hasn't been activated.
|
|
EXPECT_FALSE(base::FieldTrialList::IsTrialActive(
|
|
base::FeatureList::GetFieldTrial(features::kBackForwardCache)
|
|
->trial_name()));
|
|
}
|
|
|
|
// Trigger network reqeuests, then navigate from A to B, then go back.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestForLowMemoryDevices,
|
|
DisableBFCacheForLowEndDevices_NetworkRequests) {
|
|
net::test_server::ControllableHttpResponse image_response(
|
|
embedded_test_server(), "/image.png");
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// Ensure that the trials starts inactive.
|
|
EXPECT_FALSE(base::FieldTrialList::IsTrialActive(
|
|
base::FeatureList::GetFieldTrial(features::kBackForwardCache)
|
|
->trial_name()));
|
|
EXPECT_FALSE(base::FieldTrialList::IsTrialActive(
|
|
base::FeatureList::GetFieldTrial(
|
|
blink::features::kLoadingTasksUnfreezable)
|
|
->trial_name()));
|
|
|
|
EXPECT_FALSE(IsBackForwardCacheEnabled());
|
|
|
|
// Ensure that we do not activate the trials for kBackForwardCache and
|
|
// kLoadingTasksUnfreezable when querying bfcache or unfreezable loading tasks
|
|
// status.
|
|
EXPECT_FALSE(base::FieldTrialList::IsTrialActive(
|
|
base::FeatureList::GetFieldTrial(features::kBackForwardCache)
|
|
->trial_name()));
|
|
EXPECT_FALSE(base::FieldTrialList::IsTrialActive(
|
|
base::FeatureList::GetFieldTrial(
|
|
blink::features::kLoadingTasksUnfreezable)
|
|
->trial_name()));
|
|
|
|
// 1) Navigate to A.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
|
|
// Request for an image and send a response to trigger loading code. This is
|
|
// to ensure kLoadingTasksUnfreezable won't trigger bfcache activation.
|
|
EXPECT_TRUE(ExecJs(rfh_a, R"(
|
|
var image = document.createElement("img");
|
|
image.src = "image.png";
|
|
document.body.appendChild(image);
|
|
)"));
|
|
image_response.WaitForRequest();
|
|
image_response.Send(net::HTTP_OK, "image/png");
|
|
image_response.Send("image_body");
|
|
image_response.Done();
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
// 3) A shouldn't be stored in back-forward cache because the physical
|
|
// memory is less than the memory threshold.
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
|
|
// Nothing is recorded when the memory is less than the threshold value.
|
|
ExpectOutcomeDidNotChange(FROM_HERE);
|
|
ExpectNotRestoredDidNotChange(FROM_HERE);
|
|
|
|
// Ensure that the trials still haven't been activated.
|
|
EXPECT_FALSE(base::FieldTrialList::IsTrialActive(
|
|
base::FeatureList::GetFieldTrial(features::kBackForwardCache)
|
|
->trial_name()));
|
|
EXPECT_FALSE(base::FieldTrialList::IsTrialActive(
|
|
base::FeatureList::GetFieldTrial(
|
|
blink::features::kLoadingTasksUnfreezable)
|
|
->trial_name()));
|
|
}
|
|
|
|
// Test for functionality of memory controls in back-forward cache for high
|
|
// memory devices.
|
|
class BackForwardCacheBrowserTestForHighMemoryDevices
|
|
: public BackForwardCacheBrowserTest {
|
|
protected:
|
|
void SetUpCommandLine(base::CommandLine* command_line) override {
|
|
BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
|
|
|
|
// Set the value of memory threshold less than the physical memory and check
|
|
// if back-forward cache is enabled or not.
|
|
std::string memory_threshold =
|
|
base::NumberToString(base::SysInfo::AmountOfPhysicalMemoryMB() - 1);
|
|
scoped_feature_list_.InitWithFeaturesAndParameters(
|
|
{{features::kBackForwardCacheMemoryControls,
|
|
{{"memory_threshold_for_back_forward_cache_in_mb",
|
|
memory_threshold}}},
|
|
{blink::features::kLoadingTasksUnfreezable, {}}},
|
|
{});
|
|
}
|
|
|
|
private:
|
|
base::test::ScopedFeatureList scoped_feature_list_;
|
|
};
|
|
|
|
// Ensure that the BackForwardCache trial got activated as expected on
|
|
// high-memory devices when the BackForwardCache feature is enabled.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestForHighMemoryDevices,
|
|
EnableBFCacheForHighMemoryDevices) {
|
|
// Ensure that the BackForwardCache trial starts active on high-memory devices
|
|
// when the BackForwardCache feature is enabled, because
|
|
// IsBackForwardCacheEnabled() got queried already before the test starts.
|
|
EXPECT_TRUE(base::FieldTrialList::IsTrialActive(
|
|
base::FeatureList::GetFieldTrial(features::kBackForwardCache)
|
|
->trial_name()));
|
|
|
|
EXPECT_TRUE(IsBackForwardCacheEnabled());
|
|
|
|
// Ensure that the BackForwardCache trial stays active after querying
|
|
// IsBackForwardCacheEnabled().
|
|
EXPECT_TRUE(base::FieldTrialList::IsTrialActive(
|
|
base::FeatureList::GetFieldTrial(features::kBackForwardCache)
|
|
->trial_name()));
|
|
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// 1) Navigate to A.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
// 3) A should be stored in back-forward cache because the physical memory is
|
|
// greater than the memory threshold.
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
|
|
// Ensure that the BackForwardCache trial stays active.
|
|
EXPECT_TRUE(base::FieldTrialList::IsTrialActive(
|
|
base::FeatureList::GetFieldTrial(features::kBackForwardCache)
|
|
->trial_name()));
|
|
}
|
|
|
|
// Trigger network reqeuests, then navigate from A to B, then go back.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestForHighMemoryDevices,
|
|
EnableBFCacheForHighMemoryDevices_NetworkRequests) {
|
|
net::test_server::ControllableHttpResponse image_response(
|
|
embedded_test_server(), "/image.png");
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// Ensure that back-forward cache flag is enabled and the trial is active.
|
|
EXPECT_TRUE(IsBackForwardCacheEnabled());
|
|
EXPECT_TRUE(base::FieldTrialList::IsTrialActive(
|
|
base::FeatureList::GetFieldTrial(features::kBackForwardCache)
|
|
->trial_name()));
|
|
|
|
// Ensure that the LoadingTasksUnfreezable trials starts as inactive.
|
|
EXPECT_FALSE(base::FieldTrialList::IsTrialActive(
|
|
base::FeatureList::GetFieldTrial(
|
|
blink::features::kLoadingTasksUnfreezable)
|
|
->trial_name()));
|
|
|
|
// 1) Navigate to A.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
|
|
// Request for an image and send a response to trigger loading code.
|
|
EXPECT_TRUE(ExecJs(rfh_a, R"(
|
|
var image = document.createElement("img");
|
|
image.src = "image.png";
|
|
document.body.appendChild(image);
|
|
)"));
|
|
image_response.WaitForRequest();
|
|
image_response.Send(net::HTTP_OK, "image/png");
|
|
image_response.Send("image_body");
|
|
image_response.Done();
|
|
|
|
// The loading code activates the LoadingTasksUnfreezable trial.
|
|
EXPECT_TRUE(base::FieldTrialList::IsTrialActive(
|
|
base::FeatureList::GetFieldTrial(
|
|
blink::features::kLoadingTasksUnfreezable)
|
|
->trial_name()));
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
// 3) A should be stored in back-forward cache because the physical memory is
|
|
// greater than the memory threshold.
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
|
|
// Ensure that the trials stay activated.
|
|
EXPECT_TRUE(base::FieldTrialList::IsTrialActive(
|
|
base::FeatureList::GetFieldTrial(features::kBackForwardCache)
|
|
->trial_name()));
|
|
EXPECT_TRUE(base::FieldTrialList::IsTrialActive(
|
|
base::FeatureList::GetFieldTrial(
|
|
blink::features::kLoadingTasksUnfreezable)
|
|
->trial_name()));
|
|
}
|
|
|
|
// Tests for high memory devices that have the BackForwardCache feature flag
|
|
// disabled.
|
|
class BackForwardCacheBrowserTestForHighMemoryDevicesWithBFCacheDisabled
|
|
: public BackForwardCacheBrowserTest {
|
|
protected:
|
|
void SetUpCommandLine(base::CommandLine* command_line) override {
|
|
BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
|
|
|
|
// Set the value of memory threshold less than the physical memory and check
|
|
// if back-forward cache is enabled or not.
|
|
std::string memory_threshold =
|
|
base::NumberToString(base::SysInfo::AmountOfPhysicalMemoryMB() - 1);
|
|
scoped_feature_list_.InitWithFeaturesAndParameters(
|
|
/*enabled_features=*/
|
|
{{features::kBackForwardCacheMemoryControls,
|
|
{{"memory_threshold_for_back_forward_cache_in_mb",
|
|
memory_threshold}}},
|
|
{blink::features::kLoadingTasksUnfreezable, {}}},
|
|
/*disabled_features=*/
|
|
{features::kBackForwardCache});
|
|
}
|
|
|
|
private:
|
|
base::test::ScopedFeatureList scoped_feature_list_;
|
|
};
|
|
|
|
IN_PROC_BROWSER_TEST_F(
|
|
BackForwardCacheBrowserTestForHighMemoryDevicesWithBFCacheDisabled,
|
|
HighMemoryDevicesWithBFacheDisabled) {
|
|
// Ensure that IsBackForwardCacheEnabled() returns false, because the
|
|
// BackForwardCache feature is disabled.
|
|
EXPECT_FALSE(IsBackForwardCacheEnabled());
|
|
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// 1) Navigate to A.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
// 3) A shouldn't be stored in back-forward cache because the BackForwardCache
|
|
// feature is disabled.
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
|
|
// 4) Go back to check that only kBackForwardCacheDisabled is recorded.
|
|
ASSERT_TRUE(HistoryGoBack(web_contents()));
|
|
|
|
ExpectNotRestored(
|
|
{
|
|
BackForwardCacheMetrics::NotRestoredReason::kBackForwardCacheDisabled,
|
|
},
|
|
{}, {}, {}, {}, FROM_HERE);
|
|
}
|
|
|
|
// Start an inifite dialogs in JS, yielding after each. The first dialog should
|
|
// be dismissed by navigation. The later dialogs should be handled gracefully
|
|
// and not appear while in BFCache. Finally, when the page comes out of BFCache,
|
|
// dialogs should appear again.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
CanUseCacheWhenPageAlertsInTimeoutLoop) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// Navigate to A.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
|
|
AppModalDialogWaiter dialog_waiter(shell());
|
|
|
|
EXPECT_TRUE(ExecJs(rfh_a, R"(
|
|
function alertLoop() {
|
|
setTimeout(alertLoop, 0);
|
|
window.alert("alert");
|
|
}
|
|
// Don't block this script.
|
|
setTimeout(alertLoop, 0);
|
|
)"));
|
|
|
|
dialog_waiter.Wait();
|
|
|
|
// Navigate to B.
|
|
ASSERT_TRUE(NavigateToURL(shell(), url_b));
|
|
RenderFrameHostImpl* rfh_b = current_frame_host();
|
|
|
|
ASSERT_FALSE(delete_observer_rfh_a.deleted());
|
|
ASSERT_THAT(rfh_a, InBackForwardCache());
|
|
ASSERT_NE(rfh_a, rfh_b);
|
|
|
|
dialog_waiter.Restart();
|
|
|
|
// Go back.
|
|
ASSERT_TRUE(HistoryGoBack(web_contents()));
|
|
EXPECT_EQ(rfh_a, current_frame_host());
|
|
EXPECT_FALSE(rfh_a->IsInBackForwardCache());
|
|
|
|
// The page should still be requesting dialogs in a loop. Wait for one to be
|
|
// requested.
|
|
dialog_waiter.Wait();
|
|
}
|
|
|
|
// UnloadOldFrame will clear all dialogs. We test that further requests for
|
|
// dialogs coming from JS do not result in the creation of a dialog. This test
|
|
// posts some dialog creation JS to the render from inside the
|
|
// CommitNavigationCallback task. This JS is then able to post a task back to
|
|
// the renders to show a dialog. By the time this task runs, we the
|
|
// RenderFrameHostImpl's is_active() should be false.
|
|
//
|
|
// This test is not perfect, it can pass simply because the renderer thread does
|
|
// not run the JS in time. Ideally it would block until the renderer posts the
|
|
// request for a dialog but it's possible to do that without creating a nested
|
|
// message loop and if we do that, we risk processing the dialog request.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
DialogsCancelledAndSuppressedWhenCached) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// Navigate to A.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
|
|
// Let's us know whether the following callback ran. Not strictly necessary
|
|
// since it really should run.
|
|
bool posted_dialog_js = false;
|
|
// Create a callback that will be called during the DidCommitNavigation task.
|
|
WillEnterBackForwardCacheCallbackForTesting
|
|
will_enter_back_forward_cache_callback =
|
|
base::BindLambdaForTesting([&]() {
|
|
// Post a dialog, it should not result in a dialog being created.
|
|
ExecuteScriptAsync(rfh_a, R"(window.alert("alert");)");
|
|
posted_dialog_js = true;
|
|
});
|
|
rfh_a->render_view_host()->SetWillEnterBackForwardCacheCallbackForTesting(
|
|
will_enter_back_forward_cache_callback);
|
|
|
|
AppModalDialogWaiter dialog_waiter(shell());
|
|
|
|
// Try show another dialog. It should work.
|
|
ExecuteScriptAsync(rfh_a, R"(window.alert("alert");)");
|
|
dialog_waiter.Wait();
|
|
|
|
dialog_waiter.Restart();
|
|
|
|
// Navigate to B.
|
|
ASSERT_TRUE(NavigateToURL(shell(), url_b));
|
|
RenderFrameHostImpl* rfh_b = current_frame_host();
|
|
|
|
ASSERT_FALSE(delete_observer_rfh_a.deleted());
|
|
ASSERT_THAT(rfh_a, InBackForwardCache());
|
|
ASSERT_NE(rfh_a, rfh_b);
|
|
// Test that the JS was run and that it didn't result in a dialog.
|
|
ASSERT_TRUE(posted_dialog_js);
|
|
ASSERT_FALSE(dialog_waiter.WasDialogRequestedCallbackCalled());
|
|
|
|
// Go back.
|
|
ASSERT_TRUE(HistoryGoBack(web_contents()));
|
|
|
|
EXPECT_EQ(rfh_a, current_frame_host());
|
|
EXPECT_FALSE(rfh_a->IsInBackForwardCache());
|
|
|
|
// Try show another dialog. It should work.
|
|
ExecuteScriptAsync(rfh_a, R"(window.alert("alert");)");
|
|
dialog_waiter.Wait();
|
|
}
|
|
|
|
// Tests that pagehide handlers of the old RFH are run for bfcached pages even
|
|
// if the page is already hidden (and visibilitychange won't run).
|
|
// Disabled on Linux and Win because of flakiness, see crbug.com/40165901.
|
|
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN)
|
|
#define MAYBE_PagehideRunsWhenPageIsHidden DISABLED_PagehideRunsWhenPageIsHidden
|
|
#else
|
|
#define MAYBE_PagehideRunsWhenPageIsHidden PagehideRunsWhenPageIsHidden
|
|
#endif
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
MAYBE_PagehideRunsWhenPageIsHidden) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_1(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_2(embedded_test_server()->GetURL("b.com", "/title2.html"));
|
|
GURL url_3(embedded_test_server()->GetURL("a.com", "/title2.html"));
|
|
WebContentsImpl* web_contents =
|
|
static_cast<WebContentsImpl*>(shell()->web_contents());
|
|
|
|
// 1) Navigate to |url_1| and hide the tab.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_1));
|
|
RenderFrameHostImplWrapper main_frame_1(web_contents->GetPrimaryMainFrame());
|
|
// We need to set it to Visibility::VISIBLE first in case this is the first
|
|
// time the visibility is updated.
|
|
web_contents->UpdateWebContentsVisibility(Visibility::VISIBLE);
|
|
web_contents->UpdateWebContentsVisibility(Visibility::HIDDEN);
|
|
EXPECT_EQ(Visibility::HIDDEN, web_contents->GetVisibility());
|
|
|
|
// Create a pagehide handler that sets item "pagehide_storage" and a
|
|
// visibilitychange handler that sets item "visibilitychange_storage" in
|
|
// localStorage.
|
|
EXPECT_TRUE(ExecJs(main_frame_1.get(),
|
|
R"(
|
|
localStorage.setItem('pagehide_storage', 'not_dispatched');
|
|
var dispatched_pagehide = false;
|
|
window.onpagehide = function(e) {
|
|
if (dispatched_pagehide) {
|
|
// We shouldn't dispatch pagehide more than once.
|
|
localStorage.setItem('pagehide_storage', 'dispatched_more_than_once');
|
|
} else if (!e.persisted) {
|
|
localStorage.setItem('pagehide_storage', 'wrong_persisted');
|
|
} else {
|
|
localStorage.setItem('pagehide_storage', 'dispatched_once');
|
|
}
|
|
dispatched_pagehide = true;
|
|
}
|
|
localStorage.setItem('visibilitychange_storage', 'not_dispatched');
|
|
document.onvisibilitychange = function(e) {
|
|
localStorage.setItem('visibilitychange_storage',
|
|
'should_not_be_dispatched');
|
|
}
|
|
)"));
|
|
// |visibilitychange_storage| should be set to its initial correct value.
|
|
EXPECT_EQ("not_dispatched",
|
|
GetLocalStorage(main_frame_1.get(), "visibilitychange_storage"));
|
|
|
|
// 2) Navigate cross-site to |url_2|. We need to navigate cross-site to make
|
|
// sure we won't run pagehide and visibilitychange during new page's commit,
|
|
// which is tested in ProactivelySwapBrowsingInstancesSameSiteTest.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_2));
|
|
|
|
// |main_frame_1| should be in the back-forward cache.
|
|
EXPECT_TRUE(main_frame_1->IsInBackForwardCache());
|
|
|
|
// 3) Navigate to |url_3| which is same-origin with |url_1|, so we can check
|
|
// the localStorage values.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_3));
|
|
RenderFrameHostImpl* main_frame_3 = web_contents->GetPrimaryMainFrame();
|
|
|
|
// Check that the value for 'pagehide_storage' and 'visibilitychange_storage'
|
|
// are set correctly.
|
|
EXPECT_TRUE(
|
|
WaitForLocalStorage(main_frame_3, "pagehide_storage", "dispatched_once"));
|
|
EXPECT_TRUE(WaitForLocalStorage(main_frame_3, "visibilitychange_storage",
|
|
"not_dispatched"));
|
|
}
|
|
|
|
// Tests that we're getting the correct TextInputState and focus updates when a
|
|
// page enters the back-forward cache and when it gets restored.
|
|
// TODO(b/324570785): Re-enable the test for Android.
|
|
#if BUILDFLAG(IS_ANDROID)
|
|
#define MAYBE_TextInputStateUpdated DISABLED_TextInputStateUpdated
|
|
#else
|
|
#define MAYBE_TextInputStateUpdated TextInputStateUpdated
|
|
#endif
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
MAYBE_TextInputStateUpdated) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_1(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_2(embedded_test_server()->GetURL("b.com", "/title2.html"));
|
|
|
|
// 1) Navigate to |url_1| and add a text input with "foo" as the value.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_1));
|
|
RenderFrameHostImpl* rfh_1 = current_frame_host();
|
|
EXPECT_TRUE(ExecJs(rfh_1,
|
|
"document.title='bfcached';"
|
|
"var input = document.createElement('input');"
|
|
"input.setAttribute('type', 'text');"
|
|
"input.setAttribute('value', 'foo');"
|
|
"document.body.appendChild(input);"
|
|
"var focusCount = 0;"
|
|
"var blurCount = 0;"
|
|
"input.onfocus = () => { focusCount++;};"
|
|
"input.onblur = () => { blurCount++; };"));
|
|
|
|
{
|
|
TextInputManagerTypeObserver type_observer(web_contents(),
|
|
ui::TEXT_INPUT_TYPE_TEXT);
|
|
TextInputManagerValueObserver value_observer(web_contents(), "foo");
|
|
// 2) Press tab key to focus the <input>, and verify the type & value.
|
|
SimulateKeyPress(web_contents(), ui::DomKey::TAB, ui::DomCode::TAB,
|
|
ui::VKEY_TAB, false, false, false, false);
|
|
type_observer.Wait();
|
|
value_observer.Wait();
|
|
|
|
EXPECT_EQ(rfh_1, web_contents()->GetFocusedFrame());
|
|
EXPECT_EQ(EvalJs(rfh_1, "focusCount").ExtractInt(), 1);
|
|
EXPECT_EQ(EvalJs(rfh_1, "blurCount").ExtractInt(), 0);
|
|
}
|
|
|
|
{
|
|
TextInputManagerTester tester(web_contents());
|
|
TextInputManagerValueObserver value_observer(web_contents(), "A");
|
|
// 3) Press the "A" key to change the text input value. This should notify
|
|
// the browser that the text input value has changed.
|
|
SimulateKeyPress(web_contents(), ui::DomKey::FromCharacter('A'),
|
|
ui::DomCode::US_A, ui::VKEY_A, false, false, false, false);
|
|
value_observer.Wait();
|
|
|
|
EXPECT_EQ(rfh_1, web_contents()->GetFocusedFrame());
|
|
EXPECT_EQ(EvalJs(rfh_1, "focusCount").ExtractInt(), 1);
|
|
EXPECT_EQ(EvalJs(rfh_1, "blurCount").ExtractInt(), 0);
|
|
}
|
|
|
|
{
|
|
TextInputManagerTypeObserver type_observer(web_contents(),
|
|
ui::TEXT_INPUT_TYPE_NONE);
|
|
// 4) Navigating to |url_2| should reset type to TEXT_INPUT_TYPE_NONE.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_2));
|
|
type_observer.Wait();
|
|
// |rfh_1| should get into the back-forward cache.
|
|
EXPECT_TRUE(rfh_1->IsInBackForwardCache());
|
|
EXPECT_EQ(current_frame_host(), web_contents()->GetFocusedFrame());
|
|
EXPECT_NE(rfh_1, web_contents()->GetFocusedFrame());
|
|
}
|
|
|
|
{
|
|
// 5) Navigating back to |url_1|, we shouldn't restore the focus to the
|
|
// text input, but |rfh_1| will be focused again as we will restore focus
|
|
// to main frame after navigation.
|
|
ASSERT_TRUE(HistoryGoBack(web_contents()));
|
|
|
|
EXPECT_EQ(rfh_1, web_contents()->GetFocusedFrame());
|
|
EXPECT_EQ(EvalJs(rfh_1, "focusCount").ExtractInt(), 1);
|
|
EXPECT_EQ(EvalJs(rfh_1, "blurCount").ExtractInt(), 1);
|
|
}
|
|
|
|
{
|
|
TextInputManagerTypeObserver type_observer(web_contents(),
|
|
ui::TEXT_INPUT_TYPE_TEXT);
|
|
TextInputManagerValueObserver value_observer(web_contents(), "A");
|
|
// 6) Press tab key to focus the <input> again. Note that we need to press
|
|
// the tab key twice here, because the last "tab focus" point was the
|
|
// <input> element. The first tab key press would focus on the UI/url bar,
|
|
// then the second tab key would go back to the <input>.
|
|
SimulateKeyPress(web_contents(), ui::DomKey::TAB, ui::DomCode::TAB,
|
|
ui::VKEY_TAB, false, false, false, false);
|
|
SimulateKeyPress(web_contents(), ui::DomKey::TAB, ui::DomCode::TAB,
|
|
ui::VKEY_TAB, false, false, false, false);
|
|
type_observer.Wait();
|
|
value_observer.Wait();
|
|
|
|
EXPECT_EQ(rfh_1, web_contents()->GetFocusedFrame());
|
|
EXPECT_EQ(EvalJs(rfh_1, "focusCount").ExtractInt(), 2);
|
|
EXPECT_EQ(EvalJs(rfh_1, "blurCount").ExtractInt(), 1);
|
|
}
|
|
}
|
|
|
|
#if (BUILDFLAG(IS_MAC) || BUILDFLAG(IS_ANDROID))
|
|
#define MAYBE_SubframeTextInputStateUpdated DISABLED_SubframeTextInputStateUpdated
|
|
#else
|
|
#define MAYBE_SubframeTextInputStateUpdated SubframeTextInputStateUpdated
|
|
#endif
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
MAYBE_SubframeTextInputStateUpdated) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_1(embedded_test_server()->GetURL(
|
|
"a.com", "/cross_site_iframe_factory.html?a(b(a))"));
|
|
GURL url_2(embedded_test_server()->GetURL("b.com", "/title2.html"));
|
|
|
|
// 1) Navigate to |url_1| and add a text input with "foo" as the value in the
|
|
// a.com subframe.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_1));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host();
|
|
RenderFrameHostImpl* rfh_subframe_a =
|
|
rfh_b->child_at(0)->current_frame_host();
|
|
EXPECT_TRUE(ExecJs(rfh_subframe_a,
|
|
"var input = document.createElement('input');"
|
|
"input.setAttribute('type', 'text');"
|
|
"input.setAttribute('value', 'foo');"
|
|
"document.body.appendChild(input);"
|
|
"var focusCount = 0;"
|
|
"var blurCount = 0;"
|
|
"input.onfocus = () => { focusCount++;};"
|
|
"input.onblur = () => { blurCount++; };"));
|
|
|
|
{
|
|
TextInputManagerTypeObserver type_observer(web_contents(),
|
|
ui::TEXT_INPUT_TYPE_TEXT);
|
|
TextInputManagerValueObserver value_observer(web_contents(), "foo");
|
|
// 2) Press tab key to focus the <input>, and verify the type & value.
|
|
SimulateKeyPress(web_contents(), ui::DomKey::TAB, ui::DomCode::TAB,
|
|
ui::VKEY_TAB, false, false, false, false);
|
|
type_observer.Wait();
|
|
value_observer.Wait();
|
|
|
|
EXPECT_EQ(rfh_subframe_a, web_contents()->GetFocusedFrame());
|
|
EXPECT_EQ(EvalJs(rfh_subframe_a, "focusCount").ExtractInt(), 1);
|
|
EXPECT_EQ(EvalJs(rfh_subframe_a, "blurCount").ExtractInt(), 0);
|
|
}
|
|
|
|
{
|
|
TextInputManagerTester tester(web_contents());
|
|
TextInputManagerValueObserver value_observer(web_contents(), "A");
|
|
// 3) Press the "A" key to change the text input value. This should notify
|
|
// the browser that the text input value has changed.
|
|
SimulateKeyPress(web_contents(), ui::DomKey::FromCharacter('A'),
|
|
ui::DomCode::US_A, ui::VKEY_A, false, false, false, false);
|
|
value_observer.Wait();
|
|
|
|
EXPECT_EQ(rfh_subframe_a, web_contents()->GetFocusedFrame());
|
|
EXPECT_EQ(EvalJs(rfh_subframe_a, "focusCount").ExtractInt(), 1);
|
|
EXPECT_EQ(EvalJs(rfh_subframe_a, "blurCount").ExtractInt(), 0);
|
|
}
|
|
|
|
{
|
|
TextInputManagerTypeObserver type_observer(web_contents(),
|
|
ui::TEXT_INPUT_TYPE_NONE);
|
|
// 4) Navigating to |url_2| should reset type to TEXT_INPUT_TYPE_NONE and
|
|
// changed focus to the new page's main frame.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_2));
|
|
type_observer.Wait();
|
|
|
|
// |rfh_a| and its subframes should get into the back-forward cache.
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
EXPECT_TRUE(rfh_b->IsInBackForwardCache());
|
|
EXPECT_TRUE(rfh_subframe_a->IsInBackForwardCache());
|
|
EXPECT_NE(rfh_subframe_a, web_contents()->GetFocusedFrame());
|
|
}
|
|
|
|
{
|
|
// 5) Navigating back to |url_1|, we shouldn't restore the focus to the
|
|
// text input in the subframe (we will focus on the main frame |rfh_a|
|
|
// instead).
|
|
ASSERT_TRUE(HistoryGoBack(web_contents()));
|
|
|
|
EXPECT_EQ(rfh_a, web_contents()->GetFocusedFrame());
|
|
EXPECT_EQ(EvalJs(rfh_subframe_a, "focusCount").ExtractInt(), 1);
|
|
EXPECT_EQ(EvalJs(rfh_subframe_a, "blurCount").ExtractInt(), 1);
|
|
}
|
|
|
|
{
|
|
TextInputManagerTypeObserver type_observer(web_contents(),
|
|
ui::TEXT_INPUT_TYPE_TEXT);
|
|
TextInputManagerValueObserver value_observer(web_contents(), "A");
|
|
// 6) Press tab key to focus the <input> again.
|
|
SimulateKeyPress(web_contents(), ui::DomKey::TAB, ui::DomCode::TAB,
|
|
ui::VKEY_TAB, false, false, false, false);
|
|
type_observer.Wait();
|
|
value_observer.Wait();
|
|
|
|
EXPECT_EQ(rfh_subframe_a, web_contents()->GetFocusedFrame());
|
|
EXPECT_EQ(EvalJs(rfh_subframe_a, "focusCount").ExtractInt(), 2);
|
|
EXPECT_EQ(EvalJs(rfh_subframe_a, "blurCount").ExtractInt(), 1);
|
|
}
|
|
}
|
|
|
|
// Tests that trying to focus on a BFCached cross-site iframe won't crash.
|
|
// See https://crbug.com/1250218.
|
|
// TODO(crbug.com/40856039): Flaky on linux tsan
|
|
#if BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
|
|
#define MAYBE_FocusSameSiteSubframeOnPagehide \
|
|
DISABLED_FocusSameSiteSubframeOnPagehide
|
|
#else
|
|
#define MAYBE_FocusSameSiteSubframeOnPagehide FocusSameSiteSubframeOnPagehide
|
|
#endif
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
MAYBE_FocusSameSiteSubframeOnPagehide) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL main_url(
|
|
embedded_test_server()->GetURL("a.com", "/page_with_iframe.html"));
|
|
GURL main_url_2(embedded_test_server()->GetURL("b.com", "/title2.html"));
|
|
|
|
// 1) Navigate to a page with a same-site iframe.
|
|
EXPECT_TRUE(NavigateToURL(shell(), main_url));
|
|
RenderFrameHostImplWrapper rfh_1(current_frame_host());
|
|
EXPECT_EQ(rfh_1.get(), web_contents()->GetFocusedFrame());
|
|
|
|
// 2) Navigate away from the page while trying to focus the subframe on
|
|
// pagehide. The DidFocusFrame IPC should arrive after the page gets into
|
|
// BFCache and should be ignored by the browser. The focus after navigation
|
|
// should go to the new main frame.
|
|
EXPECT_TRUE(ExecJs(rfh_1.get(), R"(
|
|
window.onpagehide = function(e) {
|
|
document.getElementById("test_iframe").focus();
|
|
})"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), main_url_2));
|
|
EXPECT_TRUE(rfh_1->IsInBackForwardCache());
|
|
EXPECT_NE(rfh_1.get(), web_contents()->GetFocusedFrame());
|
|
EXPECT_EQ(current_frame_host(), web_contents()->GetFocusedFrame());
|
|
|
|
// 3) Navigate back to the page. The focus should be on the main frame.
|
|
ASSERT_TRUE(HistoryGoBack(web_contents()));
|
|
EXPECT_EQ(rfh_1.get(), web_contents()->GetFocusedFrame());
|
|
ExpectRestored(FROM_HERE);
|
|
}
|
|
|
|
// Tests that trying to focus on a BFCached cross-site iframe won't crash.
|
|
// See https://crbug.com/1250218.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
FocusCrossSiteSubframeOnPagehide) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL main_url(embedded_test_server()->GetURL(
|
|
"a.com", "/cross_site_iframe_factory.html?a(b)"));
|
|
GURL main_url_2(embedded_test_server()->GetURL("b.com", "/title2.html"));
|
|
|
|
// 1) Navigate to a page with a cross-site iframe.
|
|
EXPECT_TRUE(NavigateToURL(shell(), main_url));
|
|
RenderFrameHostImplWrapper rfh_1(current_frame_host());
|
|
EXPECT_EQ(rfh_1.get(), web_contents()->GetFocusedFrame());
|
|
|
|
// 2) Navigate away from the page while trying to focus the subframe on
|
|
// pagehide. The DidFocusFrame IPC should arrive after the page gets into
|
|
// BFCache and should be ignored by the browser. The focus after navigation
|
|
// should go to the new main frame.
|
|
EXPECT_TRUE(ExecJs(rfh_1.get(), R"(
|
|
window.onpagehide = function(e) {
|
|
document.getElementById("child-0").focus();
|
|
})"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), main_url_2));
|
|
EXPECT_TRUE(rfh_1->IsInBackForwardCache());
|
|
EXPECT_NE(rfh_1.get(), web_contents()->GetFocusedFrame());
|
|
|
|
// 3) Navigate back to the page. The focus should be on the original page's
|
|
// main frame.
|
|
ASSERT_TRUE(HistoryGoBack(web_contents()));
|
|
EXPECT_EQ(rfh_1.get(), current_frame_host());
|
|
ExpectRestored(FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
MainDocumentCSPHeadersAreRestored) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
GURL url_a(embedded_test_server()->GetURL(
|
|
"a.com",
|
|
"/set-header?"
|
|
"Content-Security-Policy: frame-src 'none'"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// 1) Navigate to A, which should set CSP.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
|
|
// Check that CSP was set.
|
|
{
|
|
const std::vector<network::mojom::ContentSecurityPolicyPtr>& root_csp =
|
|
current_frame_host()
|
|
->policy_container_host()
|
|
->policies()
|
|
.content_security_policies;
|
|
EXPECT_EQ(1u, root_csp.size());
|
|
EXPECT_EQ("frame-src 'none'", root_csp[0]->header->header_value);
|
|
}
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
// 3) Navigate back and expect that the CSP headers are present on the main
|
|
// frame.
|
|
ASSERT_TRUE(HistoryGoBack(web_contents()));
|
|
EXPECT_EQ(rfh_a, current_frame_host());
|
|
ExpectRestored(FROM_HERE);
|
|
|
|
// Check that CSP was restored.
|
|
{
|
|
const std::vector<network::mojom::ContentSecurityPolicyPtr>& root_csp =
|
|
current_frame_host()
|
|
->policy_container_host()
|
|
->policies()
|
|
.content_security_policies;
|
|
EXPECT_EQ(1u, root_csp.size());
|
|
EXPECT_EQ("frame-src 'none'", root_csp[0]->header->header_value);
|
|
}
|
|
}
|
|
|
|
// Check that sandboxed documents are cached and won't lose their sandbox flags
|
|
// after restoration.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, CspSandbox) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
GURL url_a(
|
|
embedded_test_server()->GetURL("a.com",
|
|
"/set-header?"
|
|
"Content-Security-Policy: sandbox"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// 1) Navigate to A, which should set CSP.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
{
|
|
const std::vector<network::mojom::ContentSecurityPolicyPtr>& root_csp =
|
|
current_frame_host()
|
|
->policy_container_host()
|
|
->policies()
|
|
.content_security_policies;
|
|
ASSERT_EQ(1u, root_csp.size());
|
|
ASSERT_EQ("sandbox", root_csp[0]->header->header_value);
|
|
ASSERT_EQ(network::mojom::WebSandboxFlags::kAll,
|
|
current_frame_host()->active_sandbox_flags());
|
|
}
|
|
|
|
// 2) Navigate to B. Expect the previous RenderFrameHost to enter the bfcache.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
EXPECT_FALSE(delete_observer_rfh_a.deleted());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
{
|
|
const std::vector<network::mojom::ContentSecurityPolicyPtr>& root_csp =
|
|
current_frame_host()
|
|
->policy_container_host()
|
|
->policies()
|
|
.content_security_policies;
|
|
ASSERT_EQ(0u, root_csp.size());
|
|
ASSERT_EQ(network::mojom::WebSandboxFlags::kNone,
|
|
current_frame_host()->active_sandbox_flags());
|
|
}
|
|
|
|
// 3) Navigate back and expect the page to be restored, with the correct
|
|
// CSP and sandbox flags.
|
|
ASSERT_TRUE(HistoryGoBack(web_contents()));
|
|
EXPECT_FALSE(delete_observer_rfh_a.deleted());
|
|
EXPECT_EQ(current_frame_host(), rfh_a);
|
|
{
|
|
const std::vector<network::mojom::ContentSecurityPolicyPtr>& root_csp =
|
|
current_frame_host()
|
|
->policy_container_host()
|
|
->policies()
|
|
.content_security_policies;
|
|
ASSERT_EQ(1u, root_csp.size());
|
|
ASSERT_EQ("sandbox", root_csp[0]->header->header_value);
|
|
ASSERT_EQ(network::mojom::WebSandboxFlags::kAll,
|
|
current_frame_host()->active_sandbox_flags());
|
|
}
|
|
}
|
|
|
|
// Check that about:blank is not cached.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, AboutBlankWillNotBeCached) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
// 1) Navigate to about:blank.
|
|
GURL blank_url(url::kAboutBlankURL);
|
|
EXPECT_TRUE(NavigateToURL(shell(), blank_url));
|
|
RenderFrameHostImplWrapper rfh_blank(current_frame_host());
|
|
|
|
// 2) Navigate to a.com.
|
|
GURL url_a(embedded_test_server()->GetURL("a.com", "/empty.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
|
|
// 3) Navigate back to about:blank.
|
|
ASSERT_TRUE(HistoryGoBack(web_contents()));
|
|
|
|
// This about:blank document does not have a SiteInstance and then loading a
|
|
// page on it doesn't swap the browsing instance.
|
|
|
|
if (ShouldCreateNewHostForAllFrames()) {
|
|
EXPECT_TRUE(rfh_blank.WaitUntilRenderFrameDeleted());
|
|
ExpectNotRestored(
|
|
{
|
|
BackForwardCacheMetrics::NotRestoredReason::kHTTPStatusNotOK,
|
|
BackForwardCacheMetrics::NotRestoredReason::kSchemeNotHTTPOrHTTPS,
|
|
BackForwardCacheMetrics::NotRestoredReason::
|
|
kBrowsingInstanceNotSwapped,
|
|
},
|
|
{}, {ShouldSwapBrowsingInstance::kNo_DoesNotHaveSite}, {}, {},
|
|
FROM_HERE);
|
|
|
|
} else {
|
|
EXPECT_FALSE(rfh_blank->IsInBackForwardCache());
|
|
ExpectNotRestored(
|
|
{
|
|
BackForwardCacheMetrics::NotRestoredReason::
|
|
kBrowsingInstanceNotSwapped,
|
|
},
|
|
{}, {ShouldSwapBrowsingInstance::kNo_DoesNotHaveSite}, {}, {},
|
|
FROM_HERE);
|
|
}
|
|
}
|
|
|
|
// Check that browsing instances are not swapped when a navigation redirects
|
|
// toward the last committed URL and the reasons are recorded correctly.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, RedirectToSelf) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
NavigationControllerImpl& controller = web_contents()->GetController();
|
|
|
|
// 1) Navigate to a.com/empty.html.
|
|
GURL url_a(embedded_test_server()->GetURL("a.com", "/empty.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImplWrapper rfh_a(current_frame_host());
|
|
EXPECT_EQ(1, controller.GetEntryCount());
|
|
EXPECT_EQ(url_a, controller.GetLastCommittedEntry()->GetURL());
|
|
|
|
// 2) Navigate to the same page by redirection.
|
|
GURL url_a2(embedded_test_server()->GetURL(
|
|
"a.com", "/server-redirect-301?" + url_a.spec()));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a2, url_a));
|
|
RenderFrameHostImplWrapper rfh_b(current_frame_host());
|
|
EXPECT_EQ(2, controller.GetEntryCount());
|
|
|
|
if (ShouldCreateNewHostForAllFrames()) {
|
|
EXPECT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());
|
|
} else {
|
|
EXPECT_FALSE(rfh_a->IsInBackForwardCache());
|
|
EXPECT_TRUE(rfh_a->GetSiteInstance()->IsRelatedSiteInstance(
|
|
rfh_b->GetSiteInstance()));
|
|
}
|
|
|
|
EXPECT_EQ(url_a, controller.GetLastCommittedEntry()->GetURL());
|
|
|
|
// 3) Navigate back to the previous page.
|
|
ASSERT_TRUE(HistoryGoBack(web_contents()));
|
|
EXPECT_EQ(2, controller.GetEntryCount());
|
|
EXPECT_EQ(url_a, controller.GetLastCommittedEntry()->GetURL());
|
|
|
|
// TODO(crbug.com/40760515): Investigate whether these navigation results are
|
|
// expected.
|
|
ExpectNotRestored(
|
|
{
|
|
BackForwardCacheMetrics::NotRestoredReason::
|
|
kBrowsingInstanceNotSwapped,
|
|
},
|
|
{}, {ShouldSwapBrowsingInstance::kNo_SameUrlNavigation}, {}, {},
|
|
FROM_HERE);
|
|
}
|
|
|
|
// Check that reloading doesn't affect the back-forward cache usage.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, ReloadDoesntAffectCache) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
NavigationControllerImpl& controller = web_contents()->GetController();
|
|
|
|
// 1) Navigate to a.com.
|
|
GURL url_a(embedded_test_server()->GetURL("a.com", "/empty.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
EXPECT_EQ(1, controller.GetEntryCount());
|
|
EXPECT_EQ(url_a, controller.GetLastCommittedEntry()->GetURL());
|
|
|
|
// 2) Navigate to b.com.
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/empty.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
EXPECT_EQ(2, controller.GetEntryCount());
|
|
EXPECT_EQ(url_b, controller.GetLastCommittedEntry()->GetURL());
|
|
|
|
// 3) Go back to a.com and reload.
|
|
ASSERT_TRUE(HistoryGoBack(web_contents()));
|
|
EXPECT_EQ(2, controller.GetEntryCount());
|
|
EXPECT_EQ(url_a, controller.GetLastCommittedEntry()->GetURL());
|
|
|
|
ExpectRestored(FROM_HERE);
|
|
|
|
// 4) Reload the tab.
|
|
web_contents()->GetController().Reload(content::ReloadType::NORMAL, false);
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
EXPECT_EQ(2, controller.GetEntryCount());
|
|
EXPECT_EQ(url_a, controller.GetLastCommittedEntry()->GetURL());
|
|
|
|
// By reloading the tab, ShouldSwapBrowsingInstance::
|
|
// kNo_AlreadyHasMatchingBrowsingInstance is set once. This should be reset
|
|
// when the navigation 4)'s commit finishes and should not prevent putting the
|
|
// page into the back-forward cache.
|
|
//
|
|
// Note that SetBrowsingInstanceSwapResult might not be called for every
|
|
// navigation because we might not get to this point for some navigations,
|
|
// e.g. if the navigation uses a pre-existing RenderFrameHost and SiteInstance
|
|
// for navigation.
|
|
//
|
|
// TODO(crbug.com/40747698): Tie BrowsingInstanceSwapResult to
|
|
// NavigationRequest instead and move the SetBrowsingInstanceSwapResult call
|
|
// for navigations to happen at commit time instead.
|
|
|
|
// 5) Go forward to b.com and reload.
|
|
ASSERT_TRUE(HistoryGoForward(web_contents()));
|
|
EXPECT_EQ(2, controller.GetEntryCount());
|
|
EXPECT_EQ(url_b, controller.GetLastCommittedEntry()->GetURL());
|
|
|
|
// The page loaded at B) is correctly cached and restored. Reloading doesn't
|
|
// affect the cache usage.
|
|
ExpectRestored(FROM_HERE);
|
|
|
|
// 6) Go back to a.com.
|
|
ASSERT_TRUE(HistoryGoBack(web_contents()));
|
|
EXPECT_EQ(2, controller.GetEntryCount());
|
|
EXPECT_EQ(url_a, controller.GetLastCommittedEntry()->GetURL());
|
|
|
|
// The page loaded at 3) is correctly cached and restored. Reloading doesn't
|
|
// affect the cache usage.
|
|
ExpectRestored(FROM_HERE);
|
|
}
|
|
|
|
// Regression test for crbug.com/1183313. Checks that CommitNavigationParam's
|
|
// |has_user_gesture| value reflects the gesture from the latest navigation
|
|
// after the commit finished.
|
|
IN_PROC_BROWSER_TEST_F(
|
|
BackForwardCacheBrowserTest,
|
|
SameDocumentNavAfterRestoringDocumentLoadedWithUserGesture) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
GURL start_url(embedded_test_server()->GetURL("/title1.html"));
|
|
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_a_foo(embedded_test_server()->GetURL("a.com", "/title1.html#foo"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
NavigationControllerImpl& controller = web_contents()->GetController();
|
|
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
|
|
->GetPrimaryFrameTree()
|
|
.root();
|
|
|
|
// Initial navigation (so that we can initiate a navigation from renderer).
|
|
EXPECT_TRUE(NavigateToURL(shell(), start_url));
|
|
|
|
// 1) Navigate to A with user gesture.
|
|
{
|
|
FrameNavigateParamsCapturer params_capturer(root);
|
|
EXPECT_TRUE(NavigateToURLFromRenderer(shell(), url_a));
|
|
params_capturer.Wait();
|
|
EXPECT_TRUE(params_capturer.has_user_gesture());
|
|
EXPECT_TRUE(root->current_frame_host()
|
|
->last_committed_common_params_has_user_gesture());
|
|
}
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
|
|
// 2) Navigate to B. A should be stored in the back-forward cache.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
EXPECT_FALSE(root->current_frame_host()
|
|
->last_committed_common_params_has_user_gesture());
|
|
|
|
// 3) GoBack to A. RenderFrameHost of A should be restored from the
|
|
// back-forward cache, and "has_user_gesture" is set to false correctly.
|
|
// Note that since this is a back-forward cache restore we create the
|
|
// DidCommitProvisionalLoadParams completely in the browser, so we got the
|
|
// correct value from the latest navigation. However, we did not update the
|
|
// renderer's navigation-related values, so the renderer's DocumentLoader
|
|
// still thinks the last "gesture" value is "true", which will get corrected
|
|
// on the next navigation.
|
|
{
|
|
FrameNavigateParamsCapturer params_capturer(root);
|
|
controller.GoBack();
|
|
params_capturer.Wait();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_EQ(rfh_a, current_frame_host());
|
|
// The navigation doesn't have user gesture.
|
|
EXPECT_FALSE(params_capturer.has_user_gesture());
|
|
EXPECT_FALSE(root->current_frame_host()
|
|
->last_committed_common_params_has_user_gesture());
|
|
}
|
|
|
|
// 4) Same-document navigation to A#foo without user gesture. At this point
|
|
// we will update the renderer's DocumentLoader's latest gesture value to
|
|
// "no user gesture", and we'll get the correct gesture value in
|
|
// DidCommitProvisionalLoadParams.
|
|
{
|
|
FrameNavigateParamsCapturer params_capturer(root);
|
|
EXPECT_TRUE(
|
|
NavigateToURLFromRendererWithoutUserGesture(shell(), url_a_foo));
|
|
params_capturer.Wait();
|
|
// The navigation doesn't have user gesture.
|
|
EXPECT_FALSE(params_capturer.has_user_gesture());
|
|
EXPECT_FALSE(root->current_frame_host()
|
|
->last_committed_common_params_has_user_gesture());
|
|
}
|
|
}
|
|
|
|
testing::Matcher<BackForwardCacheCanStoreTreeResult> MatchesTreeResult(
|
|
testing::Matcher<bool> same_origin,
|
|
GURL url) {
|
|
return testing::AllOf(
|
|
testing::Property("IsSameOrigin",
|
|
&BackForwardCacheCanStoreTreeResult::IsSameOrigin,
|
|
same_origin),
|
|
testing::Property("GetUrl", &BackForwardCacheCanStoreTreeResult::GetUrl,
|
|
url));
|
|
}
|
|
|
|
RenderFrameHostImpl* ChildFrame(RenderFrameHostImpl* rfh, int child_index) {
|
|
return rfh->child_at(child_index)->current_frame_host();
|
|
}
|
|
|
|
// Verifies that the reasons match those given and no others.
|
|
testing::Matcher<BackForwardCacheCanStoreDocumentResult>
|
|
BackForwardCacheBrowserTest::MatchesDocumentResult(
|
|
testing::Matcher<NotRestoredReasons> not_stored,
|
|
BlockListedFeatures block_listed) {
|
|
return testing::AllOf(
|
|
testing::Property(
|
|
"not_restored_reasons",
|
|
&BackForwardCacheCanStoreDocumentResult::not_restored_reasons,
|
|
not_stored),
|
|
testing::Property(
|
|
"blocklisted_features",
|
|
&BackForwardCacheCanStoreDocumentResult::blocklisted_features,
|
|
block_listed),
|
|
testing::Property(
|
|
"disabled_reasons",
|
|
&BackForwardCacheCanStoreDocumentResult::disabled_reasons,
|
|
BackForwardCacheCanStoreDocumentResult::DisabledReasonsMap()),
|
|
testing::Property(
|
|
"disallow_activation_reasons",
|
|
&BackForwardCacheCanStoreDocumentResult::disallow_activation_reasons,
|
|
std::set<uint64_t>()));
|
|
}
|
|
|
|
// Check the contents of the BackForwardCacheCanStoreTreeResult of a page.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, TreeResultFeatureUsage) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a(embedded_test_server()->GetURL(
|
|
"a.com", "/cross_site_iframe_factory.html?a(a, b, c)"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// 1) Navigate to a(a, b, c).
|
|
ASSERT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImplWrapper rfh(current_frame_host());
|
|
|
|
// 2) Add a blocking feature to the main frame A and the sub frame B.
|
|
current_frame_host()
|
|
->UseDummyStickyBackForwardCacheDisablingFeatureForTesting();
|
|
current_frame_host()
|
|
->child_at(1)
|
|
->current_frame_host()
|
|
->UseDummyStickyBackForwardCacheDisablingFeatureForTesting();
|
|
|
|
GURL url_subframe_a = ChildFrame(rfh.get(), 0)->GetLastCommittedURL();
|
|
GURL url_subframe_b = ChildFrame(rfh.get(), 1)->GetLastCommittedURL();
|
|
GURL url_subframe_c = ChildFrame(rfh.get(), 2)->GetLastCommittedURL();
|
|
|
|
// 3) Initialize the reasons tree and navigate away to ensure that everything
|
|
// from the old frame has been destroyed.
|
|
BackForwardCacheCanStoreDocumentResultWithTree can_store_result =
|
|
web_contents()
|
|
->GetController()
|
|
.GetBackForwardCache()
|
|
.GetCurrentBackForwardCacheEligibility(rfh.get());
|
|
ASSERT_TRUE(NavigateToURL(shell(), url_b));
|
|
ASSERT_TRUE(rfh.WaitUntilRenderFrameDeleted());
|
|
|
|
// 4) Check IsSameOrigin() and GetUrl().
|
|
// a
|
|
EXPECT_THAT(*can_store_result.tree_reasons,
|
|
MatchesTreeResult(/*same_origin=*/true,
|
|
/*url=*/url_a));
|
|
// a->a
|
|
EXPECT_THAT(*can_store_result.tree_reasons->GetChildren().at(0),
|
|
MatchesTreeResult(/*same_origin=*/true,
|
|
/*url=*/url_subframe_a));
|
|
// a->b
|
|
EXPECT_THAT(*can_store_result.tree_reasons->GetChildren().at(1),
|
|
MatchesTreeResult(/*same_origin=*/false,
|
|
/*url=*/url_subframe_b));
|
|
// a->c
|
|
EXPECT_THAT(*can_store_result.tree_reasons->GetChildren().at(2),
|
|
MatchesTreeResult(/*same_origin=*/false,
|
|
/*url=*/url_subframe_c));
|
|
|
|
// 5) Check that the blocking reasons match.
|
|
// a
|
|
EXPECT_THAT(can_store_result.tree_reasons->GetDocumentResult(),
|
|
MatchesDocumentResult(
|
|
NotRestoredReasons({NotRestoredReason::kBlocklistedFeatures}),
|
|
BlockListedFeatures(
|
|
{blink::scheduler::WebSchedulerTrackedFeature::kDummy})));
|
|
// a->a
|
|
EXPECT_THAT(
|
|
can_store_result.tree_reasons->GetChildren().at(0)->GetDocumentResult(),
|
|
MatchesDocumentResult(NotRestoredReasons(),
|
|
BlockListedFeatures(BlockListedFeatures())));
|
|
// a->b
|
|
EXPECT_THAT(
|
|
can_store_result.tree_reasons->GetChildren().at(1)->GetDocumentResult(),
|
|
MatchesDocumentResult(
|
|
NotRestoredReasons({NotRestoredReason::kBlocklistedFeatures}),
|
|
BlockListedFeatures(
|
|
{blink::scheduler::WebSchedulerTrackedFeature::kDummy})));
|
|
// a->c
|
|
EXPECT_THAT(
|
|
can_store_result.tree_reasons->GetChildren().at(2)->GetDocumentResult(),
|
|
MatchesDocumentResult(NotRestoredReasons(),
|
|
BlockListedFeatures(BlockListedFeatures())));
|
|
}
|
|
|
|
// Check the contents of the BackForwardCacheCanStoreTreeResult of a page when
|
|
// it is evicted.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
TreeResultEvictionMainFrame) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// 1) Navigate to a.
|
|
ASSERT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImplWrapper rfh_a(current_frame_host());
|
|
rfh_a->GetBackForwardCacheMetrics()->SetObserverForTesting(this);
|
|
|
|
// 2) Navigate to B and evict A by JavaScript execution.
|
|
ASSERT_TRUE(NavigateToURL(shell(), url_b));
|
|
EvictByJavaScript(rfh_a.get());
|
|
ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());
|
|
|
|
// 3) Go back to A.
|
|
ASSERT_TRUE(HistoryGoBack(web_contents()));
|
|
ExpectNotRestored({NotRestoredReason::kJavaScriptExecution}, {}, {}, {}, {},
|
|
FROM_HERE);
|
|
EXPECT_THAT(GetTreeResult()->GetDocumentResult(),
|
|
MatchesDocumentResult(
|
|
NotRestoredReasons({NotRestoredReason::kJavaScriptExecution}),
|
|
BlockListedFeatures()));
|
|
}
|
|
|
|
// Check the contents of the BackForwardCacheCanStoreTreeResult of a page when
|
|
// its subframe is evicted.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
TreeResultEvictionSubFrame) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
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) Navigate to A.
|
|
ASSERT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImplWrapper rfh_a(current_frame_host());
|
|
RenderFrameHostImplWrapper rfh_b(
|
|
current_frame_host()->child_at(0)->current_frame_host());
|
|
rfh_a->GetBackForwardCacheMetrics()->SetObserverForTesting(this);
|
|
|
|
// 2) Navigate to C and evict A's subframe B by JavaScript execution.
|
|
ASSERT_TRUE(NavigateToURL(shell(), url_c));
|
|
EvictByJavaScript(rfh_b.get());
|
|
ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());
|
|
|
|
// 3) Go back to A.
|
|
ASSERT_TRUE(HistoryGoBack(web_contents()));
|
|
ExpectNotRestored({NotRestoredReason::kJavaScriptExecution}, {}, {}, {}, {},
|
|
FROM_HERE);
|
|
// Main frame result in the tree is empty.
|
|
EXPECT_THAT(
|
|
GetTreeResult()->GetDocumentResult(),
|
|
MatchesDocumentResult(NotRestoredReasons(), BlockListedFeatures()));
|
|
// Subframe result in the tree contains the reason.
|
|
EXPECT_THAT(GetTreeResult()->GetChildren().at(0)->GetDocumentResult(),
|
|
MatchesDocumentResult(
|
|
NotRestoredReasons({NotRestoredReason::kJavaScriptExecution}),
|
|
BlockListedFeatures()));
|
|
}
|
|
|
|
// Check the contents of the BackForwardCacheCanStoreTreeResult of a page when
|
|
// its subframe's subframe is evicted.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
TreeResultEvictionSubFramesSubframe) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
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) Navigate to A.
|
|
ASSERT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImplWrapper rfh_a(current_frame_host());
|
|
RenderFrameHostImplWrapper rfh_c(current_frame_host()
|
|
->child_at(0)
|
|
->current_frame_host()
|
|
->child_at(0)
|
|
->current_frame_host());
|
|
rfh_a->GetBackForwardCacheMetrics()->SetObserverForTesting(this);
|
|
|
|
// 2) Navigate to D and evict C by JavaScript execution.
|
|
ASSERT_TRUE(NavigateToURL(shell(), url_d));
|
|
EvictByJavaScript(rfh_c.get());
|
|
ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());
|
|
|
|
// 3) Go back to A.
|
|
ASSERT_TRUE(HistoryGoBack(web_contents()));
|
|
ExpectNotRestored({NotRestoredReason::kJavaScriptExecution}, {}, {}, {}, {},
|
|
FROM_HERE);
|
|
// Main frame result in the tree is empty.
|
|
EXPECT_THAT(
|
|
GetTreeResult()->GetDocumentResult(),
|
|
MatchesDocumentResult(NotRestoredReasons(), BlockListedFeatures()));
|
|
// The first level subframe result in the tree is empty.
|
|
EXPECT_THAT(
|
|
GetTreeResult()->GetChildren().at(0)->GetDocumentResult(),
|
|
MatchesDocumentResult(NotRestoredReasons(), BlockListedFeatures()));
|
|
// The second level subframe result in the tree contains the reason.
|
|
EXPECT_THAT(GetTreeResult()
|
|
->GetChildren()
|
|
.at(0)
|
|
->GetChildren()
|
|
.at(0)
|
|
->GetDocumentResult(),
|
|
MatchesDocumentResult(
|
|
NotRestoredReasons({NotRestoredReason::kJavaScriptExecution}),
|
|
BlockListedFeatures()));
|
|
}
|
|
|
|
void BackForwardCacheBrowserTest::InstallUnloadHandlerOnMainFrame() {
|
|
EXPECT_TRUE(ExecJs(current_frame_host(), R"(
|
|
localStorage["unload_run_count"] = 0;
|
|
window.onunload = () => {
|
|
localStorage["unload_run_count"] =
|
|
1 + parseInt(localStorage["unload_run_count"]);
|
|
};
|
|
)"));
|
|
EXPECT_EQ("0", GetUnloadRunCount());
|
|
}
|
|
|
|
void BackForwardCacheBrowserTest::InstallUnloadHandlerOnSubFrame() {
|
|
TestNavigationObserver navigation_observer(shell()->web_contents(), 1);
|
|
EXPECT_TRUE(ExecJs(current_frame_host(), R"(
|
|
const iframeElement = document.createElement("iframe");
|
|
iframeElement.src = "%s";
|
|
document.body.appendChild(iframeElement);
|
|
)"));
|
|
navigation_observer.Wait();
|
|
RenderFrameHostImpl* subframe_render_frame_host =
|
|
current_frame_host()->child_at(0)->current_frame_host();
|
|
EXPECT_TRUE(ExecJs(subframe_render_frame_host, R"(
|
|
localStorage["unload_run_count"] = 0;
|
|
window.onunload = () => {
|
|
localStorage["unload_run_count"] =
|
|
1 + parseInt(localStorage["unload_run_count"]);
|
|
};
|
|
)"));
|
|
EXPECT_EQ("0", GetUnloadRunCount());
|
|
}
|
|
|
|
EvalJsResult BackForwardCacheBrowserTest::GetUnloadRunCount() {
|
|
return GetLocalStorage(current_frame_host(), "unload_run_count");
|
|
}
|
|
|
|
bool BackForwardCacheBrowserTest::AddBlocklistedFeature(RenderFrameHost* rfh) {
|
|
// Add kDummy as blocking feature.
|
|
RenderFrameHostImplWrapper rfh_a(rfh);
|
|
rfh_a->UseDummyStickyBackForwardCacheDisablingFeatureForTesting();
|
|
return true;
|
|
}
|
|
|
|
void BackForwardCacheBrowserTest::ExpectNotRestoredDueToBlocklistedFeature(
|
|
base::Location location) {
|
|
ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures},
|
|
{blink::scheduler::WebSchedulerTrackedFeature::kDummy}, {},
|
|
{}, {}, location);
|
|
}
|
|
|
|
const ukm::TestAutoSetUkmRecorder& BackForwardCacheBrowserTest::ukm_recorder() {
|
|
return *ukm_recorder_;
|
|
}
|
|
|
|
const base::HistogramTester& BackForwardCacheBrowserTest::histogram_tester() {
|
|
return *histogram_tester_;
|
|
}
|
|
|
|
// Ensure that psges with unload are only allowed to enter back/forward cache by
|
|
// default on Android.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, UnloadAllowedFlag) {
|
|
#if BUILDFLAG(IS_ANDROID)
|
|
ASSERT_TRUE(BackForwardCacheImpl::IsUnloadAllowed());
|
|
#else
|
|
ASSERT_FALSE(BackForwardCacheImpl::IsUnloadAllowed());
|
|
#endif
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
FrameWithBlocklistedFeatureNotCached) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
// Navigate to a page that contains a blocklisted feature.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("a.com", "/title1.html")));
|
|
|
|
RenderFrameHostWrapper rfh(current_frame_host());
|
|
|
|
ASSERT_TRUE(AddBlocklistedFeature(rfh.get()));
|
|
|
|
// Navigate away.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
|
|
|
|
// The page with the unsupported feature should be deleted (not cached).
|
|
ASSERT_TRUE(rfh.WaitUntilRenderFrameDeleted());
|
|
|
|
// Go back.
|
|
ASSERT_TRUE(HistoryGoBack(web_contents()));
|
|
ExpectNotRestoredDueToBlocklistedFeature(FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
SubframeWithBlocklistedFeatureNotCached) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
// Navigate to a page with an iframe that contains a blocklisted feature.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL(
|
|
"a.com", "/cross_site_iframe_factory.html?a(b)")));
|
|
|
|
RenderFrameHostWrapper rfh(
|
|
current_frame_host()->child_at(0)->current_frame_host());
|
|
|
|
ASSERT_TRUE(AddBlocklistedFeature(rfh.get()));
|
|
|
|
// Navigate away.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
|
|
|
|
// The page with the unsupported feature should be deleted (not cached).
|
|
ASSERT_TRUE(rfh.WaitUntilRenderFrameDeleted());
|
|
|
|
// Go back.
|
|
ASSERT_TRUE(HistoryGoBack(web_contents()));
|
|
ExpectNotRestoredDueToBlocklistedFeature(FROM_HERE);
|
|
}
|
|
|
|
class BackForwardCacheBrowserUnloadHandlerTest
|
|
: public BackForwardCacheBrowserTest,
|
|
public ::testing::WithParamInterface<
|
|
std::tuple<bool, bool, bool, TestFrameType>> {
|
|
public:
|
|
void SetUpCommandLine(base::CommandLine* command_line) override {
|
|
if (IsUnloadAllowed()) {
|
|
EnableFeatureAndSetParams(kBackForwardCacheUnloadAllowed, "", "");
|
|
} else {
|
|
DisableFeature(kBackForwardCacheUnloadAllowed);
|
|
}
|
|
if (IsUnloadBlocklisted()) {
|
|
EnableFeatureAndSetParams(blink::features::kUnloadBlocklisted, "", "");
|
|
} else {
|
|
DisableFeature(blink::features::kUnloadBlocklisted);
|
|
}
|
|
if (IsUnloadDeprecationOptedOut()) {
|
|
EnableFeatureAndSetParams(blink::features::kDeprecateUnloadOptOut, "",
|
|
"");
|
|
} else {
|
|
DisableFeature(blink::features::kDeprecateUnloadOptOut);
|
|
}
|
|
|
|
BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
|
|
}
|
|
|
|
bool IsUnloadAllowed() { return std::get<0>(GetParam()); }
|
|
bool IsUnloadBlocklisted() { return std::get<1>(GetParam()); }
|
|
bool IsUnloadDeprecationOptedOut() { return std::get<2>(GetParam()); }
|
|
|
|
TestFrameType GetTestFrameType() { return std::get<3>(GetParam()); }
|
|
|
|
private:
|
|
base::test::ScopedFeatureList scoped_feature_list_;
|
|
};
|
|
|
|
// Ensure that unload handlers in main frames and subframes block caching,
|
|
// depending on unload deprecation status and OS.
|
|
IN_PROC_BROWSER_TEST_P(BackForwardCacheBrowserUnloadHandlerTest,
|
|
UnloadHandlerPresent) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title2.html"));
|
|
|
|
// 1) Navigate to A.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
|
|
BackForwardCacheCanStoreDocumentResult::NotRestoredReasons
|
|
expected_blocking_reasons;
|
|
std::vector<blink::scheduler::WebSchedulerTrackedFeature>
|
|
expected_blocklisted_reason;
|
|
if (IsUnloadBlocklisted()) {
|
|
expected_blocking_reasons.Put(
|
|
BackForwardCacheMetrics::NotRestoredReason::kBlocklistedFeatures);
|
|
expected_blocklisted_reason.push_back(
|
|
blink::scheduler::WebSchedulerTrackedFeature::kUnloadHandler);
|
|
}
|
|
switch (GetTestFrameType()) {
|
|
case content::TestFrameType::kMainFrame:
|
|
InstallUnloadHandlerOnMainFrame();
|
|
expected_blocking_reasons.Put(BackForwardCacheMetrics::NotRestoredReason::
|
|
kUnloadHandlerExistsInMainFrame);
|
|
break;
|
|
case content::TestFrameType::kSubFrame:
|
|
InstallUnloadHandlerOnSubFrame();
|
|
expected_blocking_reasons.Put(BackForwardCacheMetrics::NotRestoredReason::
|
|
kUnloadHandlerExistsInSubFrame);
|
|
break;
|
|
default:
|
|
NOTREACHED();
|
|
}
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
// 3) Go back.
|
|
ASSERT_TRUE(HistoryGoBack(web_contents()));
|
|
|
|
bool unload_never_blocks = IsUnloadAllowed();
|
|
bool unload_deprecated_and_not_opted_out =
|
|
(base::FeatureList::IsEnabled(network::features::kDeprecateUnload) &&
|
|
!IsUnloadDeprecationOptedOut());
|
|
if (unload_never_blocks || unload_deprecated_and_not_opted_out) {
|
|
// Pages with unload handlers are eligible for bfcache only if it is
|
|
// specifically allowed (happens on Android). Also, when unload is
|
|
// deprecated and `kDeprecateUnloadOptOut` doesn't override it, unload
|
|
// handlers cannot be installed so there should be no blocker for BFCache.
|
|
ExpectRestored(FROM_HERE);
|
|
EXPECT_EQ("0", GetUnloadRunCount());
|
|
} else {
|
|
ExpectNotRestored(expected_blocking_reasons, expected_blocklisted_reason,
|
|
{}, {}, {}, FROM_HERE);
|
|
EXPECT_EQ("1", GetUnloadRunCount());
|
|
}
|
|
}
|
|
|
|
// First param: whether unload is allowed or not.
|
|
// Second one: whether unload is blocklisted or not.
|
|
// Third one: whether it's opted out from unload deprecation or not.
|
|
INSTANTIATE_TEST_SUITE_P(
|
|
All,
|
|
BackForwardCacheBrowserUnloadHandlerTest,
|
|
::testing::Combine(::testing::Bool(),
|
|
::testing::Bool(),
|
|
::testing::Bool(),
|
|
::testing::Values(TestFrameType::kMainFrame,
|
|
TestFrameType::kSubFrame)));
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, DisableForRenderFrameHost) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title2.html"));
|
|
|
|
// 1) Navigate to A.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostWrapper rfh_wrapper_a(current_frame_host());
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
RenderFrameHostWrapper rfh_wrapper_b(current_frame_host());
|
|
|
|
// Regardless of whether the source Id is set or not, it shouldn't affect the
|
|
// result of the BFCache eviction.
|
|
BackForwardCache::DisabledReason test_reason =
|
|
BackForwardCacheDisable::DisabledReason(
|
|
BackForwardCacheDisable::DisabledReasonId::kUnknown);
|
|
|
|
// 3) Disable BFCache for A with UKM source Id and go back.
|
|
BackForwardCache::DisableForRenderFrameHost(
|
|
rfh_wrapper_a.get(), test_reason, ukm::UkmRecorder::GetNewSourceID());
|
|
ASSERT_TRUE(HistoryGoBack(web_contents()));
|
|
ASSERT_TRUE(rfh_wrapper_a.WaitUntilRenderFrameDeleted());
|
|
// Page A should be evicted properly.
|
|
ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason::
|
|
kDisableForRenderFrameHostCalled},
|
|
{}, {}, {test_reason}, {}, FROM_HERE);
|
|
|
|
// 4) Disable BFCache for B without UKM source Id and go forward.
|
|
BackForwardCache::DisableForRenderFrameHost(rfh_wrapper_b.get(), test_reason);
|
|
ASSERT_TRUE(HistoryGoForward(web_contents()));
|
|
ASSERT_TRUE(rfh_wrapper_b.WaitUntilRenderFrameDeleted());
|
|
// Page B should be evicted properly.
|
|
ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason::
|
|
kDisableForRenderFrameHostCalled},
|
|
{}, {}, {test_reason}, {}, FROM_HERE);
|
|
}
|
|
|
|
namespace {
|
|
enum class SubframeType { SameSite, CrossSite };
|
|
}
|
|
|
|
class BackForwardCacheEvictionDueToSubframeNavigationBrowserTest
|
|
: public BackForwardCacheBrowserTest,
|
|
public ::testing::WithParamInterface<SubframeType> {
|
|
public:
|
|
// Provides meaningful param names instead of /0 and /1.
|
|
static std::string DescribeParams(
|
|
const ::testing::TestParamInfo<ParamType>& info) {
|
|
switch (info.param) {
|
|
case SubframeType::SameSite:
|
|
return "SameSite";
|
|
case SubframeType::CrossSite:
|
|
return "CrossSite";
|
|
}
|
|
}
|
|
|
|
protected:
|
|
bool UseCrossOriginSubframe() const {
|
|
return GetParam() == SubframeType::CrossSite;
|
|
}
|
|
};
|
|
|
|
IN_PROC_BROWSER_TEST_P(
|
|
BackForwardCacheEvictionDueToSubframeNavigationBrowserTest,
|
|
SubframePendingCommitShouldPreventCache) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
GURL subframe_url = embedded_test_server()->GetURL(
|
|
UseCrossOriginSubframe() ? "b.com" : "a.com", "/title1.html");
|
|
|
|
IsolateOriginsForTesting(embedded_test_server(), web_contents(),
|
|
std::vector<std::string>{"a.com", "b.com"});
|
|
|
|
// 1) Navigate to a.com.
|
|
EXPECT_TRUE(NavigateToURL(shell(), a_url));
|
|
RenderFrameHostImpl* main_frame = current_frame_host();
|
|
|
|
// 2) Add subframe and wait for empty document to commit.
|
|
CreateSubframe(web_contents(), "child", GURL(""), true);
|
|
|
|
CommitMessageDelayer commit_message_delayer(
|
|
web_contents(), subframe_url,
|
|
base::BindLambdaForTesting([&](RenderFrameHost*) {
|
|
// 5) Test that page cannot be stored in bfcache when subframe is
|
|
// pending commit.
|
|
BackForwardCacheCanStoreDocumentResultWithTree can_store_result =
|
|
web_contents()
|
|
->GetController()
|
|
.GetBackForwardCache()
|
|
.GetCurrentBackForwardCacheEligibility(
|
|
static_cast<RenderFrameHostImpl*>(main_frame));
|
|
EXPECT_TRUE(can_store_result.flattened_reasons.HasNotRestoredReason(
|
|
BackForwardCacheMetrics::NotRestoredReason::kSubframeIsNavigating));
|
|
}));
|
|
|
|
// 3) Start navigation in subframe to |subframe_url|.
|
|
ExecuteScriptAsync(
|
|
main_frame,
|
|
JsReplace("document.querySelector('#child').src = $1;", subframe_url));
|
|
// 4) Wait until subframe navigation is pending commit.
|
|
commit_message_delayer.Wait();
|
|
}
|
|
|
|
// Check that when the main frame gets BFCached while the subframe navigation
|
|
// deferring NavigationThrottles has already ran, the issue that the subframe
|
|
// navigation escapes the throttle deferral is addressed.
|
|
IN_PROC_BROWSER_TEST_P(
|
|
BackForwardCacheEvictionDueToSubframeNavigationBrowserTest,
|
|
MainFrameCommitFirstAndSubframePendingCommitShouldBeEvicted) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
// Prepare the main frame and the sub frame, where both of them are same-site.
|
|
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);
|
|
RenderFrameHostImplWrapper child_rfh(child->current_frame_host());
|
|
|
|
// Navigate both frames simultaneously.
|
|
const std::string subframe_origin =
|
|
UseCrossOriginSubframe() ? "b.com" : "a.com";
|
|
GURL new_url_1(
|
|
embedded_test_server()->GetURL(subframe_origin, "/title1.html"));
|
|
GURL new_url_2(
|
|
embedded_test_server()->GetURL(subframe_origin, "/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.
|
|
ASSERT_TRUE(manager1.WaitForRequestStart());
|
|
|
|
// Wait for subframe request, but don't commit it yet.
|
|
ASSERT_TRUE(manager2.WaitForRequestStart());
|
|
|
|
// Now let the main frame commit.
|
|
ASSERT_TRUE(manager1.WaitForNavigationFinished());
|
|
// Make sure the main frame is at the new URL.
|
|
ASSERT_TRUE(root->current_frame_host()->IsRenderFrameLive());
|
|
ASSERT_EQ(new_url_1, root->current_frame_host()->GetLastCommittedURL());
|
|
|
|
// The subframe should be gone now: it should have been evicted from BFCache
|
|
// because the subframe is still navigating; otherwise, this will cause a
|
|
// crash.
|
|
ASSERT_TRUE(manager2.WaitForNavigationFinished());
|
|
EXPECT_TRUE(child_rfh.IsDestroyed());
|
|
ASSERT_TRUE(HistoryGoBack(web_contents()));
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kSubframeIsNavigating}, {},
|
|
{}, {}, {}, FROM_HERE);
|
|
}
|
|
|
|
INSTANTIATE_TEST_SUITE_P(
|
|
All,
|
|
BackForwardCacheEvictionDueToSubframeNavigationBrowserTest,
|
|
::testing::Values(SubframeType::SameSite, SubframeType::CrossSite),
|
|
&BackForwardCacheEvictionDueToSubframeNavigationBrowserTest::
|
|
DescribeParams);
|
|
|
|
namespace {
|
|
enum class SubframeNavigationType { WithoutURLLoader, WithURLLoader };
|
|
}
|
|
|
|
// Test for pages which has subframe(s) with ongoing navigation(s).
|
|
class BackForwardCacheWithSubframeNavigationBrowserTest
|
|
: public BackForwardCacheBrowserTest {
|
|
protected:
|
|
void SetUpOnMainThread() override {
|
|
BackForwardCacheBrowserTest::SetUpOnMainThread();
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
}
|
|
|
|
void SetUpCommandLine(base::CommandLine* command_line) override {
|
|
EnableFeatureAndSetParams(features::kBackForwardCache, "cache_size",
|
|
base::NumberToString(2));
|
|
BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
|
|
}
|
|
|
|
// Start a subframe navigation and pause it when we get the confirmation
|
|
// dialog triggered by beforeunload event, which is before
|
|
// WillCommitWithoutUrlLoader or WillStartRequest.
|
|
void NavigateSubframeAndPauseAtBeforeUnload(
|
|
BeforeUnloadBlockingDelegate& beforeunload_pauser,
|
|
RenderFrameHostImpl* sub_rfh,
|
|
const GURL& subframe_navigate_url,
|
|
std::string_view iframe_id) {
|
|
ASSERT_TRUE(ExecJs(sub_rfh, R"(
|
|
window.addEventListener('beforeunload', e =>
|
|
e.returnValue='blocked'
|
|
);)"));
|
|
|
|
// Start a subframe navigation which will trigger the beforeunload dialog
|
|
// that pauses that navigation. Using `BeginNavigateIframeToURL` is
|
|
// necessary here, since we pause this navigation on beforeunload event. So,
|
|
// we don't want to wait for the navigation to finish.
|
|
BeginNavigateIframeToURL(web_contents(), iframe_id, subframe_navigate_url);
|
|
beforeunload_pauser.Wait();
|
|
}
|
|
|
|
// Start a subframe navigation and pause it before `DidCommitNavigation`.
|
|
void NavigateSubframeAndPauseAtDidCommit(FrameTreeNode* ftn,
|
|
const GURL& subframe_navigate_url) {
|
|
// Enforce the creation of speculative RFH to correctly wait for
|
|
// the commit event.
|
|
SpeculativeRenderFrameHostObserver observer(web_contents(),
|
|
subframe_navigate_url);
|
|
// We have to pause a navigation before `DidCommitNavigation`, so we don't
|
|
// want to wait for the navigation to finish.
|
|
ASSERT_TRUE(BeginNavigateToURLFromRenderer(ftn, subframe_navigate_url));
|
|
// Navigation without a URL loader shall always create the speculative RFH
|
|
// immediately or never create one.
|
|
// The same-site navigation to the subframe will not create a new
|
|
// speculative RFH if render document is not enabled for subframes.
|
|
if (subframe_navigate_url.SchemeIsHTTPOrHTTPS() &&
|
|
ShouldCreateNewRenderFrameHostOnSameSiteNavigation(
|
|
/*is_main_frame=*/false, /*is_local_root=*/true)) {
|
|
observer.Wait();
|
|
}
|
|
|
|
// Wait until the navigation is pending commit. Note that the navigation
|
|
// might use a speculative RenderFrameHost, so use that if necessary.
|
|
RenderFrameHostImpl* speculative_rfh =
|
|
ftn->render_manager()->speculative_frame_host();
|
|
CommitNavigationPauser commit_pauser(
|
|
speculative_rfh ? speculative_rfh : ftn->current_frame_host());
|
|
commit_pauser.WaitForCommitAndPause();
|
|
}
|
|
|
|
// Put a page which has a subframe with a navigation which hasn't reached the
|
|
// "pending commit" stage nor sent a network request into BackForwardCache and
|
|
// confirm the subframe navigation has been deferred.
|
|
void BFCachePageWithSubframeNavigationBeforeDidStartNavigation(
|
|
const GURL& main_frame_navigate_url,
|
|
const GURL& subframe_navigate_url,
|
|
RenderFrameHostImplWrapper& sub_rfh,
|
|
TestNavigationManager& subframe_navigation_manager,
|
|
std::string_view iframe_id) {
|
|
FrameTreeNode* child_ftn =
|
|
web_contents()->GetPrimaryFrameTree().root()->child_at(0);
|
|
{
|
|
BeforeUnloadBlockingDelegate beforeunload_pauser(web_contents());
|
|
NavigateSubframeAndPauseAtBeforeUnload(beforeunload_pauser, sub_rfh.get(),
|
|
subframe_navigate_url, iframe_id);
|
|
|
|
// Subframe navigation is ongoing, so `NavigateToURL` cannot be used since
|
|
// this function waits for all frames including subframe to finish
|
|
// loading.
|
|
ASSERT_TRUE(NavigateToURLFromRenderer(sub_rfh->GetMainFrame(),
|
|
main_frame_navigate_url));
|
|
|
|
// The subframe navigation hasn't reached the "pending commit" stage nor
|
|
// sent a network request, so the page is eligible for BackForwardCache.
|
|
EXPECT_TRUE(sub_rfh->GetMainFrame()->IsInBackForwardCache());
|
|
EXPECT_TRUE(sub_rfh->IsInBackForwardCache());
|
|
}
|
|
web_contents()->SetDelegate(shell());
|
|
|
|
// Wait until the subframe navigation is deferred.
|
|
ASSERT_TRUE(
|
|
subframe_navigation_manager.WaitForFirstYieldAfterDidStartNavigation());
|
|
NavigationRequest* child_navigation = child_ftn->navigation_request();
|
|
ASSERT_NE(child_navigation, nullptr);
|
|
EXPECT_TRUE(child_navigation->IsDeferredForTesting());
|
|
}
|
|
};
|
|
|
|
class BackForwardCacheWithSubframeNavigationWithParamBrowserTest
|
|
: public BackForwardCacheWithSubframeNavigationBrowserTest,
|
|
public ::testing::WithParamInterface<SubframeNavigationType> {
|
|
public:
|
|
// Provides meaningful param names instead of /0 and /1.
|
|
static std::string DescribeParams(
|
|
const ::testing::TestParamInfo<ParamType>& info) {
|
|
switch (info.param) {
|
|
case SubframeNavigationType::WithoutURLLoader:
|
|
return "WithoutURLLoader";
|
|
case SubframeNavigationType::WithURLLoader:
|
|
return "WithURLLoader";
|
|
}
|
|
}
|
|
};
|
|
|
|
// Confirm that BackForwardCache is blocked when there is only 1 navigation and
|
|
// it's pending commit.
|
|
IN_PROC_BROWSER_TEST_P(
|
|
BackForwardCacheWithSubframeNavigationWithParamBrowserTest,
|
|
SubframeNavigationWithPendingCommitShouldPreventCache) {
|
|
const GURL main_url(embedded_test_server()->GetURL(
|
|
"a.com", "/cross_site_iframe_factory.html?a(b)"));
|
|
const GURL subframe_url = embedded_test_server()->GetURL(
|
|
"b.com", "/cross_site_iframe_factory.html?b()");
|
|
const GURL navigate_url(
|
|
embedded_test_server()->GetURL("c.com", "/title1.html"));
|
|
const GURL subframe_navigate_url =
|
|
GetParam() == SubframeNavigationType::WithURLLoader
|
|
? embedded_test_server()->GetURL("b.com", "/title1.html")
|
|
: GURL("about:blank");
|
|
|
|
// Navigate to a page with a cross site iframe.
|
|
ASSERT_TRUE(NavigateToURL(shell(), main_url));
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
RenderFrameHostImplWrapper main_rfh(current_frame_host());
|
|
FrameTreeNode* child_node = main_rfh.get()->child_at(0);
|
|
RenderFrameHostImplWrapper sub_rfh(child_node->current_frame_host());
|
|
|
|
// Pause subframe's navigation before `DidCommitNavigation`.
|
|
NavigateSubframeAndPauseAtDidCommit(child_node, subframe_navigate_url);
|
|
|
|
// Subframe navigation is ongoing, so `NavigateToURL` cannot be used since
|
|
// this function waits for all frames including subframe to finish loading.
|
|
ASSERT_TRUE(NavigateToURLFromRenderer(main_rfh.get(), navigate_url));
|
|
|
|
// Subframe navigation has reached the "pending commit" stage, so the page is
|
|
// not eligible for BackForwardCache.
|
|
EXPECT_TRUE(main_rfh.WaitUntilRenderFrameDeleted());
|
|
EXPECT_TRUE(sub_rfh.WaitUntilRenderFrameDeleted());
|
|
|
|
// Navigate back.
|
|
ASSERT_TRUE(HistoryGoBack(web_contents()));
|
|
ExpectNotRestored({NotRestoredReason::kSubframeIsNavigating}, {}, {}, {}, {},
|
|
FROM_HERE);
|
|
|
|
// Confirm that subframe's url didn't change.
|
|
EXPECT_EQ(subframe_url, current_frame_host()->child_at(0)->current_url());
|
|
}
|
|
|
|
// Confirm that BackForwardCache is blocked when there are 2 navigations, 1 not
|
|
// pending commit yet, and 1 pending commit.
|
|
IN_PROC_BROWSER_TEST_F(
|
|
BackForwardCacheWithSubframeNavigationBrowserTest,
|
|
MultipleSubframeNavigationWithBeforeAndPendingCommitShouldPreventCache) {
|
|
// This test relies on the main frame and the iframe to live in different
|
|
// processes. This allows one renderer process to proceed a navigation while
|
|
// the other renderer process is busy executing its beforeunload handler.
|
|
if (!AreAllSitesIsolatedForTesting()) {
|
|
GTEST_SKIP() << "Site isolation is not enabled!";
|
|
}
|
|
const GURL main_url(embedded_test_server()->GetURL(
|
|
"a.com", "/cross_site_iframe_factory.html?a(b,c)"));
|
|
const GURL subframe_b_url = embedded_test_server()->GetURL(
|
|
"b.com", "/cross_site_iframe_factory.html?b()");
|
|
const GURL subframe_c_url = embedded_test_server()->GetURL(
|
|
"c.com", "/cross_site_iframe_factory.html?c()");
|
|
const GURL navigate_url(
|
|
embedded_test_server()->GetURL("d.com", "/title1.html"));
|
|
const GURL subframe_navigate_url = GURL("about:blank");
|
|
|
|
// Navigate to a page with two cross site iframes.
|
|
ASSERT_TRUE(NavigateToURL(shell(), main_url));
|
|
RenderFrameHostImplWrapper main_rfh(current_frame_host());
|
|
RenderFrameHostImplWrapper sub_rfh_b(
|
|
main_rfh.get()->child_at(0)->current_frame_host());
|
|
RenderFrameHostImplWrapper sub_rfh_c(
|
|
main_rfh.get()->child_at(1)->current_frame_host());
|
|
|
|
{
|
|
// The subframe_b itself does have a dialog-showing beforeunload handler.
|
|
// Pause subframe_b's navigation when we get the confirmation dialog
|
|
// triggered by beforeunload event.
|
|
BeforeUnloadBlockingDelegate beforeunload_pauser(web_contents());
|
|
NavigateSubframeAndPauseAtBeforeUnload(beforeunload_pauser, sub_rfh_b.get(),
|
|
subframe_navigate_url,
|
|
/*iframe_id=*/"child-0");
|
|
|
|
// Pause subframe_c's navigation before `DidCommitNavigation`.
|
|
NavigateSubframeAndPauseAtDidCommit(main_rfh.get()->child_at(1),
|
|
subframe_navigate_url);
|
|
|
|
// Subframe navigation is ongoing, so `NavigateToURL` cannot be used since
|
|
// this function waits for all frames including subframe to finish loading.
|
|
ASSERT_TRUE(NavigateToURLFromRenderer(main_rfh.get(), navigate_url));
|
|
|
|
// The subframe_c's navigation already started committing, so the page is
|
|
// not eligible for BackForwardCache.
|
|
EXPECT_TRUE(main_rfh.WaitUntilRenderFrameDeleted());
|
|
EXPECT_TRUE(sub_rfh_b.WaitUntilRenderFrameDeleted());
|
|
EXPECT_TRUE(sub_rfh_c.WaitUntilRenderFrameDeleted());
|
|
}
|
|
web_contents()->SetDelegate(shell());
|
|
|
|
// Navigate back.
|
|
ASSERT_TRUE(HistoryGoBack(web_contents()));
|
|
ExpectNotRestored({NotRestoredReason::kSubframeIsNavigating}, {}, {}, {}, {},
|
|
FROM_HERE);
|
|
|
|
// Confirm that subframe's url didn't change.
|
|
EXPECT_EQ(subframe_b_url, current_frame_host()->child_at(0)->current_url());
|
|
EXPECT_EQ(subframe_c_url, current_frame_host()->child_at(1)->current_url());
|
|
}
|
|
|
|
// Confirm that BackForwardCache is blocked when there are 2 navigations, 1 has
|
|
// not sent a network request yet, and 1 has already sent request.
|
|
IN_PROC_BROWSER_TEST_F(
|
|
BackForwardCacheWithSubframeNavigationBrowserTest,
|
|
MultipleSubframeNavigationWithBeforeAndAfterSendingRequestShouldPreventCache) {
|
|
// This test relies on the main frame and the iframe to live in different
|
|
// processes. This allows one renderer process to proceed a navigation while
|
|
// the other renderer process is busy executing its beforeunload handler.
|
|
if (!AreAllSitesIsolatedForTesting()) {
|
|
GTEST_SKIP() << "Site isolation is not enabled!";
|
|
}
|
|
const GURL main_url(embedded_test_server()->GetURL(
|
|
"a.com", "/cross_site_iframe_factory.html?a(b,c)"));
|
|
const GURL subframe_b_url = embedded_test_server()->GetURL(
|
|
"b.com", "/cross_site_iframe_factory.html?b()");
|
|
const GURL subframe_c_url = embedded_test_server()->GetURL(
|
|
"c.com", "/cross_site_iframe_factory.html?c()");
|
|
const GURL navigate_url(
|
|
embedded_test_server()->GetURL("d.com", "/title1.html"));
|
|
const GURL subframe_b_navigate_url(
|
|
embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
const GURL subframe_c_navigate_url(
|
|
embedded_test_server()->GetURL("c.com", "/title1.html"));
|
|
|
|
// Navigate to a page with two cross site iframes.
|
|
ASSERT_TRUE(NavigateToURL(shell(), main_url));
|
|
RenderFrameHostImplWrapper main_rfh(current_frame_host());
|
|
RenderFrameHostImplWrapper sub_rfh_b(
|
|
main_rfh.get()->child_at(0)->current_frame_host());
|
|
RenderFrameHostImplWrapper sub_rfh_c(
|
|
main_rfh.get()->child_at(1)->current_frame_host());
|
|
|
|
// Pause a subframe_b navigation on `WillStartRequest` before sending a
|
|
// network request.
|
|
TestNavigationManager subframe_b_navigation_manager(web_contents(),
|
|
subframe_b_navigate_url);
|
|
ASSERT_TRUE(
|
|
BeginNavigateToURLFromRenderer(sub_rfh_b.get(), subframe_b_navigate_url));
|
|
ASSERT_TRUE(subframe_b_navigation_manager.WaitForRequestStart());
|
|
|
|
// Pause a subframe_c navigation on `WillProcessResponse` after sending a
|
|
// network request.
|
|
TestNavigationManager subframe_c_navigation_manager(web_contents(),
|
|
subframe_c_navigate_url);
|
|
ASSERT_TRUE(
|
|
BeginNavigateToURLFromRenderer(sub_rfh_c.get(), subframe_c_navigate_url));
|
|
ASSERT_TRUE(subframe_c_navigation_manager.WaitForResponse());
|
|
|
|
// Subframe navigation is ongoing, so `NavigateToURL` cannot be used since
|
|
// this function waits for all frames including subframe to finish loading.
|
|
ASSERT_TRUE(NavigateToURLFromRenderer(main_rfh.get(), navigate_url));
|
|
|
|
// The subframe_c's navigation has already sent a network request, so the page
|
|
// is not eligible for BackForwardCache.
|
|
EXPECT_TRUE(main_rfh.WaitUntilRenderFrameDeleted());
|
|
EXPECT_TRUE(sub_rfh_b.WaitUntilRenderFrameDeleted());
|
|
EXPECT_TRUE(sub_rfh_c.WaitUntilRenderFrameDeleted());
|
|
EXPECT_TRUE(subframe_b_navigation_manager.WaitForNavigationFinished());
|
|
EXPECT_TRUE(subframe_c_navigation_manager.WaitForNavigationFinished());
|
|
EXPECT_FALSE(subframe_b_navigation_manager.was_committed());
|
|
EXPECT_FALSE(subframe_c_navigation_manager.was_committed());
|
|
|
|
// Navigate back.
|
|
ASSERT_TRUE(HistoryGoBack(web_contents()));
|
|
ExpectNotRestored({NotRestoredReason::kSubframeIsNavigating}, {}, {}, {}, {},
|
|
FROM_HERE);
|
|
|
|
// Confirm that subframe's url didn't change.
|
|
EXPECT_EQ(subframe_b_url, current_frame_host()->child_at(0)->current_url());
|
|
EXPECT_EQ(subframe_c_url, current_frame_host()->child_at(1)->current_url());
|
|
}
|
|
|
|
// Confirm that subframe navigation which needs url loader that has already sent
|
|
// a network request should block BackForwardCache.
|
|
IN_PROC_BROWSER_TEST_F(
|
|
BackForwardCacheWithSubframeNavigationBrowserTest,
|
|
SubframeNavigationWithUrlLoaderAfterSendingRequestShouldPreventCache) {
|
|
const GURL main_url(embedded_test_server()->GetURL(
|
|
"a.com", "/cross_site_iframe_factory.html?a(b)"));
|
|
const GURL subframe_url = embedded_test_server()->GetURL(
|
|
"b.com", "/cross_site_iframe_factory.html?b()");
|
|
const GURL navigate_url(
|
|
embedded_test_server()->GetURL("c.com", "/title1.html"));
|
|
const GURL subframe_navigate_url(
|
|
embedded_test_server()->GetURL("b.com", "/title2.html"));
|
|
|
|
// Navigate to a page with a cross site iframe.
|
|
ASSERT_TRUE(NavigateToURL(shell(), main_url));
|
|
RenderFrameHostImplWrapper main_rfh(current_frame_host());
|
|
RenderFrameHostImplWrapper sub_rfh(
|
|
main_rfh.get()->child_at(0)->current_frame_host());
|
|
TestNavigationManager subframe_navigation_manager(web_contents(),
|
|
subframe_navigate_url);
|
|
ASSERT_TRUE(
|
|
BeginNavigateToURLFromRenderer(sub_rfh.get(), subframe_navigate_url));
|
|
|
|
// Pause the subframe navigation on `WillProcessResponse`.
|
|
ASSERT_TRUE(subframe_navigation_manager.WaitForResponse());
|
|
|
|
// Subframe navigation is ongoing, so `NavigateToURL` cannot be used since
|
|
// this function waits for all frames including subframe to finish loading.
|
|
ASSERT_TRUE(NavigateToURLFromRenderer(main_rfh.get(), navigate_url));
|
|
|
|
// Subframe navigation has already sent a network request, so the page is not
|
|
// eligible for BackForwardCache.
|
|
EXPECT_TRUE(main_rfh.WaitUntilRenderFrameDeleted());
|
|
EXPECT_TRUE(sub_rfh.WaitUntilRenderFrameDeleted());
|
|
EXPECT_FALSE(subframe_navigation_manager.was_committed());
|
|
|
|
// Navigate back.
|
|
ASSERT_TRUE(HistoryGoBack(web_contents()));
|
|
ExpectNotRestored({NotRestoredReason::kSubframeIsNavigating}, {}, {}, {}, {},
|
|
FROM_HERE);
|
|
|
|
// Confirm that subframe's url didn't change.
|
|
EXPECT_EQ(subframe_url, current_frame_host()->child_at(0)->current_url());
|
|
}
|
|
|
|
// Confirm that subframe navigation which needs url loader that hasn't sent a
|
|
// network request should not block BackForwardCache.
|
|
IN_PROC_BROWSER_TEST_F(
|
|
BackForwardCacheWithSubframeNavigationBrowserTest,
|
|
SubframeNavigationWithUrlLoaderBeforeSendingRequestShouldNotPreventCache) {
|
|
// This test relies on the main frame and the iframe to live in different
|
|
// processes. This allows one renderer process to proceed a navigation while
|
|
// the other renderer process is busy executing its beforeunload handler.
|
|
if (!AreAllSitesIsolatedForTesting()) {
|
|
GTEST_SKIP() << "Site isolation is not enabled!";
|
|
}
|
|
const GURL main_url(embedded_test_server()->GetURL(
|
|
"a.com", "/cross_site_iframe_factory.html?a(b)"));
|
|
const GURL subframe_url = embedded_test_server()->GetURL(
|
|
"b.com", "/cross_site_iframe_factory.html?b()");
|
|
const GURL navigate_url(
|
|
embedded_test_server()->GetURL("c.com", "/title1.html"));
|
|
const GURL subframe_navigate_url(
|
|
embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// Navigate to a page with a cross site iframe.
|
|
ASSERT_TRUE(NavigateToURL(shell(), main_url));
|
|
RenderFrameHostImplWrapper main_rfh(current_frame_host());
|
|
RenderFrameHostImplWrapper sub_rfh(
|
|
main_rfh.get()->child_at(0)->current_frame_host());
|
|
|
|
// Put a page which has a subframe with a URLLoader navigation which hasn't
|
|
// sent a network request into BackForwardCache. The iframe itself
|
|
// does have a dialog-showing beforeunload handler.
|
|
TestNavigationManager subframe_navigation_manager(web_contents(),
|
|
subframe_navigate_url);
|
|
BFCachePageWithSubframeNavigationBeforeDidStartNavigation(
|
|
navigate_url, subframe_navigate_url, sub_rfh, subframe_navigation_manager,
|
|
/*iframe_id=*/"child-0");
|
|
|
|
// Navigate back.
|
|
TestNavigationObserver back_load_observer(shell()->web_contents());
|
|
web_contents()->GetController().GoBack();
|
|
back_load_observer.WaitForNavigationFinished();
|
|
ASSERT_FALSE(main_rfh->IsInBackForwardCache());
|
|
|
|
// Wait until the resumed subframe navigation finishes.
|
|
EXPECT_TRUE(subframe_navigation_manager.WaitForNavigationFinished());
|
|
EXPECT_TRUE(subframe_navigation_manager.was_successful());
|
|
EXPECT_EQ(subframe_navigate_url,
|
|
current_frame_host()->child_at(0)->current_url());
|
|
}
|
|
|
|
// Confirm that subframe no-url loader navigation (e.g., about:blank) in
|
|
// bfcached page is deferred and then resumed when the page is navigated back.
|
|
IN_PROC_BROWSER_TEST_F(
|
|
BackForwardCacheWithSubframeNavigationBrowserTest,
|
|
SubframeNavigationWithoutUrlLoaderBeforeCommitShouldNotPreventCache) {
|
|
// This test relies on the main frame and the iframe to live in different
|
|
// processes. This allows one renderer process to proceed a navigation while
|
|
// the other renderer process is busy executing its beforeunload handler.
|
|
if (!AreAllSitesIsolatedForTesting()) {
|
|
GTEST_SKIP() << "Site isolation is not enabled!";
|
|
}
|
|
const GURL main_url(embedded_test_server()->GetURL(
|
|
"a.com", "/cross_site_iframe_factory.html?a(b)"));
|
|
const GURL navigate_url(
|
|
embedded_test_server()->GetURL("c.com", "/title1.html"));
|
|
const GURL subframe_navigate_url = GURL("about:blank");
|
|
|
|
// Navigate to a page with a cross site iframe.
|
|
ASSERT_TRUE(NavigateToURL(shell(), main_url));
|
|
RenderFrameHostImplWrapper main_rfh(current_frame_host());
|
|
RenderFrameHostImplWrapper sub_rfh(
|
|
main_rfh.get()->child_at(0)->current_frame_host());
|
|
|
|
// Put a page which has a subframe with a no-URLLoader navigation which hasn't
|
|
// reached the "pending commit" stage into BackForwardCache. The iframe itself
|
|
// does have a dialog-showing beforeunload handler.
|
|
TestNavigationManager subframe_navigation_manager(web_contents(),
|
|
subframe_navigate_url);
|
|
BFCachePageWithSubframeNavigationBeforeDidStartNavigation(
|
|
navigate_url, subframe_navigate_url, sub_rfh, subframe_navigation_manager,
|
|
/*iframe_id=*/"child-0");
|
|
|
|
// Navigate back.
|
|
ASSERT_TRUE(HistoryGoBack(web_contents()));
|
|
ASSERT_FALSE(main_rfh->IsInBackForwardCache());
|
|
|
|
// Confirm the deferred navigation was resumed and subframe's url changed.
|
|
EXPECT_TRUE(subframe_navigation_manager.WaitForNavigationFinished());
|
|
EXPECT_TRUE(subframe_navigation_manager.was_successful());
|
|
EXPECT_EQ(subframe_navigate_url,
|
|
current_frame_host()->child_at(0)->current_url());
|
|
}
|
|
|
|
// Confirm that we don't resume a subframe navigation when an unrelated BFCached
|
|
// page gets restored.
|
|
IN_PROC_BROWSER_TEST_P(
|
|
BackForwardCacheWithSubframeNavigationWithParamBrowserTest,
|
|
SubframeNavigationShouldNotBeResumedWhenUnrelatedPageRestored) {
|
|
// This test relies on the main frame and the iframe to live in different
|
|
// processes. This allows one renderer process to proceed a navigation while
|
|
// the other renderer process is busy executing its beforeunload handler.
|
|
if (!AreAllSitesIsolatedForTesting()) {
|
|
GTEST_SKIP() << "Site isolation is not enabled!";
|
|
}
|
|
const GURL main_url_a(embedded_test_server()->GetURL(
|
|
"a.com", "/cross_site_iframe_factory.html?a(b)"));
|
|
const GURL navigate_url_c(
|
|
embedded_test_server()->GetURL("c.com", "/title1.html"));
|
|
const GURL navigate_url_d(
|
|
embedded_test_server()->GetURL("d.com", "/title1.html"));
|
|
const GURL subframe_navigate_url =
|
|
GetParam() == SubframeNavigationType::WithURLLoader
|
|
? embedded_test_server()->GetURL("b.com", "/title1.html")
|
|
: GURL("about:blank");
|
|
|
|
// Navigate to a page with a cross site iframe.
|
|
ASSERT_TRUE(NavigateToURL(shell(), main_url_a));
|
|
RenderFrameHostImplWrapper main_rfh_a(current_frame_host());
|
|
RenderFrameHostImplWrapper sub_rfh_b(
|
|
main_rfh_a.get()->child_at(0)->current_frame_host());
|
|
|
|
// Put a page which has a subframe with a navigation which hasn't reached the
|
|
// "pending commit" stage or sent a network request into BackForwardCache.
|
|
TestNavigationManager subframe_navigation_manager(web_contents(),
|
|
subframe_navigate_url);
|
|
BFCachePageWithSubframeNavigationBeforeDidStartNavigation(
|
|
navigate_url_c, subframe_navigate_url, sub_rfh_b,
|
|
subframe_navigation_manager,
|
|
/*iframe_id=*/"child-0");
|
|
|
|
// Navigate away.
|
|
// Currently, `main_rfh_a` is in BFCache and we are on `navigate_url_c`. Then,
|
|
// we will navigate to `navigate_url_d` which will put `main_rfh_c` in
|
|
// BFCache.
|
|
RenderFrameHostImplWrapper main_rfh_c(current_frame_host());
|
|
ASSERT_TRUE(NavigateToURL(shell(), navigate_url_d));
|
|
ASSERT_TRUE(main_rfh_c->IsInBackForwardCache());
|
|
|
|
// Navigate back to `main_rfh_c` and restore that from BFCache, while
|
|
// `main_rfh_a` is still in BFCache.
|
|
ASSERT_TRUE(HistoryGoBack(web_contents()));
|
|
ASSERT_EQ(main_rfh_c.get(), current_frame_host());
|
|
ASSERT_TRUE(main_rfh_a->IsInBackForwardCache());
|
|
|
|
// Confirm the subframe's deferred navigation is not committed.
|
|
EXPECT_FALSE(subframe_navigation_manager.was_committed());
|
|
|
|
// Navigate back to `main_rfh_a`.
|
|
TestNavigationObserver back_load_observer(shell()->web_contents());
|
|
web_contents()->GetController().GoBack();
|
|
back_load_observer.WaitForNavigationFinished();
|
|
ASSERT_FALSE(main_rfh_a->IsInBackForwardCache());
|
|
|
|
// Confirm the deferred navigation was resumed and subframe's url changed.
|
|
EXPECT_TRUE(subframe_navigation_manager.WaitForNavigationFinished());
|
|
EXPECT_TRUE(subframe_navigation_manager.was_successful());
|
|
EXPECT_EQ(subframe_navigate_url,
|
|
current_frame_host()->child_at(0)->current_url());
|
|
}
|
|
|
|
// Evict the bfcached page which has a subframe with a deferred navigation and
|
|
// confirm the subframe'url didn't change when the page is navigated back.
|
|
IN_PROC_BROWSER_TEST_P(
|
|
BackForwardCacheWithSubframeNavigationWithParamBrowserTest,
|
|
EvictBFCachedPageWithDeferredSubframeNavigationBeforeCommit) {
|
|
// This test relies on the main frame and the iframe to live in different
|
|
// processes. This allows one renderer process to proceed a navigation while
|
|
// the other renderer process is busy executing its beforeunload handler.
|
|
if (!AreAllSitesIsolatedForTesting()) {
|
|
GTEST_SKIP() << "Site isolation is not enabled!";
|
|
}
|
|
const GURL main_url(embedded_test_server()->GetURL(
|
|
"a.com", "/cross_site_iframe_factory.html?a(b)"));
|
|
const GURL subframe_url = embedded_test_server()->GetURL(
|
|
"b.com", "/cross_site_iframe_factory.html?b()");
|
|
const GURL navigate_url(
|
|
embedded_test_server()->GetURL("c.com", "/title1.html"));
|
|
const GURL subframe_navigate_url =
|
|
GetParam() == SubframeNavigationType::WithURLLoader
|
|
? embedded_test_server()->GetURL("b.com", "/title1.html")
|
|
: GURL("about:blank");
|
|
|
|
// Navigate to a page with a cross site iframe.
|
|
ASSERT_TRUE(NavigateToURL(shell(), main_url));
|
|
RenderFrameHostImplWrapper main_rfh(current_frame_host());
|
|
RenderFrameHostImplWrapper sub_rfh(
|
|
main_rfh.get()->child_at(0)->current_frame_host());
|
|
|
|
// Put a page which has a subframe with a navigation which hasn't reached the
|
|
// "pending commit" stage or sent a network request into BackForwardCache. The
|
|
// iframe itself does have a dialog-showing beforeunload handler.
|
|
TestNavigationManager subframe_navigation_manager(web_contents(),
|
|
subframe_navigate_url);
|
|
BFCachePageWithSubframeNavigationBeforeDidStartNavigation(
|
|
navigate_url, subframe_navigate_url, sub_rfh, subframe_navigation_manager,
|
|
/*iframe_id=*/"child-0");
|
|
|
|
// Flush the cache and evict the previously BFCached page.
|
|
web_contents()->GetController().GetBackForwardCache().Flush();
|
|
ASSERT_TRUE(main_rfh.WaitUntilRenderFrameDeleted());
|
|
ASSERT_TRUE(sub_rfh.WaitUntilRenderFrameDeleted());
|
|
|
|
// Confirm the subframe's deferred navigation has finished and was not
|
|
// committed.
|
|
EXPECT_TRUE(subframe_navigation_manager.WaitForNavigationFinished());
|
|
EXPECT_FALSE(subframe_navigation_manager.was_committed());
|
|
|
|
// Navigate back.
|
|
ASSERT_TRUE(HistoryGoBack(web_contents()));
|
|
|
|
// Confirm that subframe's url didn't change.
|
|
EXPECT_EQ(subframe_url, current_frame_host()->child_at(0)->current_url());
|
|
}
|
|
|
|
INSTANTIATE_TEST_SUITE_P(
|
|
All,
|
|
BackForwardCacheWithSubframeNavigationWithParamBrowserTest,
|
|
::testing::Values(SubframeNavigationType::WithoutURLLoader,
|
|
SubframeNavigationType::WithURLLoader),
|
|
&BackForwardCacheWithSubframeNavigationWithParamBrowserTest::
|
|
DescribeParams);
|
|
|
|
class BackForwardCacheFencedFrameBrowserTest
|
|
: public BackForwardCacheBrowserTest {
|
|
public:
|
|
BackForwardCacheFencedFrameBrowserTest() = default;
|
|
~BackForwardCacheFencedFrameBrowserTest() override = default;
|
|
BackForwardCacheFencedFrameBrowserTest(
|
|
const BackForwardCacheFencedFrameBrowserTest&) = delete;
|
|
|
|
BackForwardCacheFencedFrameBrowserTest& operator=(
|
|
const BackForwardCacheFencedFrameBrowserTest&) = delete;
|
|
|
|
void SetUpCommandLine(base::CommandLine* command_line) override {
|
|
BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
|
|
fenced_frame_helper_ = std::make_unique<test::FencedFrameTestHelper>();
|
|
}
|
|
|
|
test::FencedFrameTestHelper& fenced_frame_test_helper() {
|
|
return *fenced_frame_helper_;
|
|
}
|
|
|
|
private:
|
|
std::unique_ptr<test::FencedFrameTestHelper> fenced_frame_helper_;
|
|
};
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheFencedFrameBrowserTest,
|
|
FencedFramePageNotStoredInBackForwardCache) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_b(
|
|
embedded_test_server()->GetURL("b.com", "/fenced_frames/title1.html"));
|
|
GURL url_c(
|
|
embedded_test_server()->GetURL("c.com", "/fenced_frames/title1.html"));
|
|
|
|
// 1) Navigate to A.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
|
|
// 2) Create a fenced frame.
|
|
content::RenderFrameHostImpl* fenced_frame_host =
|
|
static_cast<content::RenderFrameHostImpl*>(
|
|
fenced_frame_test_helper().CreateFencedFrame(
|
|
web_contents()->GetPrimaryMainFrame(), url_b));
|
|
RenderFrameHostWrapper fenced_frame_host_wrapper(fenced_frame_host);
|
|
|
|
// 3) Navigate to C on the fenced frame host.
|
|
fenced_frame_test_helper().NavigateFrameInFencedFrameTree(fenced_frame_host,
|
|
url_c);
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
if (!fenced_frame_host_wrapper.IsRenderFrameDeleted())
|
|
EXPECT_FALSE(fenced_frame_host->IsInBackForwardCache());
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
RendererInitiatedNavigateToSameUrl) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// 1) Navigate to A.
|
|
ASSERT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImplWrapper rfh_a(current_frame_host());
|
|
|
|
// 2) Navigate to B.
|
|
ASSERT_TRUE(NavigateToURL(shell(), url_b));
|
|
RenderFrameHostImplWrapper rfh_b(current_frame_host());
|
|
|
|
// 3) Navigate to B again, renderer initiated.
|
|
ASSERT_TRUE(NavigateToURLFromRenderer(rfh_b.get(), url_b));
|
|
RenderFrameHostImplWrapper rfh_b2(current_frame_host());
|
|
|
|
// This is treated as replacement, and the previous B page did not get into
|
|
// back/forward cache.
|
|
if (ShouldCreateNewHostForAllFrames()) {
|
|
EXPECT_TRUE(rfh_b.WaitUntilRenderFrameDeleted());
|
|
} else {
|
|
EXPECT_FALSE(rfh_b->IsInBackForwardCache());
|
|
EXPECT_EQ(rfh_b.get(), rfh_b2.get());
|
|
}
|
|
|
|
// 4) Go back. Make sure we go back to A instead of B and restore from
|
|
// bfcache.
|
|
ASSERT_TRUE(HistoryGoBack(shell()->web_contents()));
|
|
EXPECT_EQ(current_frame_host(), rfh_a.get());
|
|
EXPECT_TRUE(rfh_b2.get()->IsInBackForwardCache());
|
|
ExpectRestored(FROM_HERE);
|
|
|
|
// 5) Go forward and restore from bfcache.
|
|
ASSERT_TRUE(HistoryGoForward(shell()->web_contents()));
|
|
EXPECT_EQ(current_frame_host(), rfh_b2.get());
|
|
ExpectRestored(FROM_HERE);
|
|
}
|
|
|
|
// BEFORE ADDING A NEW TEST HERE
|
|
// Read the note at the top about the other files you could add it to.
|
|
} // namespace content
|