
This allows a caller to access all RenderFrameHosts in a given WebContents, including those not descending from the primary main frame, such as bfcached or prerendered RFHs. This is largely implemented in terms of |RenderFrameHostImpl::ForEachRenderFrameHost| where we call this over each of the WebContents' outermost main frames, thereby reaching everything. We also clean up some code that goes from FTN to RFH and back to FTN in PrerenderHost. Bug: 1196715 Change-Id: I1735a2010936cef11b589a4454391ecbe63fedc0 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2892345 Reviewed-by: Alex Moshchuk <alexmos@chromium.org> Commit-Queue: Kevin McNee <mcnee@chromium.org> Cr-Commit-Position: refs/heads/master@{#884497}
11734 lines
461 KiB
C++
11734 lines
461 KiB
C++
// Copyright 2018 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include <memory>
|
|
#include <unordered_map>
|
|
|
|
#include "base/callback_helpers.h"
|
|
#include "base/command_line.h"
|
|
#include "base/hash/hash.h"
|
|
#include "base/location.h"
|
|
#include "base/metrics/metrics_hashes.h"
|
|
#include "base/run_loop.h"
|
|
#include "base/strings/string_piece_forward.h"
|
|
#include "base/strings/stringprintf.h"
|
|
#include "base/system/sys_info.h"
|
|
#include "base/task/common/task_annotator.h"
|
|
#include "base/task/post_task.h"
|
|
#include "base/test/bind.h"
|
|
#include "base/test/scoped_feature_list.h"
|
|
#include "base/test/test_mock_time_task_runner.h"
|
|
#include "base/test/test_timeouts.h"
|
|
#include "base/threading/thread_restrictions.h"
|
|
#include "base/threading/thread_task_runner_handle.h"
|
|
#include "base/time/time.h"
|
|
#include "base/trace_event/trace_log.h"
|
|
#include "build/build_config.h"
|
|
#include "build/chromeos_buildflags.h"
|
|
#include "components/network_session_configurator/common/network_switches.h"
|
|
#include "components/ukm/test_ukm_recorder.h"
|
|
#include "content/browser/bad_message.h"
|
|
#include "content/browser/generic_sensor/sensor_provider_proxy_impl.h"
|
|
#include "content/browser/presentation/presentation_test_utils.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/navigation_request.h"
|
|
#include "content/browser/renderer_host/page_lifecycle_state_manager.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/file_chooser_impl.h"
|
|
#include "content/browser/web_contents/web_contents_impl.h"
|
|
#include "content/common/content_navigation_policy.h"
|
|
#include "content/common/render_accessibility.mojom.h"
|
|
#include "content/public/browser/back_forward_cache.h"
|
|
#include "content/public/browser/frame_service_base.h"
|
|
#include "content/public/browser/global_routing_id.h"
|
|
#include "content/public/browser/idle_manager.h"
|
|
#include "content/public/browser/media_session.h"
|
|
#include "content/public/browser/navigation_handle.h"
|
|
#include "content/public/browser/render_frame_host.h"
|
|
#include "content/public/browser/site_isolation_policy.h"
|
|
#include "content/public/browser/web_contents.h"
|
|
#include "content/public/browser/web_contents_observer.h"
|
|
#include "content/public/common/content_features.h"
|
|
#include "content/public/common/content_switches.h"
|
|
#include "content/public/test/back_forward_cache_util.h"
|
|
#include "content/public/test/browser_test.h"
|
|
#include "content/public/test/browser_test_utils.h"
|
|
#include "content/public/test/content_browser_test.h"
|
|
#include "content/public/test/content_browser_test_utils.h"
|
|
#include "content/public/test/idle_test_utils.h"
|
|
#include "content/public/test/mock_web_contents_observer.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/shell/browser/shell.h"
|
|
#include "content/shell/browser/shell_javascript_dialog_manager.h"
|
|
#include "content/test/content_browser_test_utils_internal.h"
|
|
#include "content/test/echo.test-mojom.h"
|
|
#include "content/test/web_contents_observer_test_utils.h"
|
|
#include "device/bluetooth/bluetooth_adapter_factory.h"
|
|
#include "device/bluetooth/test/mock_bluetooth_adapter.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 "net/test/spawned_test_server/spawned_test_server.h"
|
|
#include "net/test/test_data_directory.h"
|
|
#include "services/device/public/cpp/test/fake_sensor_and_provider.h"
|
|
#include "services/device/public/cpp/test/scoped_geolocation_overrider.h"
|
|
#include "services/device/public/mojom/vibration_manager.mojom.h"
|
|
#include "services/service_manager/public/cpp/interface_provider.h"
|
|
#include "testing/gmock/include/gmock/gmock.h"
|
|
#include "third_party/abseil-cpp/absl/types/optional.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/scheduler/web_scheduler_tracked_feature.h"
|
|
#include "third_party/blink/public/common/switches.h"
|
|
#include "third_party/blink/public/mojom/app_banner/app_banner.mojom.h"
|
|
|
|
using testing::_;
|
|
using testing::Each;
|
|
using testing::ElementsAre;
|
|
using testing::Not;
|
|
using testing::UnorderedElementsAreArray;
|
|
|
|
namespace content {
|
|
|
|
namespace {
|
|
|
|
// hash for std::unordered_map.
|
|
struct FeatureHash {
|
|
size_t operator()(base::Feature feature) const {
|
|
return base::FastHash(feature.name);
|
|
}
|
|
};
|
|
|
|
// compare operator for std::unordered_map.
|
|
struct FeatureEqualOperator {
|
|
bool operator()(base::Feature feature1, base::Feature feature2) const {
|
|
return std::strcmp(feature1.name, feature2.name) == 0;
|
|
}
|
|
};
|
|
|
|
// Test about the BackForwardCache.
|
|
class BackForwardCacheBrowserTest : public ContentBrowserTest,
|
|
public WebContentsObserver {
|
|
public:
|
|
~BackForwardCacheBrowserTest() override {
|
|
if (fail_for_unexpected_messages_while_cached_) {
|
|
// If this is triggered, see
|
|
// tools/metrics/histograms/histograms_xml/navigation/histograms.xml for
|
|
// which values correspond which messages.
|
|
EXPECT_THAT(histogram_tester_.GetAllSamples(
|
|
"BackForwardCache.UnexpectedRendererToBrowserMessage."
|
|
"InterfaceName"),
|
|
testing::ElementsAre());
|
|
}
|
|
}
|
|
|
|
protected:
|
|
using UkmMetrics = ukm::TestUkmRecorder::HumanReadableUkmMetrics;
|
|
|
|
// Disables checking metrics that are recorded recardless of the domains. By
|
|
// default, this class' Expect* function checks the metrics both for the
|
|
// specific domain and for all domains at the same time. In the case when the
|
|
// test results need to be different, call this function.
|
|
void DisableCheckingMetricsForAllSites() { check_all_sites_ = false; }
|
|
|
|
void SetUpCommandLine(base::CommandLine* command_line) override {
|
|
base::CommandLine::ForCurrentProcess()->AppendSwitch(
|
|
switches::kUseFakeUIForMediaStream);
|
|
base::CommandLine::ForCurrentProcess()->AppendSwitch(
|
|
switches::kIgnoreCertificateErrors);
|
|
base::CommandLine::ForCurrentProcess()->AppendSwitch(
|
|
switches::kEnableExperimentalWebPlatformFeatures);
|
|
// TODO(sreejakshetty): Initialize ScopedFeatureLists from test constructor.
|
|
EnableFeatureAndSetParams(features::kBackForwardCache,
|
|
"TimeToLiveInBackForwardCacheInSeconds", "3600");
|
|
EnableFeatureAndSetParams(features::kBackForwardCache,
|
|
"message_handling_when_cached", "log");
|
|
EnableFeatureAndSetParams(
|
|
features::kBackForwardCache, "enable_same_site",
|
|
same_site_back_forward_cache_enabled_ ? "true" : "false");
|
|
EnableFeatureAndSetParams(
|
|
features::kBackForwardCache, "skip_same_site_if_unload_exists",
|
|
skip_same_site_if_unload_exists_ ? "true" : "false");
|
|
EnableFeatureAndSetParams(
|
|
features::kBackForwardCache, "check_eligibility_after_pagehide",
|
|
check_eligibility_after_pagehide_ ? "true" : "false");
|
|
EnableFeatureAndSetParams(
|
|
blink::features::kLogUnexpectedIPCPostedToBackForwardCachedDocuments,
|
|
"delay_before_tracking_ms", "0");
|
|
#if defined(OS_ANDROID)
|
|
EnableFeatureAndSetParams(features::kBackForwardCache,
|
|
"process_binding_strength", "NORMAL");
|
|
#endif
|
|
// Allow BackForwardCache for all devices regardless of their memory.
|
|
DisableFeature(features::kBackForwardCacheMemoryControls);
|
|
|
|
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);
|
|
ContentBrowserTest::SetUpCommandLine(command_line);
|
|
}
|
|
|
|
void SetupFeaturesAndParameters() {
|
|
std::vector<base::test::ScopedFeatureList::FeatureAndParams>
|
|
enabled_features;
|
|
|
|
for (auto& features_with_param : features_with_params_) {
|
|
enabled_features.emplace_back(features_with_param.first,
|
|
features_with_param.second);
|
|
}
|
|
|
|
feature_list_.InitWithFeaturesAndParameters(enabled_features,
|
|
disabled_features_);
|
|
}
|
|
|
|
void EnableFeatureAndSetParams(base::Feature feature,
|
|
std::string param_name,
|
|
std::string param_value) {
|
|
features_with_params_[feature][param_name] = param_value;
|
|
}
|
|
|
|
void DisableFeature(base::Feature feature) {
|
|
disabled_features_.push_back(feature);
|
|
}
|
|
|
|
void SetUp() override {
|
|
// Fake the BluetoothAdapter to say it's present.
|
|
// Used in WebBluetooth test.
|
|
adapter_ =
|
|
base::MakeRefCounted<testing::NiceMock<device::MockBluetoothAdapter>>();
|
|
device::BluetoothAdapterFactory::SetAdapterForTesting(adapter_);
|
|
#if BUILDFLAG(IS_CHROMEOS_ASH)
|
|
// In CHROMEOS build, even when |adapter_| object is released at TearDown()
|
|
// it causes the test to fail on exit with an error indicating |adapter_| is
|
|
// leaked.
|
|
testing::Mock::AllowLeak(adapter_.get());
|
|
#endif
|
|
|
|
ContentBrowserTest::SetUp();
|
|
}
|
|
|
|
void TearDown() override {
|
|
testing::Mock::VerifyAndClearExpectations(adapter_.get());
|
|
adapter_.reset();
|
|
ContentBrowserTest::TearDown();
|
|
}
|
|
|
|
void SetUpOnMainThread() override {
|
|
host_resolver()->AddRule("*", "127.0.0.1");
|
|
// TestAutoSetUkmRecorder's constructor requires a sequenced context.
|
|
ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
|
|
ContentBrowserTest::SetUpOnMainThread();
|
|
}
|
|
|
|
void TearDownOnMainThread() override {
|
|
ukm_recorder_.reset();
|
|
ContentBrowserTest::TearDownOnMainThread();
|
|
}
|
|
|
|
WebContentsImpl* web_contents() const {
|
|
return static_cast<WebContentsImpl*>(shell()->web_contents());
|
|
}
|
|
|
|
RenderFrameHostImpl* current_frame_host() {
|
|
return web_contents()->GetFrameTree()->root()->current_frame_host();
|
|
}
|
|
|
|
RenderFrameHostManager* render_frame_host_manager() {
|
|
return web_contents()->GetFrameTree()->root()->render_manager();
|
|
}
|
|
|
|
std::string DepictFrameTree(FrameTreeNode* node) {
|
|
return visualizer_.DepictFrameTree(node);
|
|
}
|
|
|
|
bool HistogramContainsIntValue(base::HistogramBase::Sample sample,
|
|
std::vector<base::Bucket> histogram_values) {
|
|
auto it = std::find_if(histogram_values.begin(), histogram_values.end(),
|
|
[sample](const base::Bucket& bucket) {
|
|
return bucket.min == static_cast<int>(sample);
|
|
});
|
|
return it != histogram_values.end();
|
|
}
|
|
|
|
void ExpectOutcomeDidNotChange(base::Location location) {
|
|
EXPECT_EQ(expected_outcomes_,
|
|
histogram_tester_.GetAllSamples(
|
|
"BackForwardCache.HistoryNavigationOutcome"))
|
|
<< location.ToString();
|
|
|
|
if (!check_all_sites_)
|
|
return;
|
|
|
|
EXPECT_EQ(expected_outcomes_,
|
|
histogram_tester_.GetAllSamples(
|
|
"BackForwardCache.AllSites.HistoryNavigationOutcome"))
|
|
<< location.ToString();
|
|
|
|
std::string is_served_from_bfcache =
|
|
"BackForwardCache.IsServedFromBackForwardCache";
|
|
EXPECT_THAT(ukm_recorder_->GetMetrics("HistoryNavigation",
|
|
{is_served_from_bfcache}),
|
|
expected_ukm_outcomes_)
|
|
<< location.ToString();
|
|
}
|
|
|
|
void ExpectRestored(base::Location location) {
|
|
ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kRestored,
|
|
location);
|
|
ExpectReasons({}, {}, {}, {}, location);
|
|
}
|
|
|
|
void ExpectNotRestored(
|
|
std::vector<BackForwardCacheMetrics::NotRestoredReason> not_restored,
|
|
std::vector<blink::scheduler::WebSchedulerTrackedFeature> block_listed,
|
|
const std::vector<ShouldSwapBrowsingInstance>& not_swapped,
|
|
const std::vector<BackForwardCache::DisabledReason>&
|
|
disabled_for_render_frame_host,
|
|
base::Location location) {
|
|
ExpectOutcome(
|
|
BackForwardCacheMetrics::HistoryNavigationOutcome::kNotRestored,
|
|
location);
|
|
ExpectReasons(not_restored, block_listed, not_swapped,
|
|
disabled_for_render_frame_host, location);
|
|
}
|
|
|
|
void ExpectNotRestoredDidNotChange(base::Location location) {
|
|
EXPECT_EQ(expected_not_restored_,
|
|
histogram_tester_.GetAllSamples(
|
|
"BackForwardCache.HistoryNavigationOutcome."
|
|
"NotRestoredReason"))
|
|
<< location.ToString();
|
|
|
|
std::string not_restored_reasons = "BackForwardCache.NotRestoredReasons";
|
|
|
|
if (!check_all_sites_)
|
|
return;
|
|
|
|
EXPECT_EQ(expected_not_restored_,
|
|
histogram_tester_.GetAllSamples(
|
|
"BackForwardCache.AllSites.HistoryNavigationOutcome."
|
|
"NotRestoredReason"))
|
|
<< location.ToString();
|
|
|
|
EXPECT_THAT(
|
|
ukm_recorder_->GetMetrics("HistoryNavigation", {not_restored_reasons}),
|
|
expected_ukm_not_restored_reasons_)
|
|
<< location.ToString();
|
|
}
|
|
|
|
void ExpectBlocklistedFeature(
|
|
blink::scheduler::WebSchedulerTrackedFeature feature,
|
|
base::Location location) {
|
|
ExpectBlocklistedFeatures({feature}, location);
|
|
}
|
|
|
|
void ExpectBrowsingInstanceNotSwappedReason(ShouldSwapBrowsingInstance reason,
|
|
base::Location location) {
|
|
ExpectBrowsingInstanceNotSwappedReasons({reason}, location);
|
|
}
|
|
|
|
void ExpectEvictedAfterCommitted(
|
|
std::vector<BackForwardCacheMetrics::EvictedAfterDocumentRestoredReason>
|
|
reasons,
|
|
base::Location location) {
|
|
for (BackForwardCacheMetrics::EvictedAfterDocumentRestoredReason reason :
|
|
reasons) {
|
|
base::HistogramBase::Sample sample = base::HistogramBase::Sample(reason);
|
|
AddSampleToBuckets(&expected_eviction_after_committing_, sample);
|
|
}
|
|
|
|
EXPECT_THAT(histogram_tester_.GetAllSamples(
|
|
"BackForwardCache.EvictedAfterDocumentRestoredReason"),
|
|
UnorderedElementsAreArray(expected_eviction_after_committing_))
|
|
<< location.ToString();
|
|
if (!check_all_sites_)
|
|
return;
|
|
|
|
EXPECT_THAT(
|
|
histogram_tester_.GetAllSamples(
|
|
"BackForwardCache.AllSites.EvictedAfterDocumentRestoredReason"),
|
|
UnorderedElementsAreArray(expected_eviction_after_committing_))
|
|
<< location.ToString();
|
|
}
|
|
|
|
void 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 StartRecordingEvents(RenderFrameHostImpl* rfh) {
|
|
EXPECT_TRUE(ExecJs(rfh, R"(
|
|
window.testObservedEvents = [];
|
|
let event_list = [
|
|
'visibilitychange',
|
|
'pagehide',
|
|
'pageshow',
|
|
'freeze',
|
|
'resume',
|
|
'unload',
|
|
];
|
|
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 MatchEventList(RenderFrameHostImpl* rfh,
|
|
base::ListValue list,
|
|
base::Location location = base::Location::Current()) {
|
|
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* CreateHttpsServer() {
|
|
https_server_ = std::make_unique<net::EmbeddedTestServer>(
|
|
net::EmbeddedTestServer::TYPE_HTTPS);
|
|
https_server_->AddDefaultHandlers(GetTestDataFilePath());
|
|
https_server_->SetSSLConfig(net::EmbeddedTestServer::CERT_OK);
|
|
return https_server();
|
|
}
|
|
|
|
net::EmbeddedTestServer* https_server() { return https_server_.get(); }
|
|
|
|
void ExpectTotalCount(base::StringPiece name,
|
|
base::HistogramBase::Count count) {
|
|
histogram_tester_.ExpectTotalCount(name, count);
|
|
}
|
|
|
|
template <typename T>
|
|
void ExpectBucketCount(base::StringPiece name,
|
|
T sample,
|
|
base::HistogramBase::Count expected_count) {
|
|
histogram_tester_.ExpectBucketCount(name, sample, expected_count);
|
|
}
|
|
|
|
// Do not fail this test if a message from a renderer arrives at the browser
|
|
// for a cached page.
|
|
void DoNotFailForUnexpectedMessagesWhileCached() {
|
|
fail_for_unexpected_messages_while_cached_ = false;
|
|
}
|
|
|
|
base::HistogramTester histogram_tester_;
|
|
|
|
protected:
|
|
bool same_site_back_forward_cache_enabled_ = true;
|
|
bool skip_same_site_if_unload_exists_ = false;
|
|
bool check_eligibility_after_pagehide_ = false;
|
|
|
|
private:
|
|
void AddSampleToBuckets(std::vector<base::Bucket>* buckets,
|
|
base::HistogramBase::Sample sample) {
|
|
auto it = std::find_if(
|
|
buckets->begin(), buckets->end(),
|
|
[sample](const base::Bucket& bucket) { return bucket.min == sample; });
|
|
if (it == buckets->end()) {
|
|
buckets->push_back(base::Bucket(sample, 1));
|
|
} else {
|
|
it->count++;
|
|
}
|
|
}
|
|
|
|
void ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome outcome,
|
|
base::Location location) {
|
|
base::HistogramBase::Sample sample = base::HistogramBase::Sample(outcome);
|
|
AddSampleToBuckets(&expected_outcomes_, sample);
|
|
|
|
EXPECT_THAT(histogram_tester_.GetAllSamples(
|
|
"BackForwardCache.HistoryNavigationOutcome"),
|
|
UnorderedElementsAreArray(expected_outcomes_))
|
|
<< location.ToString();
|
|
if (!check_all_sites_)
|
|
return;
|
|
|
|
EXPECT_THAT(histogram_tester_.GetAllSamples(
|
|
"BackForwardCache.AllSites.HistoryNavigationOutcome"),
|
|
UnorderedElementsAreArray(expected_outcomes_))
|
|
<< location.ToString();
|
|
|
|
std::string is_served_from_bfcache =
|
|
"BackForwardCache.IsServedFromBackForwardCache";
|
|
bool ukm_outcome =
|
|
outcome == BackForwardCacheMetrics::HistoryNavigationOutcome::kRestored;
|
|
expected_ukm_outcomes_.push_back(
|
|
{{is_served_from_bfcache, static_cast<int64_t>(ukm_outcome)}});
|
|
EXPECT_THAT(ukm_recorder_->GetMetrics("HistoryNavigation",
|
|
{is_served_from_bfcache}),
|
|
expected_ukm_outcomes_)
|
|
<< location.ToString();
|
|
}
|
|
|
|
void ExpectReasons(
|
|
std::vector<BackForwardCacheMetrics::NotRestoredReason> not_restored,
|
|
std::vector<blink::scheduler::WebSchedulerTrackedFeature> block_listed,
|
|
const std::vector<ShouldSwapBrowsingInstance>& not_swapped,
|
|
const std::vector<BackForwardCache::DisabledReason>&
|
|
disabled_for_render_frame_host,
|
|
base::Location location) {
|
|
// Check that the expected reasons are consistent.
|
|
bool expect_blocklisted =
|
|
std::count(
|
|
not_restored.begin(), not_restored.end(),
|
|
BackForwardCacheMetrics::NotRestoredReason::kBlocklistedFeatures) >
|
|
0;
|
|
bool has_blocklisted = block_listed.size() > 0;
|
|
EXPECT_EQ(expect_blocklisted, has_blocklisted);
|
|
bool expect_disabled_for_render_frame_host =
|
|
std::count(not_restored.begin(), not_restored.end(),
|
|
BackForwardCacheMetrics::NotRestoredReason::
|
|
kDisableForRenderFrameHostCalled) > 0;
|
|
bool has_disabled_for_render_frame_host =
|
|
disabled_for_render_frame_host.size() > 0;
|
|
EXPECT_EQ(expect_disabled_for_render_frame_host,
|
|
has_disabled_for_render_frame_host);
|
|
|
|
// Check that the reasons are as expected.
|
|
ExpectNotRestoredReasons(not_restored, location);
|
|
ExpectBlocklistedFeatures(block_listed, location);
|
|
ExpectBrowsingInstanceNotSwappedReasons(not_swapped, location);
|
|
ExpectDisabledWithReasons(disabled_for_render_frame_host, location);
|
|
}
|
|
|
|
void ExpectNotRestoredReasons(
|
|
std::vector<BackForwardCacheMetrics::NotRestoredReason> reasons,
|
|
base::Location location) {
|
|
uint64_t not_restored_reasons_bits = 0;
|
|
for (BackForwardCacheMetrics::NotRestoredReason reason : reasons) {
|
|
base::HistogramBase::Sample sample = base::HistogramBase::Sample(reason);
|
|
AddSampleToBuckets(&expected_not_restored_, sample);
|
|
not_restored_reasons_bits |= 1ull << static_cast<int>(reason);
|
|
}
|
|
|
|
EXPECT_THAT(histogram_tester_.GetAllSamples(
|
|
"BackForwardCache.HistoryNavigationOutcome."
|
|
"NotRestoredReason"),
|
|
UnorderedElementsAreArray(expected_not_restored_))
|
|
<< location.ToString();
|
|
|
|
if (!check_all_sites_)
|
|
return;
|
|
|
|
EXPECT_THAT(histogram_tester_.GetAllSamples(
|
|
"BackForwardCache.AllSites.HistoryNavigationOutcome."
|
|
"NotRestoredReason"),
|
|
UnorderedElementsAreArray(expected_not_restored_))
|
|
<< location.ToString();
|
|
|
|
std::string not_restored_reasons = "BackForwardCache.NotRestoredReasons";
|
|
expected_ukm_not_restored_reasons_.push_back(
|
|
{{not_restored_reasons, not_restored_reasons_bits}});
|
|
EXPECT_THAT(
|
|
ukm_recorder_->GetMetrics("HistoryNavigation", {not_restored_reasons}),
|
|
expected_ukm_not_restored_reasons_)
|
|
<< location.ToString();
|
|
}
|
|
|
|
void ExpectBlocklistedFeatures(
|
|
std::vector<blink::scheduler::WebSchedulerTrackedFeature> features,
|
|
base::Location location) {
|
|
for (auto feature : features) {
|
|
base::HistogramBase::Sample sample = base::HistogramBase::Sample(feature);
|
|
AddSampleToBuckets(&expected_blocklisted_features_, sample);
|
|
}
|
|
|
|
EXPECT_THAT(histogram_tester_.GetAllSamples(
|
|
"BackForwardCache.HistoryNavigationOutcome."
|
|
"BlocklistedFeature"),
|
|
UnorderedElementsAreArray(expected_blocklisted_features_))
|
|
<< location.ToString();
|
|
|
|
if (!check_all_sites_)
|
|
return;
|
|
|
|
EXPECT_THAT(histogram_tester_.GetAllSamples(
|
|
"BackForwardCache.AllSites.HistoryNavigationOutcome."
|
|
"BlocklistedFeature"),
|
|
UnorderedElementsAreArray(expected_blocklisted_features_))
|
|
<< location.ToString();
|
|
}
|
|
|
|
void ExpectDisabledWithReasons(
|
|
const std::vector<BackForwardCache::DisabledReason>& reasons,
|
|
base::Location location) {
|
|
for (BackForwardCache::DisabledReason reason : reasons) {
|
|
base::HistogramBase::Sample sample = base::HistogramBase::Sample(
|
|
content::BackForwardCacheMetrics::MetricValue(reason));
|
|
AddSampleToBuckets(&expected_disabled_reasons_, sample);
|
|
}
|
|
EXPECT_THAT(histogram_tester_.GetAllSamples(
|
|
"BackForwardCache.HistoryNavigationOutcome."
|
|
"DisabledForRenderFrameHostReason2"),
|
|
UnorderedElementsAreArray(expected_disabled_reasons_))
|
|
<< location.ToString();
|
|
}
|
|
|
|
void ExpectBrowsingInstanceNotSwappedReasons(
|
|
const std::vector<ShouldSwapBrowsingInstance>& reasons,
|
|
base::Location location) {
|
|
for (auto reason : reasons) {
|
|
base::HistogramBase::Sample sample = base::HistogramBase::Sample(reason);
|
|
AddSampleToBuckets(&expected_browsing_instance_not_swapped_reasons_,
|
|
sample);
|
|
}
|
|
EXPECT_THAT(histogram_tester_.GetAllSamples(
|
|
"BackForwardCache.HistoryNavigationOutcome."
|
|
"BrowsingInstanceNotSwappedReason"),
|
|
UnorderedElementsAreArray(
|
|
expected_browsing_instance_not_swapped_reasons_))
|
|
<< location.ToString();
|
|
if (!check_all_sites_)
|
|
return;
|
|
|
|
EXPECT_THAT(histogram_tester_.GetAllSamples(
|
|
"BackForwardCache.AllSites.HistoryNavigationOutcome."
|
|
"BrowsingInstanceNotSwappedReason"),
|
|
UnorderedElementsAreArray(
|
|
expected_browsing_instance_not_swapped_reasons_))
|
|
<< location.ToString();
|
|
}
|
|
|
|
base::test::ScopedFeatureList feature_list_;
|
|
|
|
FrameTreeVisualizer visualizer_;
|
|
std::vector<base::Bucket> expected_outcomes_;
|
|
std::vector<base::Bucket> expected_not_restored_;
|
|
std::vector<base::Bucket> expected_blocklisted_features_;
|
|
std::vector<base::Bucket> expected_disabled_reasons_;
|
|
std::vector<base::Bucket> expected_browsing_instance_not_swapped_reasons_;
|
|
std::vector<base::Bucket> expected_eviction_after_committing_;
|
|
std::unique_ptr<net::EmbeddedTestServer> https_server_;
|
|
std::unordered_map<base::Feature,
|
|
std::map<std::string, std::string>,
|
|
FeatureHash,
|
|
FeatureEqualOperator>
|
|
features_with_params_;
|
|
std::vector<base::Feature> disabled_features_;
|
|
|
|
std::vector<UkmMetrics> expected_ukm_outcomes_;
|
|
std::vector<UkmMetrics> expected_ukm_not_restored_reasons_;
|
|
std::unique_ptr<ukm::TestAutoSetUkmRecorder> ukm_recorder_;
|
|
|
|
// Indicates whether metrics for all sites regardless of the domains are
|
|
// checked or not.
|
|
bool check_all_sites_ = true;
|
|
// Whether we should fail the test if a message arrived at the browser from a
|
|
// renderer for a bfcached page.
|
|
bool fail_for_unexpected_messages_while_cached_ = true;
|
|
|
|
scoped_refptr<device::MockBluetoothAdapter> adapter_;
|
|
};
|
|
|
|
// Match RenderFrameHostImpl* that are in the BackForwardCache.
|
|
MATCHER(InBackForwardCache, "") {
|
|
return arg->IsInBackForwardCache();
|
|
}
|
|
|
|
// Match RenderFrameDeleteObserver* which observed deletion of the RenderFrame.
|
|
MATCHER(Deleted, "") {
|
|
return arg->deleted();
|
|
}
|
|
|
|
// Helper function to pass an initializer list to the EXPECT_THAT macro. This is
|
|
// indeed the identity function.
|
|
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)) {}
|
|
|
|
private:
|
|
// WebContentsObserver:
|
|
void ReadyToCommitNavigation(NavigationHandle* navigation_handle) override {
|
|
if (callback_)
|
|
std::move(callback_).Run(navigation_handle);
|
|
}
|
|
|
|
base::OnceCallback<void(NavigationHandle*)> callback_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(ReadyToCommitNavigationCallback);
|
|
};
|
|
|
|
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) {}
|
|
void DidChangeThemeColor() override { observed_ = true; }
|
|
|
|
bool did_fire() const { return observed_; }
|
|
|
|
private:
|
|
bool observed_ = false;
|
|
};
|
|
|
|
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();
|
|
}
|
|
|
|
void Wait() {
|
|
if (render_frame_host_->IsDOMContentLoaded())
|
|
run_loop_.Quit();
|
|
run_loop_.Run();
|
|
}
|
|
|
|
private:
|
|
RenderFrameHostImpl* render_frame_host_;
|
|
base::RunLoop run_loop_;
|
|
};
|
|
|
|
void WaitForDOMContentLoaded(RenderFrameHostImpl* rfh) {
|
|
DOMContentLoadedObserver observer(rfh);
|
|
observer.Wait();
|
|
}
|
|
|
|
class PageLifecycleStateManagerTestDelegate
|
|
: public PageLifecycleStateManager::TestDelegate {
|
|
public:
|
|
explicit PageLifecycleStateManagerTestDelegate(
|
|
PageLifecycleStateManager* manager)
|
|
: manager_(manager) {
|
|
manager->SetDelegateForTesting(this);
|
|
}
|
|
|
|
~PageLifecycleStateManagerTestDelegate() override {
|
|
if (manager_)
|
|
manager_->SetDelegateForTesting(nullptr);
|
|
}
|
|
|
|
void WaitForInBackForwardCacheAck() {
|
|
DCHECK(manager_);
|
|
if (manager_->last_acknowledged_state().is_in_back_forward_cache) {
|
|
return;
|
|
}
|
|
base::RunLoop loop;
|
|
store_in_back_forward_cache_ack_received_ = loop.QuitClosure();
|
|
loop.Run();
|
|
}
|
|
|
|
void OnStoreInBackForwardCacheSent(base::OnceClosure cb) {
|
|
store_in_back_forward_cache_sent_ = std::move(cb);
|
|
}
|
|
|
|
void OnDisableJsEvictionSent(base::OnceClosure cb) {
|
|
disable_eviction_sent_ = std::move(cb);
|
|
}
|
|
|
|
void OnRestoreFromBackForwardCacheSent(base::OnceClosure cb) {
|
|
restore_from_back_forward_cache_sent_ = std::move(cb);
|
|
}
|
|
|
|
private:
|
|
void OnLastAcknowledgedStateChanged(
|
|
const blink::mojom::PageLifecycleState& old_state,
|
|
const blink::mojom::PageLifecycleState& new_state) override {
|
|
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 OnUpdateSentToRenderer(
|
|
const blink::mojom::PageLifecycleState& new_state) override {
|
|
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 OnDeleted() override { manager_ = nullptr; }
|
|
|
|
PageLifecycleStateManager* manager_;
|
|
base::OnceClosure store_in_back_forward_cache_sent_;
|
|
base::OnceClosure store_in_back_forward_cache_ack_received_;
|
|
base::OnceClosure restore_from_back_forward_cache_sent_;
|
|
base::OnceClosure disable_eviction_sent_;
|
|
};
|
|
|
|
class FakeIdleTimeProvider : public IdleManager::IdleTimeProvider {
|
|
public:
|
|
FakeIdleTimeProvider() = default;
|
|
~FakeIdleTimeProvider() override = default;
|
|
FakeIdleTimeProvider(const FakeIdleTimeProvider&) = delete;
|
|
FakeIdleTimeProvider& operator=(const FakeIdleTimeProvider&) = delete;
|
|
|
|
base::TimeDelta CalculateIdleTime() override {
|
|
return base::TimeDelta::FromSeconds(0);
|
|
}
|
|
|
|
bool CheckIdleStateIsLocked() override { return false; }
|
|
};
|
|
|
|
} // namespace
|
|
|
|
// Navigate from A to B and go back.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, Basic) {
|
|
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"));
|
|
url::Origin origin_a = url::Origin::Create(url_a);
|
|
url::Origin origin_b = url::Origin::Create(url_b);
|
|
|
|
// 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));
|
|
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_EQ(rfh_a->GetVisibilityState(), PageVisibilityState::kHidden);
|
|
EXPECT_EQ(origin_a, rfh_a->GetLastCommittedOrigin());
|
|
EXPECT_EQ(origin_b, rfh_b->GetLastCommittedOrigin());
|
|
EXPECT_FALSE(rfh_b->IsInBackForwardCache());
|
|
EXPECT_EQ(rfh_b->GetVisibilityState(), PageVisibilityState::kVisible);
|
|
|
|
// 3) Go back to A.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_FALSE(delete_observer_rfh_a.deleted());
|
|
EXPECT_FALSE(delete_observer_rfh_b.deleted());
|
|
EXPECT_EQ(origin_a, rfh_a->GetLastCommittedOrigin());
|
|
EXPECT_EQ(origin_b, rfh_b->GetLastCommittedOrigin());
|
|
EXPECT_EQ(rfh_a, current_frame_host());
|
|
EXPECT_FALSE(rfh_a->IsInBackForwardCache());
|
|
EXPECT_EQ(rfh_a->GetVisibilityState(), PageVisibilityState::kVisible);
|
|
EXPECT_TRUE(rfh_b->IsInBackForwardCache());
|
|
EXPECT_EQ(rfh_b->GetVisibilityState(), PageVisibilityState::kHidden);
|
|
|
|
ExpectRestored(FROM_HERE);
|
|
}
|
|
|
|
// Navigate from A to B and go back.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, BasicDocumentInitiated) {
|
|
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(ExecJs(shell(), JsReplace("location = $1;", url_b.spec())));
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
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());
|
|
|
|
// The two pages are using different BrowsingInstances.
|
|
EXPECT_FALSE(rfh_a->GetSiteInstance()->IsRelatedSiteInstance(
|
|
rfh_b->GetSiteInstance()));
|
|
|
|
// 3) Go back to A.
|
|
EXPECT_TRUE(ExecJs(shell(), "history.back();"));
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->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());
|
|
|
|
ExpectRestored(FROM_HERE);
|
|
}
|
|
|
|
// Navigate from back and forward repeatedly.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
NavigateBackForwardRepeatedly) {
|
|
// Do not check for unexpected messages because the input task queue is not
|
|
// currently frozen, causing flakes in this test: crbug.com/1099395.
|
|
DoNotFailForUnexpectedMessagesWhileCached();
|
|
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));
|
|
RenderFrameHostImpl* rfh_b = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b);
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
EXPECT_FALSE(rfh_b->IsInBackForwardCache());
|
|
|
|
// 3) Go back to A.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
EXPECT_EQ(rfh_a, current_frame_host());
|
|
EXPECT_FALSE(rfh_a->IsInBackForwardCache());
|
|
EXPECT_TRUE(rfh_b->IsInBackForwardCache());
|
|
|
|
ExpectRestored(FROM_HERE);
|
|
|
|
// 4) Go forward to B.
|
|
web_contents()->GetController().GoForward();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
EXPECT_EQ(rfh_b, current_frame_host());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
EXPECT_FALSE(rfh_b->IsInBackForwardCache());
|
|
|
|
ExpectRestored(FROM_HERE);
|
|
|
|
// 5) Go back to A.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
EXPECT_EQ(rfh_a, current_frame_host());
|
|
EXPECT_FALSE(rfh_a->IsInBackForwardCache());
|
|
EXPECT_TRUE(rfh_b->IsInBackForwardCache());
|
|
|
|
ExpectRestored(FROM_HERE);
|
|
|
|
// 6) Go forward to B.
|
|
web_contents()->GetController().GoForward();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
EXPECT_EQ(rfh_b, current_frame_host());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
EXPECT_FALSE(rfh_b->IsInBackForwardCache());
|
|
|
|
EXPECT_FALSE(delete_observer_rfh_a.deleted());
|
|
EXPECT_FALSE(delete_observer_rfh_b.deleted());
|
|
|
|
ExpectRestored(FROM_HERE);
|
|
}
|
|
|
|
// The current page can't enter the BackForwardCache if another page can script
|
|
// it. This can happen when one document opens a popup using window.open() for
|
|
// instance. It prevents the BackForwardCache from being used.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, WindowOpen) {
|
|
// This test assumes cross-site navigation staying in the same
|
|
// BrowsingInstance to use a different SiteInstance. Otherwise, it will
|
|
// timeout at step 2).
|
|
if (!SiteIsolationPolicy::UseDedicatedProcessesForAllSites())
|
|
return;
|
|
|
|
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 and open a popup.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
EXPECT_EQ(1u, rfh_a->GetSiteInstance()->GetRelatedActiveContentsCount());
|
|
Shell* popup = OpenPopup(rfh_a, url_a, "");
|
|
EXPECT_EQ(2u, rfh_a->GetSiteInstance()->GetRelatedActiveContentsCount());
|
|
|
|
// 2) Navigate to B. The previous document can't enter the BackForwardCache,
|
|
// because of the popup.
|
|
EXPECT_TRUE(ExecJs(rfh_a, JsReplace("location = $1;", url_b.spec())));
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
RenderFrameHostImpl* rfh_b = current_frame_host();
|
|
EXPECT_EQ(2u, rfh_b->GetSiteInstance()->GetRelatedActiveContentsCount());
|
|
|
|
// 3) Go back to A. The previous document can't enter the BackForwardCache,
|
|
// because of the popup.
|
|
RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b);
|
|
EXPECT_TRUE(ExecJs(rfh_b, "history.back();"));
|
|
delete_observer_rfh_b.WaitUntilDeleted();
|
|
|
|
// 4) Make the popup drop the window.opener connection. It happens when the
|
|
// user does an omnibox-initiated navigation, which happens in a new
|
|
// BrowsingInstance.
|
|
RenderFrameHostImpl* rfh_a_new = current_frame_host();
|
|
EXPECT_EQ(2u, rfh_a_new->GetSiteInstance()->GetRelatedActiveContentsCount());
|
|
EXPECT_TRUE(NavigateToURL(popup, url_b));
|
|
EXPECT_EQ(1u, rfh_a_new->GetSiteInstance()->GetRelatedActiveContentsCount());
|
|
|
|
// 5) Navigate to B again. As the scripting relationship with the popup is
|
|
// now severed, the current page (|rfh_a_new|) can enter back-forward cache.
|
|
RenderFrameDeletedObserver delete_observer_rfh_a_new(rfh_a_new);
|
|
EXPECT_TRUE(ExecJs(rfh_a_new, JsReplace("location = $1;", url_b.spec())));
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
EXPECT_FALSE(delete_observer_rfh_a_new.deleted());
|
|
EXPECT_TRUE(rfh_a_new->IsInBackForwardCache());
|
|
|
|
// 6) Go back to A. The current document can finally enter the
|
|
// BackForwardCache, because it is alone in its BrowsingInstance and has never
|
|
// been related to any other document.
|
|
RenderFrameHostImpl* rfh_b_new = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_b_new(rfh_b_new);
|
|
EXPECT_TRUE(ExecJs(rfh_b_new, "history.back();"));
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
EXPECT_FALSE(delete_observer_rfh_b_new.deleted());
|
|
EXPECT_TRUE(rfh_b_new->IsInBackForwardCache());
|
|
}
|
|
|
|
// Navigate from A(B) to C and go back.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, BasicIframe) {
|
|
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(B).
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b);
|
|
|
|
// 2) Navigate to C.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_c));
|
|
RenderFrameHostImpl* rfh_c = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_c(rfh_c);
|
|
EXPECT_FALSE(delete_observer_rfh_a.deleted());
|
|
EXPECT_FALSE(delete_observer_rfh_b.deleted());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
EXPECT_TRUE(rfh_b->IsInBackForwardCache());
|
|
EXPECT_FALSE(rfh_c->IsInBackForwardCache());
|
|
|
|
// 3) Go back to A(B).
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_FALSE(delete_observer_rfh_a.deleted());
|
|
EXPECT_FALSE(delete_observer_rfh_b.deleted());
|
|
EXPECT_FALSE(delete_observer_rfh_c.deleted());
|
|
EXPECT_EQ(rfh_a, current_frame_host());
|
|
EXPECT_FALSE(rfh_a->IsInBackForwardCache());
|
|
EXPECT_FALSE(rfh_b->IsInBackForwardCache());
|
|
EXPECT_TRUE(rfh_c->IsInBackForwardCache());
|
|
|
|
ExpectRestored(FROM_HERE);
|
|
}
|
|
|
|
// Ensure flushing the BackForwardCache works properly.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, BackForwardCacheFlush) {
|
|
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));
|
|
RenderFrameHostImpl* rfh_b = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b);
|
|
EXPECT_FALSE(delete_observer_rfh_a.deleted());
|
|
|
|
// 3) Flush A.
|
|
web_contents()->GetController().GetBackForwardCache().Flush();
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
EXPECT_FALSE(delete_observer_rfh_b.deleted());
|
|
|
|
// 4) Go back to a new A.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_FALSE(delete_observer_rfh_b.deleted());
|
|
|
|
// 5) Flush B.
|
|
web_contents()->GetController().GetBackForwardCache().Flush();
|
|
delete_observer_rfh_b.WaitUntilDeleted();
|
|
}
|
|
|
|
// 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.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_EQ(url_a, web_contents()->GetVisibleURL());
|
|
|
|
// 4) Go forward to B.
|
|
web_contents()->GetController().GoForward();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->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.
|
|
|
|
web_contents()->GetController().GoToOffset(-2);
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
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.com", "/set-header?X-Foo: bar"));
|
|
GURL url_b(https_server()->GetURL("b.com", "/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);
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->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);
|
|
}
|
|
|
|
class HighCacheSizeBackForwardCacheBrowserTest
|
|
: public BackForwardCacheBrowserTest {
|
|
protected:
|
|
void SetUpCommandLine(base::CommandLine* command_line) override {
|
|
EnableFeatureAndSetParams(features::kBackForwardCache, "cache_size",
|
|
base::NumberToString(kBackForwardCacheSize));
|
|
BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
|
|
}
|
|
|
|
// The number of pages the BackForwardCache can hold per tab.
|
|
const size_t kBackForwardCacheSize = 10;
|
|
};
|
|
|
|
// TODO(crbug.com/1184360): Disabled on Android for being flaky.
|
|
#if defined(OS_ANDROID)
|
|
#define MAYBE_CacheEvictionWithIncreasedCacheSize \
|
|
DISABLED_CacheEvictionWithIncreasedCacheSize
|
|
#else
|
|
#define MAYBE_CacheEvictionWithIncreasedCacheSize \
|
|
CacheEvictionWithIncreasedCacheSize
|
|
#endif
|
|
// Test documents are evicted from the BackForwardCache at some point.
|
|
IN_PROC_BROWSER_TEST_F(HighCacheSizeBackForwardCacheBrowserTest,
|
|
MAYBE_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());
|
|
}
|
|
}
|
|
|
|
class BackgroundForegroundProcessLimitBackForwardCacheBrowserTest
|
|
: public BackForwardCacheBrowserTest {
|
|
protected:
|
|
void SetUpCommandLine(base::CommandLine* command_line) override {
|
|
EnableFeatureAndSetParams(features::kBackForwardCache, "cache_size",
|
|
base::NumberToString(kBackForwardCacheSize));
|
|
EnableFeatureAndSetParams(
|
|
features::kBackForwardCache, "foreground_cache_size",
|
|
base::NumberToString(kForegroundBackForwardCacheSize));
|
|
BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
|
|
}
|
|
|
|
void ExpectCached(const RenderFrameDeletedObserver& deleted_observer,
|
|
bool cached,
|
|
bool backgrounded) {
|
|
EXPECT_FALSE(deleted_observer.deleted());
|
|
EXPECT_EQ(cached, static_cast<RenderFrameHostImpl*>(
|
|
deleted_observer.render_frame_host())
|
|
->IsInBackForwardCache());
|
|
EXPECT_EQ(backgrounded, deleted_observer.render_frame_host()
|
|
->GetProcess()
|
|
->IsProcessBackgrounded());
|
|
}
|
|
// The number of pages the BackForwardCache can hold per tab.
|
|
const size_t kBackForwardCacheSize = 4;
|
|
const size_t kForegroundBackForwardCacheSize = 2;
|
|
};
|
|
|
|
// Test that a series of same-site navigations (which use the same process)
|
|
// uses the foreground limit.
|
|
IN_PROC_BROWSER_TEST_F(
|
|
BackgroundForegroundProcessLimitBackForwardCacheBrowserTest,
|
|
CacheEvictionSameSite) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
std::vector<std::unique_ptr<RenderFrameDeletedObserver>> delete_observers;
|
|
|
|
for (size_t i = 0; i <= kBackForwardCacheSize * 2; ++i) {
|
|
SCOPED_TRACE(i);
|
|
GURL url(embedded_test_server()->GetURL(
|
|
"a.com", base::StringPrintf("/title1.html?i=%zu", i)));
|
|
ASSERT_TRUE(NavigateToURL(shell(), url));
|
|
RenderFrameHostImpl* rfh = current_frame_host();
|
|
EXPECT_FALSE(rfh->GetProcess()->IsProcessBackgrounded());
|
|
delete_observers.emplace_back(new RenderFrameDeletedObserver(rfh));
|
|
|
|
for (size_t j = 0; j <= i; ++j) {
|
|
SCOPED_TRACE(j);
|
|
// The last page is active, the previous |kForegroundBackForwardCacheSize|
|
|
// should be in the cache, any before that should be deleted.
|
|
if (i - j <= kForegroundBackForwardCacheSize) {
|
|
// All of the processes should be in the foreground.
|
|
ExpectCached(*delete_observers[j], /*cached=*/i != j,
|
|
/*backgrounded=*/false);
|
|
} else {
|
|
delete_observers[j]->WaitUntilDeleted();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Navigate back but not to the initial about:blank.
|
|
for (size_t i = 0; i <= kBackForwardCacheSize * 2 - 1; ++i) {
|
|
SCOPED_TRACE(i);
|
|
web_contents()->GetController().GoBack();
|
|
ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
// The first |kBackForwardCacheSize| navigations should be restored from the
|
|
// cache. The rest should not.
|
|
if (i < kForegroundBackForwardCacheSize) {
|
|
ExpectRestored(FROM_HERE);
|
|
} else {
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kForegroundCacheLimit},
|
|
{}, {}, {}, FROM_HERE);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test that a series of cross-site navigations (which use different processes)
|
|
// use the background limit.
|
|
//
|
|
// TODO(crbug.com/1203418): This test is flaky.
|
|
IN_PROC_BROWSER_TEST_F(
|
|
BackgroundForegroundProcessLimitBackForwardCacheBrowserTest,
|
|
DISABLED_CacheEvictionCrossSite) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
std::vector<std::unique_ptr<RenderFrameDeletedObserver>> delete_observers;
|
|
|
|
for (size_t i = 0; i <= kBackForwardCacheSize * 2; ++i) {
|
|
SCOPED_TRACE(i);
|
|
GURL url(embedded_test_server()->GetURL(base::StringPrintf("a%zu.com", i),
|
|
"/title1.html"));
|
|
ASSERT_TRUE(NavigateToURL(shell(), url));
|
|
RenderFrameHostImpl* rfh = current_frame_host();
|
|
EXPECT_FALSE(rfh->GetProcess()->IsProcessBackgrounded());
|
|
delete_observers.emplace_back(new RenderFrameDeletedObserver(rfh));
|
|
|
|
for (size_t j = 0; j <= i; ++j) {
|
|
SCOPED_TRACE(j);
|
|
// The last page is active, the previous |kBackgroundBackForwardCacheSize|
|
|
// should be in the cache, any before that should be deleted.
|
|
if (i - j <= kBackForwardCacheSize) {
|
|
EXPECT_FALSE(delete_observers[j]->deleted());
|
|
// Pages except the active one should be cached and in the background.
|
|
ExpectCached(*delete_observers[j], /*cached=*/i != j,
|
|
/*backgrounded=*/i != j);
|
|
} else {
|
|
delete_observers[j]->WaitUntilDeleted();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Navigate back but not to the initial about:blank.
|
|
for (size_t i = 0; i <= kBackForwardCacheSize * 2 - 1; ++i) {
|
|
SCOPED_TRACE(i);
|
|
web_contents()->GetController().GoBack();
|
|
ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
// The first |kBackForwardCacheSize| navigations should be restored from the
|
|
// cache. The rest should not.
|
|
if (i < kBackForwardCacheSize) {
|
|
ExpectRestored(FROM_HERE);
|
|
} else {
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kCacheLimit}, {}, {}, {},
|
|
FROM_HERE);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test that the cache responds to processes switching from background to
|
|
// foreground. We set things up so that we have
|
|
// Cached sites:
|
|
// a0.com
|
|
// a1.com
|
|
// a2.com
|
|
// a3.com
|
|
// and the active page is a4.com. Then set the process for a[1-3] to
|
|
// foregrounded so that there are 3 entries whose processes are foregrounded.
|
|
// BFCache should evict the eldest (a1) leaving a0 because despite being older,
|
|
// it is backgrounded. Setting the priority directly is not ideal but there is
|
|
// no reliable way to cause the processes to go into the foreground just by
|
|
// navigating because proactive browsing instance swap makes it impossible to
|
|
// reliably create a new a1.com renderer in the same process as the old a1.com.
|
|
IN_PROC_BROWSER_TEST_F(
|
|
BackgroundForegroundProcessLimitBackForwardCacheBrowserTest,
|
|
ChangeToForeground) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
std::vector<std::unique_ptr<RenderFrameDeletedObserver>> delete_observers;
|
|
|
|
// Navigate through a[0-3].com.
|
|
for (size_t i = 0; i < kBackForwardCacheSize; ++i) {
|
|
SCOPED_TRACE(i);
|
|
GURL url(embedded_test_server()->GetURL(base::StringPrintf("a%zu.com", i),
|
|
"/title1.html"));
|
|
ASSERT_TRUE(NavigateToURL(shell(), url));
|
|
RenderFrameHostImpl* rfh = current_frame_host();
|
|
EXPECT_FALSE(rfh->GetProcess()->IsProcessBackgrounded());
|
|
delete_observers.emplace_back(new RenderFrameDeletedObserver(rfh));
|
|
}
|
|
// Check that a0-2 are cached and backgrounded.
|
|
for (size_t i = 0; i < kBackForwardCacheSize - 1; ++i) {
|
|
SCOPED_TRACE(i);
|
|
ExpectCached(*delete_observers[i], /*cached=*/true, /*backgrounded=*/true);
|
|
}
|
|
|
|
// Navigate to a page which causes the processes for a[1-3] to be
|
|
// foregrounded.
|
|
GURL url(embedded_test_server()->GetURL("a4.com", "/title1.html"));
|
|
ASSERT_TRUE(NavigateToURL(shell(), url));
|
|
|
|
// Assert that we really have set up the situation we want where the processes
|
|
// are shared and in the foreground.
|
|
RenderFrameHostImpl* rfh = current_frame_host();
|
|
ASSERT_FALSE(rfh->GetProcess()->IsProcessBackgrounded());
|
|
|
|
delete_observers[1]->render_frame_host()->GetProcess()->SetPriorityOverride(
|
|
/*foreground=*/true);
|
|
delete_observers[2]->render_frame_host()->GetProcess()->SetPriorityOverride(
|
|
/*foreground=*/true);
|
|
delete_observers[3]->render_frame_host()->GetProcess()->SetPriorityOverride(
|
|
/*foreground=*/true);
|
|
|
|
// The page should be evicted.
|
|
delete_observers[1]->WaitUntilDeleted();
|
|
|
|
// Check that a0 is cached and backgrounded.
|
|
ExpectCached(*delete_observers[0], /*cached=*/true, /*backgrounded=*/true);
|
|
// Check that a2-3 are cached and foregrounded.
|
|
ExpectCached(*delete_observers[2], /*cached=*/true, /*backgrounded=*/false);
|
|
ExpectCached(*delete_observers[3], /*cached=*/true, /*backgrounded=*/false);
|
|
}
|
|
|
|
// Tests that |RenderFrameHost::ForEachRenderFrameHost| and
|
|
// |WebContents::ForEachRenderFrameHost| behave correctly with bfcached
|
|
// RenderFrameHosts.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, ForEachRenderFrameHost) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a(embedded_test_server()->GetURL(
|
|
"a.com", "/cross_site_iframe_factory.html?a(b(c),d)"));
|
|
GURL url_e(embedded_test_server()->GetURL("e.com", "/title1.html"));
|
|
|
|
std::vector<RenderFrameDeletedObserver*> rfh_observers;
|
|
|
|
// 1) Navigate to a(b(c),d).
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host();
|
|
RenderFrameHostImpl* rfh_c = rfh_b->child_at(0)->current_frame_host();
|
|
RenderFrameHostImpl* rfh_d = rfh_a->child_at(1)->current_frame_host();
|
|
RenderFrameDeletedObserver a_observer(rfh_a), b_observer(rfh_b),
|
|
c_observer(rfh_c), d_observer(rfh_d);
|
|
rfh_observers.insert(rfh_observers.end(),
|
|
{&a_observer, &b_observer, &c_observer, &d_observer});
|
|
|
|
// Ensure the visited frames are what we would expect for the page before
|
|
// entering bfcache.
|
|
EXPECT_THAT(CollectAllRenderFrameHosts(rfh_a),
|
|
testing::ElementsAre(rfh_a, rfh_b, rfh_d, rfh_c));
|
|
EXPECT_THAT(CollectAllRenderFrameHosts(web_contents()),
|
|
testing::ElementsAre(rfh_a, rfh_b, rfh_d, rfh_c));
|
|
|
|
// 2) Navigate to e.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_e));
|
|
RenderFrameHostImpl* rfh_e = current_frame_host();
|
|
RenderFrameDeletedObserver e_observer(rfh_e);
|
|
rfh_observers.push_back(&e_observer);
|
|
ASSERT_THAT(rfh_observers, Each(Not(Deleted())));
|
|
EXPECT_THAT(Elements({rfh_a, rfh_b, rfh_c, rfh_d}),
|
|
Each(InBackForwardCache()));
|
|
EXPECT_THAT(rfh_e, Not(InBackForwardCache()));
|
|
|
|
// When starting iteration from the primary frame, we shouldn't see any of the
|
|
// frames in bfcache.
|
|
EXPECT_THAT(CollectAllRenderFrameHosts(rfh_e), testing::ElementsAre(rfh_e));
|
|
|
|
// When starting iteration from a bfcached RFH, we should see the frame itself
|
|
// and its descendants in breadth first order.
|
|
EXPECT_THAT(CollectAllRenderFrameHosts(rfh_a),
|
|
testing::ElementsAre(rfh_a, rfh_b, rfh_d, rfh_c));
|
|
|
|
// Ensure that starting iteration from a subframe of a bfcached frame also
|
|
// works.
|
|
EXPECT_THAT(CollectAllRenderFrameHosts(rfh_b),
|
|
testing::ElementsAre(rfh_b, rfh_c));
|
|
|
|
// When iterating over all RenderFrameHosts in a WebContents, we should see
|
|
// the RFHs of both the primary page and the bfcached page.
|
|
EXPECT_THAT(CollectAllRenderFrameHosts(web_contents()),
|
|
testing::UnorderedElementsAre(rfh_a, rfh_b, rfh_c, rfh_d, rfh_e));
|
|
|
|
{
|
|
// If we stop iteration in |WebContents::ForEachRenderFrameHost|, we stop
|
|
// the entire iteration, not just iteration in the page being iterated at
|
|
// that point. In this case, if we stop iteration in the primary page, we do
|
|
// not continue to iterate in the bfcached page.
|
|
bool stopped = false;
|
|
web_contents()->ForEachRenderFrameHost(
|
|
base::BindLambdaForTesting([&](RenderFrameHostImpl* rfh) {
|
|
EXPECT_FALSE(stopped);
|
|
stopped = true;
|
|
return RenderFrameHost::FrameIterationAction::kStop;
|
|
}));
|
|
}
|
|
}
|
|
|
|
// Tests that |RenderFrameHostImpl::ForEachRenderFrameHostIncludingSpeculative|
|
|
// and |WebContentsImpl::ForEachRenderFrameHostIncludingSpeculative|
|
|
// behave correctly when a FrameTreeNode has both a speculative RFH and a
|
|
// bfcached RFH.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
ForEachRenderFrameHostWithSpeculative) {
|
|
IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess());
|
|
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"));
|
|
|
|
std::vector<RenderFrameDeletedObserver*> rfh_observers;
|
|
|
|
// 1) Navigate to a.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver a_observer(rfh_a);
|
|
rfh_observers.push_back(&a_observer);
|
|
|
|
// 2) Navigate to b.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
RenderFrameHostImpl* rfh_b = current_frame_host();
|
|
RenderFrameDeletedObserver b_observer(rfh_b);
|
|
rfh_observers.push_back(&b_observer);
|
|
ASSERT_THAT(rfh_observers, Each(Not(Deleted())));
|
|
|
|
// 3) Begin navigation to c.
|
|
TestNavigationManager nav_manager(web_contents(), url_c);
|
|
shell()->LoadURL(url_c);
|
|
ASSERT_TRUE(nav_manager.WaitForRequestStart());
|
|
|
|
RenderFrameHostImpl* rfh_c =
|
|
rfh_b->frame_tree_node()->render_manager()->speculative_frame_host();
|
|
ASSERT_TRUE(rfh_c);
|
|
EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kInBackForwardCache,
|
|
rfh_a->lifecycle_state());
|
|
EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kActive,
|
|
rfh_b->lifecycle_state());
|
|
EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kSpeculative,
|
|
rfh_c->lifecycle_state());
|
|
|
|
// When starting iteration from the bfcached RFH, we should not see the
|
|
// speculative RFH.
|
|
EXPECT_THAT(CollectAllRenderFrameHostsIncludingSpeculative(rfh_a),
|
|
testing::ElementsAre(rfh_a));
|
|
|
|
// When starting iteration from the primary frame, we shouldn't see the
|
|
// bfcached RFH, but we should see the speculative RFH.
|
|
EXPECT_THAT(CollectAllRenderFrameHostsIncludingSpeculative(rfh_b),
|
|
testing::UnorderedElementsAre(rfh_b, rfh_c));
|
|
|
|
// When starting iteration from the speculative RFH, we should only see
|
|
// the speculative RFH. In particular, we should not see the bfcached RFH.
|
|
EXPECT_THAT(CollectAllRenderFrameHostsIncludingSpeculative(rfh_c),
|
|
testing::ElementsAre(rfh_c));
|
|
|
|
// When iterating over all RenderFrameHosts in a WebContents, we should see
|
|
// the RFHs of both the primary page and the bfcached page.
|
|
EXPECT_THAT(CollectAllRenderFrameHostsIncludingSpeculative(web_contents()),
|
|
testing::UnorderedElementsAre(rfh_a, rfh_b, rfh_c));
|
|
}
|
|
|
|
// Similar to BackForwardCacheBrowserTest.SubframeSurviveCache*
|
|
// Test case: a1(b2) -> c3 -> a1(b2)
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, SubframeSurviveCache1) {
|
|
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"));
|
|
|
|
std::vector<RenderFrameDeletedObserver*> rfh_observer;
|
|
|
|
// 1) Navigate to a1(b2).
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* a1 = current_frame_host();
|
|
RenderFrameHostImpl* b2 = a1->child_at(0)->current_frame_host();
|
|
RenderFrameDeletedObserver a1_observer(a1), b2_observer(b2);
|
|
rfh_observer.insert(rfh_observer.end(), {&a1_observer, &b2_observer});
|
|
EXPECT_TRUE(ExecJs(b2, "window.alive = 'I am alive';"));
|
|
|
|
// 2) Navigate to c3.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_c));
|
|
RenderFrameHostImpl* c3 = current_frame_host();
|
|
RenderFrameDeletedObserver c3_observer(c3);
|
|
rfh_observer.push_back(&c3_observer);
|
|
ASSERT_THAT(rfh_observer, Each(Not(Deleted())));
|
|
EXPECT_THAT(Elements({a1, b2}), Each(InBackForwardCache()));
|
|
EXPECT_THAT(c3, Not(InBackForwardCache()));
|
|
|
|
// 3) Go back to a1(b2).
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ASSERT_THAT(rfh_observer, Each(Not(Deleted())));
|
|
EXPECT_THAT(Elements({a1, b2}), Each(Not(InBackForwardCache())));
|
|
EXPECT_THAT(c3, InBackForwardCache());
|
|
|
|
// Even after a new IPC round trip with the renderer, b2 must still be alive.
|
|
EXPECT_EQ("I am alive", EvalJs(b2, "window.alive"));
|
|
EXPECT_FALSE(b2_observer.deleted());
|
|
|
|
ExpectRestored(FROM_HERE);
|
|
}
|
|
|
|
// Similar to BackForwardCacheBrowserTest.SubframeSurviveCache*
|
|
// Test case: a1(b2) -> b3 -> a1(b2).
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, SubframeSurviveCache2) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a(embedded_test_server()->GetURL(
|
|
"a.com", "/cross_site_iframe_factory.html?a(b)"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
std::vector<RenderFrameDeletedObserver*> rfh_observer;
|
|
|
|
// 1) Navigate to a1(b2).
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* a1 = current_frame_host();
|
|
RenderFrameHostImpl* b2 = a1->child_at(0)->current_frame_host();
|
|
RenderFrameDeletedObserver a1_observer(a1), b2_observer(b2);
|
|
rfh_observer.insert(rfh_observer.end(), {&a1_observer, &b2_observer});
|
|
EXPECT_TRUE(ExecJs(b2, "window.alive = 'I am alive';"));
|
|
|
|
// 2) Navigate to b3.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
RenderFrameHostImpl* b3 = current_frame_host();
|
|
RenderFrameDeletedObserver b3_observer(b3);
|
|
rfh_observer.push_back(&b3_observer);
|
|
ASSERT_THAT(rfh_observer, Each(Not(Deleted())));
|
|
EXPECT_THAT(Elements({a1, b2}), Each(InBackForwardCache()));
|
|
EXPECT_THAT(b3, Not(InBackForwardCache()));
|
|
|
|
// 3) Go back to a1(b2).
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ASSERT_THAT(rfh_observer, Each(Not(Deleted())));
|
|
EXPECT_EQ(a1, current_frame_host());
|
|
EXPECT_THAT(Elements({a1, b2}), Each(Not(InBackForwardCache())));
|
|
EXPECT_THAT(b3, InBackForwardCache());
|
|
|
|
// Even after a new IPC round trip with the renderer, b2 must still be alive.
|
|
EXPECT_EQ("I am alive", EvalJs(b2, "window.alive"));
|
|
EXPECT_FALSE(b2_observer.deleted());
|
|
|
|
ExpectRestored(FROM_HERE);
|
|
}
|
|
|
|
// Similar to BackForwardCacheBrowserTest.tSubframeSurviveCache*
|
|
// Test case: a1(b2) -> b3(a4) -> a1(b2) -> b3(a4)
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, SubframeSurviveCache3) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a(embedded_test_server()->GetURL(
|
|
"a.com", "/cross_site_iframe_factory.html?a(b)"));
|
|
GURL url_b(embedded_test_server()->GetURL(
|
|
"b.com", "/cross_site_iframe_factory.html?b(a)"));
|
|
|
|
std::vector<RenderFrameDeletedObserver*> rfh_observer;
|
|
|
|
// 1) Navigate to a1(b2).
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* a1 = current_frame_host();
|
|
RenderFrameHostImpl* b2 = a1->child_at(0)->current_frame_host();
|
|
RenderFrameDeletedObserver a1_observer(a1), b2_observer(b2);
|
|
rfh_observer.insert(rfh_observer.end(), {&a1_observer, &b2_observer});
|
|
EXPECT_TRUE(ExecJs(b2, "window.alive = 'I am alive';"));
|
|
|
|
// 2) Navigate to b3(a4)
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
RenderFrameHostImpl* b3 = current_frame_host();
|
|
RenderFrameHostImpl* a4 = b3->child_at(0)->current_frame_host();
|
|
RenderFrameDeletedObserver b3_observer(b3), a4_observer(a4);
|
|
rfh_observer.insert(rfh_observer.end(), {&b3_observer, &a4_observer});
|
|
ASSERT_THAT(rfh_observer, Each(Not(Deleted())));
|
|
EXPECT_THAT(Elements({a1, b2}), Each(InBackForwardCache()));
|
|
EXPECT_THAT(Elements({b3, a4}), Each(Not(InBackForwardCache())));
|
|
EXPECT_TRUE(ExecJs(a4, "window.alive = 'I am alive';"));
|
|
|
|
// 3) Go back to a1(b2).
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ASSERT_THAT(rfh_observer, Each(Not(Deleted())));
|
|
EXPECT_EQ(a1, current_frame_host());
|
|
EXPECT_THAT(Elements({a1, b2}), Each(Not(InBackForwardCache())));
|
|
EXPECT_THAT(Elements({b3, a4}), Each(InBackForwardCache()));
|
|
|
|
// Even after a new IPC round trip with the renderer, b2 must still be alive.
|
|
EXPECT_EQ("I am alive", EvalJs(b2, "window.alive"));
|
|
EXPECT_FALSE(b2_observer.deleted());
|
|
|
|
ExpectRestored(FROM_HERE);
|
|
|
|
// 4) Go forward to b3(a4).
|
|
web_contents()->GetController().GoForward();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ASSERT_THAT(rfh_observer, Each(Not(Deleted())));
|
|
EXPECT_EQ(b3, current_frame_host());
|
|
EXPECT_THAT(Elements({a1, b2}), Each(InBackForwardCache()));
|
|
EXPECT_THAT(Elements({b3, a4}), Each(Not(InBackForwardCache())));
|
|
|
|
// Even after a new IPC round trip with the renderer, a4 must still be alive.
|
|
EXPECT_EQ("I am alive", EvalJs(a4, "window.alive"));
|
|
EXPECT_FALSE(a4_observer.deleted());
|
|
|
|
ExpectRestored(FROM_HERE);
|
|
}
|
|
|
|
// Similar to BackForwardCacheBrowserTest.SubframeSurviveCache*
|
|
// Test case: a1(b2) -> b3 -> a4 -> b5 -> a1(b2).
|
|
IN_PROC_BROWSER_TEST_F(HighCacheSizeBackForwardCacheBrowserTest,
|
|
SubframeSurviveCache4) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_ab(embedded_test_server()->GetURL(
|
|
"a.com", "/cross_site_iframe_factory.html?a(b)"));
|
|
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
std::vector<RenderFrameDeletedObserver*> rfh_observer;
|
|
|
|
// 1) Navigate to a1(b2).
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_ab));
|
|
RenderFrameHostImpl* a1 = current_frame_host();
|
|
RenderFrameHostImpl* b2 = a1->child_at(0)->current_frame_host();
|
|
RenderFrameDeletedObserver a1_observer(a1), b2_observer(b2);
|
|
rfh_observer.insert(rfh_observer.end(), {&a1_observer, &b2_observer});
|
|
EXPECT_TRUE(ExecJs(b2, "window.alive = 'I am alive';"));
|
|
|
|
// 2) Navigate to b3.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
RenderFrameHostImpl* b3 = current_frame_host();
|
|
RenderFrameDeletedObserver b3_observer(b3);
|
|
rfh_observer.push_back(&b3_observer);
|
|
ASSERT_THAT(rfh_observer, Each(Not(Deleted())));
|
|
EXPECT_THAT(Elements({a1, b2}), Each(InBackForwardCache()));
|
|
EXPECT_THAT(b3, Not(InBackForwardCache()));
|
|
|
|
// 3) Navigate to a4.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* a4 = current_frame_host();
|
|
RenderFrameDeletedObserver a4_observer(a4);
|
|
rfh_observer.push_back(&a4_observer);
|
|
ASSERT_THAT(rfh_observer, Each(Not(Deleted())));
|
|
|
|
// 4) Navigate to b5
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
RenderFrameHostImpl* b5 = current_frame_host();
|
|
RenderFrameDeletedObserver b5_observer(b5);
|
|
rfh_observer.push_back(&b5_observer);
|
|
ASSERT_THAT(rfh_observer, Each(Not(Deleted())));
|
|
EXPECT_THAT(Elements({a1, b2, b3, a4}), Each(InBackForwardCache()));
|
|
EXPECT_THAT(b5, Not(InBackForwardCache()));
|
|
|
|
// 3) Go back to a1(b2).
|
|
web_contents()->GetController().GoToOffset(-3);
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_EQ(a1, current_frame_host());
|
|
ASSERT_THAT(rfh_observer, Each(Not(Deleted())));
|
|
EXPECT_THAT(Elements({b3, a4, b5}), Each(InBackForwardCache()));
|
|
EXPECT_THAT(Elements({a1, b2}), Each(Not(InBackForwardCache())));
|
|
|
|
// Even after a new IPC round trip with the renderer, b2 must still be alive.
|
|
EXPECT_EQ("I am alive", EvalJs(b2, "window.alive"));
|
|
EXPECT_FALSE(b2_observer.deleted());
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
NavigationsAreFullyCommitted) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
// During a navigation, the document being navigated *away from* can either be
|
|
// deleted or stored into the BackForwardCache. The document being navigated
|
|
// *to* can either be new or restored from the BackForwardCache.
|
|
//
|
|
// This test covers every combination:
|
|
//
|
|
// 1. Navigate to a cacheable page (()->A)
|
|
// 2. Navigate to an uncacheable page (A->B)
|
|
// 3. Go Back to a cached page (B->A)
|
|
// 4. Navigate to a cacheable page (A->C)
|
|
// 5. Go Back to a cached page (C->A)
|
|
//
|
|
// +-+-------+----------------+---------------+
|
|
// |#|nav | curr_document | dest_document |
|
|
// +-+-------+----------------+---------------|
|
|
// |1|(()->A)| N/A | new |
|
|
// |2|(A->B) | cached | new |
|
|
// |3|(B->A) | deleted | restored |
|
|
// |4|(A->C) | cached | new |
|
|
// |5|(C->A) | cached | restored |
|
|
// +-+-------+----------------+---------------+
|
|
//
|
|
// As part of these navigations we check that LastCommittedURL was updated,
|
|
// to verify that the frame wasn't simply swapped in without actually
|
|
// committing.
|
|
|
|
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_b(embedded_test_server()->GetURL(
|
|
"b.com", "/back_forward_cache/page_with_dedicated_worker.html"));
|
|
GURL url_c(embedded_test_server()->GetURL("c.com", "/title1.html"));
|
|
|
|
// 1. Navigate to a cacheable page (A).
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
|
|
// 2. Navigate from a cacheable page to an uncacheable page (A->B).
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
EXPECT_EQ(web_contents()->GetLastCommittedURL(), url_b);
|
|
RenderFrameHostImpl* rfh_b = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b);
|
|
|
|
// Page A should be in the cache.
|
|
EXPECT_FALSE(delete_observer_rfh_a.deleted());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
|
|
// 3. Navigate from an uncacheable to a cached page page (B->A).
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_EQ(web_contents()->GetLastCommittedURL(), url_a);
|
|
|
|
// Page B should be deleted (not cached).
|
|
delete_observer_rfh_b.WaitUntilDeleted();
|
|
|
|
ExpectRestored(FROM_HERE);
|
|
|
|
// 4. Navigate from a cacheable page to a cacheable page (A->C).
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_c));
|
|
EXPECT_EQ(web_contents()->GetLastCommittedURL(), url_c);
|
|
RenderFrameHostImpl* rfh_c = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_c(rfh_c);
|
|
|
|
// Page A should be in the cache.
|
|
EXPECT_FALSE(delete_observer_rfh_a.deleted());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
|
|
// 5. Navigate from a cacheable page to a cached page (C->A).
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_EQ(web_contents()->GetLastCommittedURL(), url_a);
|
|
|
|
// Page C should be in the cache.
|
|
EXPECT_FALSE(delete_observer_rfh_c.deleted());
|
|
EXPECT_TRUE(rfh_c->IsInBackForwardCache());
|
|
|
|
ExpectRestored(FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
ProxiesAreStoredAndRestored) {
|
|
// This test makes assumption about where iframe processes live.
|
|
if (!AreAllSitesIsolatedForTesting())
|
|
return;
|
|
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
// During a navigation, the document being navigated *away from* can either be
|
|
// deleted or stored into the BackForwardCache. The document being navigated
|
|
// *to* can either be new or restored from the BackForwardCache.
|
|
//
|
|
// This test covers every combination:
|
|
//
|
|
// 1. Navigate to a cacheable page (()->A)
|
|
// 2. Navigate to an uncacheable page (A->B)
|
|
// 3. Go Back to a cached page (B->A)
|
|
// 4. Navigate to a cacheable page (A->C)
|
|
// 5. Go Back to a cached page (C->A)
|
|
//
|
|
// +-+-------+----------------+---------------+
|
|
// |#|nav | curr_document | dest_document |
|
|
// +-+-------+----------------+---------------|
|
|
// |1|(()->A)| N/A | new |
|
|
// |2|(A->B) | cached | new |
|
|
// |3|(B->A) | deleted | restored |
|
|
// |4|(A->C) | cached | new |
|
|
// |5|(C->A) | cached | restored |
|
|
// +-+-------+----------------+---------------+
|
|
//
|
|
// We use pages with cross process iframes to verify that proxy storage and
|
|
// retrieval works well in every possible combination.
|
|
|
|
GURL url_a(embedded_test_server()->GetURL(
|
|
"a.com", "/cross_site_iframe_factory.html?a(i,j)"));
|
|
GURL url_b(embedded_test_server()->GetURL(
|
|
"b.com", "/back_forward_cache/page_with_dedicated_worker.html"));
|
|
GURL url_c(embedded_test_server()->GetURL(
|
|
"c.com", "/cross_site_iframe_factory.html?c(k,l,m)"));
|
|
|
|
NavigationControllerImpl& controller = web_contents()->GetController();
|
|
BackForwardCacheImpl& cache = controller.GetBackForwardCache();
|
|
|
|
// 1. Navigate to a cacheable page (A).
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
EXPECT_EQ(2u, render_frame_host_manager()->GetProxyCount());
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
std::string frame_tree_a = DepictFrameTree(rfh_a->frame_tree_node());
|
|
|
|
// 2. Navigate from a cacheable page to an uncacheable page (A->B).
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
EXPECT_EQ(0u, render_frame_host_manager()->GetProxyCount());
|
|
RenderFrameHostImpl* rfh_b = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b);
|
|
|
|
// Page A should be in the cache.
|
|
EXPECT_FALSE(delete_observer_rfh_a.deleted());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
|
|
// Verify proxies are stored as well.
|
|
auto* cached_entry = cache.GetEntry(rfh_a->nav_entry_id());
|
|
EXPECT_EQ(2u, cached_entry->proxy_hosts.size());
|
|
|
|
// 3. Navigate from an uncacheable to a cached page page (B->A).
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
// Note: We still have a transition proxy that will be used to perform the
|
|
// frame swap. It gets deleted with rfh_b below.
|
|
EXPECT_EQ(3u, render_frame_host_manager()->GetProxyCount());
|
|
|
|
// Page B should be deleted (not cached).
|
|
delete_observer_rfh_b.WaitUntilDeleted();
|
|
EXPECT_EQ(2u, render_frame_host_manager()->GetProxyCount());
|
|
|
|
// Page A should still have the correct frame tree.
|
|
EXPECT_EQ(frame_tree_a,
|
|
DepictFrameTree(current_frame_host()->frame_tree_node()));
|
|
|
|
// 4. Navigate from a cacheable page to a cacheable page (A->C).
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_c));
|
|
EXPECT_EQ(3u, render_frame_host_manager()->GetProxyCount());
|
|
RenderFrameHostImpl* rfh_c = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_c(rfh_c);
|
|
|
|
// Page A should be in the cache.
|
|
EXPECT_FALSE(delete_observer_rfh_a.deleted());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
|
|
// Verify proxies are stored as well.
|
|
cached_entry = cache.GetEntry(rfh_a->nav_entry_id());
|
|
EXPECT_EQ(2u, cached_entry->proxy_hosts.size());
|
|
|
|
// 5. Navigate from a cacheable page to a cached page (C->A).
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_EQ(2u, render_frame_host_manager()->GetProxyCount());
|
|
|
|
// Page A should still have the correct frame tree.
|
|
EXPECT_EQ(frame_tree_a,
|
|
DepictFrameTree(current_frame_host()->frame_tree_node()));
|
|
|
|
// Page C should be in the cache.
|
|
EXPECT_FALSE(delete_observer_rfh_c.deleted());
|
|
EXPECT_TRUE(rfh_c->IsInBackForwardCache());
|
|
|
|
// Verify proxies are stored as well.
|
|
cached_entry = cache.GetEntry(rfh_c->nav_entry_id());
|
|
EXPECT_EQ(3u, cached_entry->proxy_hosts.size());
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
RestoredProxiesAreFunctional) {
|
|
// This test makes assumption about where iframe processes live.
|
|
if (!AreAllSitesIsolatedForTesting())
|
|
return;
|
|
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
// Page A is cacheable, while page B is not.
|
|
GURL url_a(embedded_test_server()->GetURL(
|
|
"a.com", "/cross_site_iframe_factory.html?a(z)"));
|
|
GURL url_b(embedded_test_server()->GetURL(
|
|
"b.com", "/back_forward_cache/page_with_dedicated_worker.html"));
|
|
GURL test_url(embedded_test_server()->GetURL("c.com", "/title1.html"));
|
|
|
|
NavigationControllerImpl& controller = web_contents()->GetController();
|
|
|
|
// 1. Navigate to a cacheable page (A).
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
|
|
// 2. Navigate from a cacheable page to an uncacheable page (A->B).
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
// 3. Navigate from an uncacheable to a cached page page (B->A).
|
|
// This restores the top frame's proxy in the z.com (iframe's) process.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
// 4. Verify that the main frame's z.com proxy is still functional.
|
|
RenderFrameHostImpl* iframe =
|
|
rfh_a->frame_tree_node()->child_at(0)->current_frame_host();
|
|
EXPECT_TRUE(ExecJs(iframe, "top.location.href = '" + test_url.spec() + "';"));
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
// We expect to have navigated through the proxy.
|
|
EXPECT_EQ(test_url, controller.GetLastCommittedEntry()->GetURL());
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
PageWithDedicatedWorkerNotCached) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(),
|
|
embedded_test_server()->GetURL(
|
|
"a.com", "/back_forward_cache/page_with_dedicated_worker.html")));
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(current_frame_host());
|
|
|
|
// 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).
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
}
|
|
|
|
// TODO(https://crbug.com/154571): Shared workers are not available on Android.
|
|
#if defined(OS_ANDROID)
|
|
#define MAYBE_PageWithSharedWorkerNotCached \
|
|
DISABLED_PageWithSharedWorkerNotCached
|
|
#else
|
|
#define MAYBE_PageWithSharedWorkerNotCached PageWithSharedWorkerNotCached
|
|
#endif
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
MAYBE_PageWithSharedWorkerNotCached) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(),
|
|
embedded_test_server()->GetURL(
|
|
"a.com", "/back_forward_cache/page_with_shared_worker.html")));
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(current_frame_host());
|
|
|
|
// 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).
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
|
|
// Go back.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kBlocklistedFeatures},
|
|
{blink::scheduler::WebSchedulerTrackedFeature::kSharedWorker}, {}, {},
|
|
FROM_HERE);
|
|
}
|
|
|
|
#if defined(OS_MAC) || defined(OS_LINUX) || defined(OS_CHROMEOS)
|
|
// Flaky: https://crbug.com/1076594 on Mac
|
|
// Flaky: https://crbug.com/1102571 on Linux Ozone
|
|
#define MAYBE_SubframeWithDisallowedFeatureNotCached \
|
|
DISABLED_SubframeWithDisallowedFeatureNotCached
|
|
#else
|
|
#define MAYBE_SubframeWithDisallowedFeatureNotCached \
|
|
SubframeWithDisallowedFeatureNotCached
|
|
#endif
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
MAYBE_SubframeWithDisallowedFeatureNotCached) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
// Navigate to a page with an iframe that contains a dedicated worker.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(),
|
|
embedded_test_server()->GetURL(
|
|
"a.com", "/back_forward_cache/dedicated_worker_in_subframe.html")));
|
|
RenderFrameDeletedObserver delete_rfh_a(current_frame_host());
|
|
|
|
// 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).
|
|
delete_rfh_a.WaitUntilDeleted();
|
|
|
|
// Go back.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kBlocklistedFeatures},
|
|
{blink::scheduler::WebSchedulerTrackedFeature::kDedicatedWorkerOrWorklet},
|
|
{}, {}, 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();
|
|
}
|
|
|
|
// Check that unload event handlers are not dispatched when the page goes
|
|
// into BackForwardCache.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
ConfirmUnloadEventNotFired) {
|
|
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) Set unload handler and check the title.
|
|
EXPECT_TRUE(ExecJs(rfh_a,
|
|
"document.title = 'loaded!';"
|
|
"window.addEventListener('unload', () => {"
|
|
" document.title = 'unloaded!';"
|
|
"});"));
|
|
{
|
|
std::u16string title_when_loaded = u"loaded!";
|
|
TitleWatcher title_watcher(web_contents(), title_when_loaded);
|
|
EXPECT_EQ(title_watcher.WaitAndGetTitle(), title_when_loaded);
|
|
}
|
|
|
|
// 3) Navigate to 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());
|
|
|
|
// 4) Go back to A and check the title again.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
EXPECT_FALSE(delete_observer_rfh_a.deleted());
|
|
EXPECT_FALSE(delete_observer_rfh_b.deleted());
|
|
EXPECT_EQ(rfh_a, current_frame_host());
|
|
EXPECT_TRUE(rfh_b->IsInBackForwardCache());
|
|
{
|
|
std::u16string title_when_loaded = u"loaded!";
|
|
TitleWatcher title_watcher(web_contents(), title_when_loaded);
|
|
EXPECT_EQ(title_watcher.WaitAndGetTitle(), title_when_loaded);
|
|
}
|
|
}
|
|
|
|
// Flaky on Linux: https://crbug.com/1054194
|
|
#if defined(OS_LINUX) || defined(OS_CHROMEOS)
|
|
#define MAYBE_DoesNotCacheIfRecordingAudio DISABLED_DoesNotCacheIfRecordingAudio
|
|
#else
|
|
#define MAYBE_DoesNotCacheIfRecordingAudio DoesNotCacheIfRecordingAudio
|
|
#endif
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
MAYBE_DoesNotCacheIfRecordingAudio) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
BackForwardCacheDisabledTester tester;
|
|
|
|
// Navigate to an empty page.
|
|
GURL url(embedded_test_server()->GetURL("/title1.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
int process_id = current_frame_host()->GetProcess()->GetID();
|
|
int routing_id = current_frame_host()->GetRoutingID();
|
|
|
|
// Request for audio recording.
|
|
EXPECT_EQ("success", EvalJs(current_frame_host(), R"(
|
|
new Promise(resolve => {
|
|
navigator.mediaDevices.getUserMedia({audio: true})
|
|
.then(m => { resolve("success"); })
|
|
.catch(() => { resolve("error"); });
|
|
});
|
|
)"));
|
|
|
|
RenderFrameDeletedObserver deleted(current_frame_host());
|
|
|
|
// 2) Navigate away.
|
|
shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// The page was still recording audio when we navigated away, so it shouldn't
|
|
// have been cached.
|
|
deleted.WaitUntilDeleted();
|
|
|
|
// 3) Go back. Note that the reason for kWasGrantedMediaAccess occurs after
|
|
// MediaDevicesDispatcherHost is called, hence, both are reasons for the page
|
|
// not being restored.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
auto reason = BackForwardCacheDisable::DisabledReason(
|
|
BackForwardCacheDisable::DisabledReasonId::kMediaDevicesDispatcherHost);
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kWasGrantedMediaAccess,
|
|
BackForwardCacheMetrics::NotRestoredReason::
|
|
kDisableForRenderFrameHostCalled},
|
|
{}, {}, {reason}, FROM_HERE);
|
|
EXPECT_TRUE(
|
|
tester.IsDisabledForFrameWithReason(process_id, routing_id, reason));
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
DoesNotCacheIfSubframeRecordingAudio) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
BackForwardCacheDisabledTester tester;
|
|
|
|
// Navigate to a page with an iframe.
|
|
GURL url(embedded_test_server()->GetURL("/page_with_iframe.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
RenderFrameHostImpl* rfh = current_frame_host();
|
|
int process_id =
|
|
rfh->child_at(0)->current_frame_host()->GetProcess()->GetID();
|
|
int routing_id = rfh->child_at(0)->current_frame_host()->GetRoutingID();
|
|
|
|
// Request for audio recording from the subframe.
|
|
EXPECT_EQ("success", EvalJs(rfh->child_at(0)->current_frame_host(), R"(
|
|
new Promise(resolve => {
|
|
navigator.mediaDevices.getUserMedia({audio: true})
|
|
.then(m => { resolve("success"); })
|
|
.catch(() => { resolve("error"); });
|
|
});
|
|
)"));
|
|
|
|
RenderFrameDeletedObserver deleted(current_frame_host());
|
|
|
|
// 2) Navigate away.
|
|
shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// The page was still recording audio when we navigated away, so it shouldn't
|
|
// have been cached.
|
|
deleted.WaitUntilDeleted();
|
|
|
|
// 3) Go back. Note that the reason for kWasGrantedMediaAccess occurs after
|
|
// MediaDevicesDispatcherHost is called, hence, both are reasons for the page
|
|
// not being restored.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
auto reason = BackForwardCacheDisable::DisabledReason(
|
|
BackForwardCacheDisable::DisabledReasonId::kMediaDevicesDispatcherHost);
|
|
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kWasGrantedMediaAccess,
|
|
BackForwardCacheMetrics::NotRestoredReason::
|
|
kDisableForRenderFrameHostCalled},
|
|
{}, {}, {reason}, FROM_HERE);
|
|
EXPECT_TRUE(
|
|
tester.IsDisabledForFrameWithReason(process_id, routing_id, reason));
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
DoesNotCacheIfMediaDeviceSubscribed) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
BackForwardCacheDisabledTester tester;
|
|
|
|
// Navigate to a page with an iframe.
|
|
GURL url(embedded_test_server()->GetURL("/page_with_iframe.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
RenderFrameHostImpl* rfh = current_frame_host();
|
|
int process_id =
|
|
rfh->child_at(0)->current_frame_host()->GetProcess()->GetID();
|
|
int routing_id = rfh->child_at(0)->current_frame_host()->GetRoutingID();
|
|
|
|
EXPECT_EQ("success", EvalJs(rfh->child_at(0)->current_frame_host(), R"(
|
|
new Promise(resolve => {
|
|
navigator.mediaDevices.addEventListener('devicechange', function(event){});
|
|
resolve("success");
|
|
});
|
|
)"));
|
|
|
|
RenderFrameDeletedObserver deleted(current_frame_host());
|
|
|
|
// 2) Navigate away.
|
|
shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// The page was subscribed to media devices when we navigated away, so it
|
|
// shouldn't have been cached.
|
|
deleted.WaitUntilDeleted();
|
|
|
|
// 3) Go back.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
auto reason = BackForwardCacheDisable::DisabledReason(
|
|
BackForwardCacheDisable::DisabledReasonId::kMediaDevicesDispatcherHost);
|
|
ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason::
|
|
kDisableForRenderFrameHostCalled},
|
|
{}, {}, {reason}, FROM_HERE);
|
|
EXPECT_TRUE(
|
|
tester.IsDisabledForFrameWithReason(process_id, routing_id, reason));
|
|
}
|
|
|
|
// TODO(https://crbug.com/1075936) disabled due to flakiness
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
DISABLED_DoesNotCacheIfMainFrameStillLoading) {
|
|
net::test_server::ControllableHttpResponse response(embedded_test_server(),
|
|
"/main_document");
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
// 1) Navigate to a page that doesn't finish loading.
|
|
GURL url(embedded_test_server()->GetURL("a.com", "/main_document"));
|
|
TestNavigationManager navigation_manager(shell()->web_contents(), url);
|
|
shell()->LoadURL(url);
|
|
|
|
// The navigation starts.
|
|
EXPECT_TRUE(navigation_manager.WaitForRequestStart());
|
|
navigation_manager.ResumeNavigation();
|
|
|
|
// The server sends the first part of the response and waits.
|
|
response.WaitForRequest();
|
|
response.Send(
|
|
"HTTP/1.1 200 OK\r\n"
|
|
"Content-Type: text/html; charset=utf-8\r\n"
|
|
"\r\n"
|
|
"<html><body> ... ");
|
|
|
|
// The navigation finishes while the body is still loading.
|
|
navigation_manager.WaitForNavigationFinished();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(current_frame_host());
|
|
|
|
// 2) Navigate away.
|
|
shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// The page was still loading when we navigated away, so it shouldn't have
|
|
// been cached.
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
|
|
// 3) Go back.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_FALSE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason::kLoading}, {},
|
|
{}, {}, FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
DoesNotCacheIfImageStillLoading) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
// 1) Navigate to a page with an image that loads forever.
|
|
GURL url(embedded_test_server()->GetURL("a.com",
|
|
"/infinitely_loading_image.html"));
|
|
TestNavigationManager navigation_manager(shell()->web_contents(), url);
|
|
shell()->LoadURL(url);
|
|
|
|
// The navigation finishes while the image is still loading.
|
|
navigation_manager.WaitForNavigationFinished();
|
|
// Wait for the document to load DOM to ensure that kLoading is not
|
|
// one of the reasons why the document wasn't cached.
|
|
WaitForDOMContentLoaded(current_frame_host());
|
|
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(current_frame_host());
|
|
|
|
// 2) Navigate away.
|
|
shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// The page was still loading when we navigated away, so it shouldn't have
|
|
// been cached.
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
|
|
// 3) Go back.
|
|
TestNavigationManager navigation_manager_back(shell()->web_contents(), url);
|
|
web_contents()->GetController().GoBack();
|
|
navigation_manager_back.WaitForNavigationFinished();
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kBlocklistedFeatures},
|
|
{blink::scheduler::WebSchedulerTrackedFeature::
|
|
kOutstandingNetworkRequestOthers},
|
|
{}, {}, FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
DoesNotCacheLoadingSubframe) {
|
|
net::test_server::ControllableHttpResponse response(embedded_test_server(),
|
|
"/controlled");
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
// 1) Navigate to a page with an iframe that loads forever.
|
|
GURL url(embedded_test_server()->GetURL(
|
|
"a.com", "/back_forward_cache/controllable_subframe.html"));
|
|
TestNavigationManager navigation_manager(shell()->web_contents(), url);
|
|
shell()->LoadURL(url);
|
|
|
|
// The navigation finishes while the iframe is still loading.
|
|
navigation_manager.WaitForNavigationFinished();
|
|
|
|
// Wait for the iframe request to arrive, and leave it hanging with no
|
|
// response.
|
|
response.WaitForRequest();
|
|
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
|
|
// 2) Navigate away.
|
|
shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
// The page should not have been added to cache, since it had a subframe that
|
|
// was still loading at the time it was navigated away from.
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
|
|
// 3) Go back.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored(
|
|
{
|
|
BackForwardCacheMetrics::NotRestoredReason::kLoading,
|
|
BackForwardCacheMetrics::NotRestoredReason::kSubframeIsNavigating,
|
|
},
|
|
{}, {}, {}, FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
DoesNotCacheLoadingSubframeOfSubframe) {
|
|
net::test_server::ControllableHttpResponse response(embedded_test_server(),
|
|
"/controlled");
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
// 1) Navigate to a page with an iframe that contains yet another iframe, that
|
|
// hangs while loading.
|
|
GURL url(embedded_test_server()->GetURL(
|
|
"a.com", "/back_forward_cache/controllable_subframe_of_subframe.html"));
|
|
TestNavigationManager navigation_manager(shell()->web_contents(), url);
|
|
shell()->LoadURL(url);
|
|
|
|
// The navigation finishes while the iframe within an iframe is still loading.
|
|
navigation_manager.WaitForNavigationFinished();
|
|
|
|
// Wait for the innermost iframe request to arrive, and leave it hanging with
|
|
// no response.
|
|
response.WaitForRequest();
|
|
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver delete_rfh_a(rfh_a);
|
|
|
|
// 2) Navigate away.
|
|
shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
// The page should not have been added to the cache, since it had an iframe
|
|
// that was still loading at the time it was navigated away from.
|
|
delete_rfh_a.WaitUntilDeleted();
|
|
|
|
// 3) Go back.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored(
|
|
{
|
|
BackForwardCacheMetrics::NotRestoredReason::kLoading,
|
|
BackForwardCacheMetrics::NotRestoredReason::kSubframeIsNavigating,
|
|
},
|
|
{}, {}, {}, FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, CacheIfWebGL) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
// 1) Navigate to a page with WebGL usage
|
|
GURL url(embedded_test_server()->GetURL(
|
|
"example.com", "/back_forward_cache/page_with_webgl.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
|
|
// 2) Navigate away.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
|
|
|
|
// The page had an active WebGL context when we navigated away,
|
|
// but it should be cached.
|
|
|
|
// 3) Go back.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectRestored(FROM_HERE);
|
|
}
|
|
|
|
// Since blink::mojom::HidService binder is not added in
|
|
// content/browser/browser_interface_binders.cc for Android, this test is not
|
|
// applicable for this OS.
|
|
#if !defined(OS_ANDROID)
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, DoesNotCacheIfWebHID) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
// 1) Navigate to an empty page.
|
|
GURL url(embedded_test_server()->GetURL("/title1.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
|
|
// Request for HID devices.
|
|
EXPECT_EQ("success", EvalJs(current_frame_host(), R"(
|
|
new Promise(resolve => {
|
|
navigator.hid.getDevices()
|
|
.then(m => { resolve("success"); })
|
|
.catch(() => { resolve("error"); });
|
|
});
|
|
)"));
|
|
|
|
RenderFrameDeletedObserver deleted(current_frame_host());
|
|
|
|
// 2) Navigate away.
|
|
shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// The page uses WebHID so it should be deleted.
|
|
deleted.WaitUntilDeleted();
|
|
|
|
// 3) Go back.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kBlocklistedFeatures},
|
|
{blink::scheduler::WebSchedulerTrackedFeature::kWebHID}, {}, {},
|
|
FROM_HERE);
|
|
}
|
|
#endif // !defined(OS_ANDROID)
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
WakeLockReleasedUponEnteringBfcache) {
|
|
ASSERT_TRUE(CreateHttpsServer()->Start());
|
|
|
|
// 1) Navigate to a page with WakeLock usage.
|
|
GURL url(https_server()->GetURL(
|
|
"a.com", "/back_forward_cache/page_with_wakelock.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
// Acquire WakeLock.
|
|
EXPECT_EQ("DONE", EvalJs(rfh_a, "acquireWakeLock()"));
|
|
// Make sure that WakeLock is not released yet.
|
|
EXPECT_FALSE(EvalJs(rfh_a, "wakeLockIsReleased()").ExtractBool());
|
|
|
|
// 2) Navigate away.
|
|
shell()->LoadURL(https_server()->GetURL("b.com", "/title1.html"));
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
|
|
// 3) Go back to the page with WakeLock, restored from BackForwardCache.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_EQ(current_frame_host(), rfh_a);
|
|
EXPECT_TRUE(EvalJs(rfh_a, "wakeLockIsReleased()").ExtractBool());
|
|
ExpectRestored(FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
DoesNotCacheIfWebFileSystem) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
// 1) Navigate to a page with WebFileSystem usage.
|
|
GURL url(embedded_test_server()->GetURL("/fileapi/request_test.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver deleted(rfh_a);
|
|
|
|
// 2) Navigate away.
|
|
shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
// The page uses WebFilesystem so it should be deleted.
|
|
deleted.WaitUntilDeleted();
|
|
|
|
// 3) Go back to the page with WebFileSystem.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kBlocklistedFeatures},
|
|
{blink::scheduler::WebSchedulerTrackedFeature::kWebFileSystem}, {}, {},
|
|
FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, DoesNotCacheIfHttpError) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
GURL error_url(embedded_test_server()->GetURL("a.com", "/page404.html"));
|
|
GURL url(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// Navigate to an error page.
|
|
EXPECT_TRUE(NavigateToURL(shell(), error_url));
|
|
EXPECT_EQ(net::HTTP_NOT_FOUND, current_frame_host()->last_http_status_code());
|
|
RenderFrameDeletedObserver delete_rfh_a(current_frame_host());
|
|
|
|
// Navigate away.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
|
|
// The page did not return 200 (OK), so it shouldn't have been cached.
|
|
delete_rfh_a.WaitUntilDeleted();
|
|
|
|
// Go back.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kHTTPStatusNotOK}, {}, {},
|
|
{}, FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, DoesNotCacheIdleManager) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
// 1) Navigate to a page and start using the IdleManager class.
|
|
GURL url(embedded_test_server()->GetURL("/title1.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver deleted(rfh_a);
|
|
|
|
content::IdleManagerHelper::SetIdleTimeProviderForTest(
|
|
rfh_a, std::make_unique<FakeIdleTimeProvider>());
|
|
|
|
EXPECT_TRUE(ExecJs(rfh_a, R"(
|
|
new Promise(async resolve => {
|
|
let idleDetector = new IdleDetector();
|
|
idleDetector.start();
|
|
resolve();
|
|
});
|
|
)"));
|
|
|
|
// 2) Navigate away.
|
|
shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// The page uses IdleManager so it should be deleted.
|
|
deleted.WaitUntilDeleted();
|
|
|
|
// 3) Go back and make sure the IdleManager page wasn't in the cache.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kBlocklistedFeatures},
|
|
{blink::scheduler::WebSchedulerTrackedFeature::kIdleManager}, {}, {},
|
|
FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, DoesNotCacheSMSService) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
// 1) Navigate to a page and start using the SMSService.
|
|
GURL url(embedded_test_server()->GetURL("/title1.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver rfh_a_deleted(rfh_a);
|
|
|
|
EXPECT_TRUE(ExecJs(rfh_a, R"(
|
|
navigator.credentials.get({otp: {transport: ["sms"]}});
|
|
)",
|
|
EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
|
|
|
|
// 2) Navigate away.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
|
|
|
|
// The page uses SMSService so it should be deleted.
|
|
rfh_a_deleted.WaitUntilDeleted();
|
|
|
|
// 3) Go back and make sure the SMSService page wasn't in the cache.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
// Note that on certain linux tests, there is occasionally a not restored
|
|
// reason of kDisableForRenderFrameHostCalled. This is due to the javascript
|
|
// navigator.credentials.get, which will call on authentication code for linux
|
|
// but not other operating systems. The authenticator code explicitly invokes
|
|
// kDisableForRenderFrameHostCalled. This causes flakiness if we check against
|
|
// all not restored reasons. As a result, we only check for the blocklist
|
|
// reason.
|
|
ExpectBlocklistedFeature(
|
|
blink::scheduler::WebSchedulerTrackedFeature::kWebOTPService, FROM_HERE);
|
|
}
|
|
|
|
// crbug.com/1090223
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
DISABLED_DoesNotCachePaymentManager) {
|
|
ASSERT_TRUE(CreateHttpsServer()->Start());
|
|
|
|
// 1) Navigate to a page which includes PaymentManager functionality. Note
|
|
// that service workers are used, and therefore we use https server instead of
|
|
// embedded_server()
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(), https_server()->GetURL(
|
|
"a.com", "/payments/payment_app_invocation.html")));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver rfh_a_deleted(rfh_a);
|
|
|
|
// Execute functionality that calls PaymentManager.
|
|
EXPECT_TRUE(ExecJs(rfh_a, R"(
|
|
new Promise(async resolve => {
|
|
registerPaymentApp();
|
|
resolve();
|
|
});
|
|
)"));
|
|
|
|
// 2) Navigate away.
|
|
EXPECT_TRUE(
|
|
NavigateToURL(shell(), https_server()->GetURL("b.com", "/title1.html")));
|
|
|
|
// The page uses PaymentManager so it should be deleted.
|
|
rfh_a_deleted.WaitUntilDeleted();
|
|
|
|
// 3) Go back.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kBlocklistedFeatures},
|
|
{blink::scheduler::WebSchedulerTrackedFeature::kPaymentManager}, {}, {},
|
|
FROM_HERE);
|
|
|
|
// Note that on Mac10.10, there is occasionally blocklisting for network
|
|
// requests (kOutstandingNetworkRequestOthers). This causes flakiness if we
|
|
// check against all blocklisted features. As a result, we only check for the
|
|
// blocklist we care about.
|
|
base::HistogramBase::Sample sample = base::HistogramBase::Sample(
|
|
blink::scheduler::WebSchedulerTrackedFeature::kPaymentManager);
|
|
std::vector<base::Bucket> blocklist_values = histogram_tester_.GetAllSamples(
|
|
"BackForwardCache.HistoryNavigationOutcome."
|
|
"BlocklistedFeature");
|
|
auto it = std::find_if(
|
|
blocklist_values.begin(), blocklist_values.end(),
|
|
[sample](const base::Bucket& bucket) { return bucket.min == sample; });
|
|
EXPECT_TRUE(it != blocklist_values.end());
|
|
|
|
std::vector<base::Bucket> all_sites_blocklist_values =
|
|
histogram_tester_.GetAllSamples(
|
|
"BackForwardCache.AllSites.HistoryNavigationOutcome."
|
|
"BlocklistedFeature");
|
|
|
|
auto all_sites_it = std::find_if(
|
|
all_sites_blocklist_values.begin(), all_sites_blocklist_values.end(),
|
|
[sample](const base::Bucket& bucket) { return bucket.min == sample; });
|
|
EXPECT_TRUE(all_sites_it != all_sites_blocklist_values.end());
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
DoesNotCacheOnKeyboardLock) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
// 1) Navigate to a page and start using the IdleManager class.
|
|
GURL url(embedded_test_server()->GetURL("/title1.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver rfh_a_deleted(rfh_a);
|
|
|
|
EXPECT_TRUE(ExecJs(rfh_a, R"(
|
|
new Promise(resolve => {
|
|
navigator.keyboard.lock();
|
|
resolve();
|
|
});
|
|
)"));
|
|
|
|
// 2) Navigate away.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
|
|
|
|
// The page uses IdleManager so it should be deleted.
|
|
rfh_a_deleted.WaitUntilDeleted();
|
|
|
|
// 3) Go back and make sure the IdleManager page wasn't in the cache.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kBlocklistedFeatures},
|
|
{blink::scheduler::WebSchedulerTrackedFeature::kKeyboardLock}, {}, {},
|
|
FROM_HERE);
|
|
}
|
|
|
|
// Tests which blocklisted features are tracked in the metrics when we used
|
|
// blocklisted features (sticky and non-sticky) and do a browser-initiated
|
|
// cross-site navigation.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
BlocklistedFeaturesTracking_CrossSite_BrowserInitiated) {
|
|
ASSERT_TRUE(CreateHttpsServer()->Start());
|
|
GURL url_a(https_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_b(https_server()->GetURL("b.com", "/title2.html"));
|
|
// 1) Navigate to a page.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
scoped_refptr<SiteInstanceImpl> site_instance_a =
|
|
static_cast<SiteInstanceImpl*>(rfh_a->GetSiteInstance());
|
|
RenderFrameDeletedObserver rfh_a_deleted(rfh_a);
|
|
|
|
// 2) Use BroadcastChannel (non-sticky) and KeyboardLock (sticky) blocklisted
|
|
// features.
|
|
EXPECT_TRUE(ExecJs(rfh_a, "window.foo = new BroadcastChannel('foo');"));
|
|
EXPECT_TRUE(ExecJs(rfh_a, R"(
|
|
new Promise(resolve => {
|
|
navigator.keyboard.lock();
|
|
resolve();
|
|
});
|
|
)"));
|
|
|
|
// 3) Navigate cross-site, browser-initiated.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
// The previous page won't get into the back-forward cache because of the
|
|
// blocklisted features. Because we used sticky blocklisted features, we will
|
|
// not do a proactive BrowsingInstance swap, however the RFH will still change
|
|
// and get deleted.
|
|
rfh_a_deleted.WaitUntilDeleted();
|
|
EXPECT_FALSE(site_instance_a->IsRelatedSiteInstance(
|
|
web_contents()->GetMainFrame()->GetSiteInstance()));
|
|
|
|
// 4) Go back.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
// All features (sticky and non-sticky) will be tracked, because they're
|
|
// tracked in RenderFrameHostManager::UnloadOldFrame.
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kBlocklistedFeatures},
|
|
{blink::scheduler::WebSchedulerTrackedFeature::kBroadcastChannel,
|
|
blink::scheduler::WebSchedulerTrackedFeature::kKeyboardLock},
|
|
{}, {}, FROM_HERE);
|
|
}
|
|
|
|
// Tests which blocklisted features are tracked in the metrics when we used
|
|
// blocklisted features (sticky and non-sticky) and do a renderer-initiated
|
|
// cross-site navigation.
|
|
IN_PROC_BROWSER_TEST_F(
|
|
BackForwardCacheBrowserTest,
|
|
BlocklistedFeaturesTracking_CrossSite_RendererInitiated) {
|
|
ASSERT_TRUE(CreateHttpsServer()->Start());
|
|
GURL url_a(https_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_b(https_server()->GetURL("b.com", "/title2.html"));
|
|
|
|
// 1) Navigate to a page.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
scoped_refptr<SiteInstanceImpl> site_instance_a =
|
|
static_cast<SiteInstanceImpl*>(rfh_a->GetSiteInstance());
|
|
|
|
// 2) Use BroadcastChannel (non-sticky) and KeyboardLock (sticky) blocklisted
|
|
// features.
|
|
EXPECT_TRUE(ExecJs(rfh_a, "window.foo = new BroadcastChannel('foo');"));
|
|
EXPECT_TRUE(ExecJs(rfh_a, R"(
|
|
new Promise(resolve => {
|
|
navigator.keyboard.lock();
|
|
resolve();
|
|
});
|
|
)"));
|
|
|
|
// 3) Navigate cross-site, renderer-inititated.
|
|
EXPECT_TRUE(ExecJs(shell(), JsReplace("location = $1;", url_b.spec())));
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
// The previous page won't get into the back-forward cache because of the
|
|
// blocklisted features. Because we used sticky blocklisted features, we will
|
|
// not do a proactive BrowsingInstance swap.
|
|
EXPECT_TRUE(site_instance_a->IsRelatedSiteInstance(
|
|
web_contents()->GetMainFrame()->GetSiteInstance()));
|
|
|
|
// 4) Go back.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
if (AreStrictSiteInstancesEnabled()) {
|
|
// All features (sticky and non-sticky) will be tracked, because they're
|
|
// tracked in RenderFrameHostManager::UnloadOldFrame.
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::
|
|
kRelatedActiveContentsExist,
|
|
BackForwardCacheMetrics::NotRestoredReason::kBlocklistedFeatures,
|
|
BackForwardCacheMetrics::NotRestoredReason::
|
|
kBrowsingInstanceNotSwapped},
|
|
{blink::scheduler::WebSchedulerTrackedFeature::kBroadcastChannel,
|
|
blink::scheduler::WebSchedulerTrackedFeature::kKeyboardLock},
|
|
{ShouldSwapBrowsingInstance::kNo_NotNeededForBackForwardCache}, {},
|
|
FROM_HERE);
|
|
|
|
web_contents()->GetController().GoForward();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
ExpectBrowsingInstanceNotSwappedReason(
|
|
ShouldSwapBrowsingInstance::kNo_AlreadyHasMatchingBrowsingInstance,
|
|
FROM_HERE);
|
|
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
ExpectBrowsingInstanceNotSwappedReason(
|
|
ShouldSwapBrowsingInstance::kNo_AlreadyHasMatchingBrowsingInstance,
|
|
FROM_HERE);
|
|
} else {
|
|
// Non-sticky reasons are not recorded here.
|
|
ExpectNotRestored(
|
|
{
|
|
BackForwardCacheMetrics::NotRestoredReason::kBlocklistedFeatures,
|
|
BackForwardCacheMetrics::NotRestoredReason::
|
|
kBrowsingInstanceNotSwapped,
|
|
},
|
|
{blink::scheduler::WebSchedulerTrackedFeature::kKeyboardLock},
|
|
{ShouldSwapBrowsingInstance::kNo_NotNeededForBackForwardCache}, {},
|
|
FROM_HERE);
|
|
}
|
|
}
|
|
|
|
// Tests which blocklisted features are tracked in the metrics when we used
|
|
// blocklisted features (sticky and non-sticky) and do a same-site navigation.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
BlocklistedFeaturesTracking_SameSite) {
|
|
ASSERT_TRUE(CreateHttpsServer()->Start());
|
|
|
|
ASSERT_TRUE(CreateHttpsServer()->Start());
|
|
GURL url_1(https_server()->GetURL("/title1.html"));
|
|
GURL url_2(https_server()->GetURL("/title2.html"));
|
|
|
|
// 1) Navigate to a page.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_1));
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
RenderFrameHostImpl* rfh_1 = current_frame_host();
|
|
scoped_refptr<SiteInstanceImpl> site_instance_1 =
|
|
static_cast<SiteInstanceImpl*>(rfh_1->GetSiteInstance());
|
|
|
|
// 2) Use BroadcastChannel (non-sticky) and KeyboardLock (sticky) blocklisted
|
|
// features.
|
|
EXPECT_TRUE(ExecJs(rfh_1, "window.foo = new BroadcastChannel('foo');"));
|
|
EXPECT_TRUE(ExecJs(rfh_1, R"(
|
|
new Promise(resolve => {
|
|
navigator.keyboard.lock();
|
|
resolve();
|
|
});
|
|
)"));
|
|
|
|
// 3) Navigate same-site.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_2));
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
// Because we used sticky blocklisted features, we will not do a proactive
|
|
// BrowsingInstance swap.
|
|
EXPECT_TRUE(site_instance_1->IsRelatedSiteInstance(
|
|
web_contents()->GetMainFrame()->GetSiteInstance()));
|
|
|
|
// 4) Go back.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
// Non-sticky reasons are not recorded here.
|
|
ExpectNotRestored(
|
|
{
|
|
BackForwardCacheMetrics::NotRestoredReason::kBlocklistedFeatures,
|
|
BackForwardCacheMetrics::NotRestoredReason::
|
|
kBrowsingInstanceNotSwapped,
|
|
},
|
|
{blink::scheduler::WebSchedulerTrackedFeature::kKeyboardLock},
|
|
{ShouldSwapBrowsingInstance::kNo_NotNeededForBackForwardCache}, {},
|
|
FROM_HERE);
|
|
}
|
|
|
|
// Tests which blocklisted features are tracked in the metrics when we used a
|
|
// non-sticky blocklisted feature and do a browser-initiated cross-site
|
|
// navigation.
|
|
IN_PROC_BROWSER_TEST_F(
|
|
BackForwardCacheBrowserTest,
|
|
BlocklistedFeaturesTracking_CrossSite_BrowserInitiated_NonSticky) {
|
|
ASSERT_TRUE(CreateHttpsServer()->Start());
|
|
|
|
// 1) Navigate to an empty page.
|
|
GURL url_a(https_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_b(https_server()->GetURL("b.com", "/title2.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
// 2) Use BroadcastChannel (a non-sticky blocklisted feature).
|
|
EXPECT_TRUE(ExecJs(rfh_a, "window.foo = new BroadcastChannel('foo');"));
|
|
scoped_refptr<SiteInstanceImpl> site_instance_a =
|
|
static_cast<SiteInstanceImpl*>(
|
|
web_contents()->GetMainFrame()->GetSiteInstance());
|
|
|
|
// 3) Navigate cross-site, browser-initiated.
|
|
// The previous page won't get into the back-forward cache because of the
|
|
// blocklisted feature.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
// Because we only used non-sticky blocklisted features, we will still do a
|
|
// proactive BrowsingInstance swap.
|
|
EXPECT_FALSE(site_instance_a->IsRelatedSiteInstance(
|
|
web_contents()->GetMainFrame()->GetSiteInstance()));
|
|
|
|
// 4) Go back.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
// Because the RenderFrameHostManager changed, the blocklisted features will
|
|
// be tracked in RenderFrameHostManager::UnloadOldFrame.
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kBlocklistedFeatures},
|
|
{blink::scheduler::WebSchedulerTrackedFeature::kBroadcastChannel}, {}, {},
|
|
FROM_HERE);
|
|
}
|
|
|
|
// Tests which blocklisted features are tracked in the metrics when we used a
|
|
// non-sticky blocklisted feature and do a renderer-initiated cross-site
|
|
// navigation.
|
|
IN_PROC_BROWSER_TEST_F(
|
|
BackForwardCacheBrowserTest,
|
|
BlocklistedFeaturesTracking_CrossSite_RendererInitiated_NonSticky) {
|
|
ASSERT_TRUE(CreateHttpsServer()->Start());
|
|
|
|
// 1) Navigate to an empty page.
|
|
GURL url_a(https_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_b(https_server()->GetURL("b.com", "/title1.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
// 2) Use BroadcastChannel (a non-sticky blocklisted feature).
|
|
EXPECT_TRUE(ExecJs(rfh_a, "window.foo = new BroadcastChannel('foo');"));
|
|
scoped_refptr<SiteInstanceImpl> site_instance_a =
|
|
static_cast<SiteInstanceImpl*>(
|
|
web_contents()->GetMainFrame()->GetSiteInstance());
|
|
|
|
// 3) Navigate cross-site, renderer-inititated.
|
|
// The previous page won't get into the back-forward cache because of the
|
|
// blocklisted feature.
|
|
EXPECT_TRUE(ExecJs(shell(), JsReplace("location = $1;", url_b.spec())));
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
// Because we only used non-sticky blocklisted features, we will still do a
|
|
// proactive BrowsingInstance swap.
|
|
EXPECT_FALSE(site_instance_a->IsRelatedSiteInstance(
|
|
web_contents()->GetMainFrame()->GetSiteInstance()));
|
|
|
|
// 4) Go back.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
// Because the RenderFrameHostManager changed, the blocklisted features will
|
|
// be tracked in RenderFrameHostManager::UnloadOldFrame.
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kBlocklistedFeatures},
|
|
{blink::scheduler::WebSchedulerTrackedFeature::kBroadcastChannel}, {}, {},
|
|
FROM_HERE);
|
|
}
|
|
|
|
// Tests which blocklisted features are tracked in the metrics when we used a
|
|
// non-sticky blocklisted feature and do a same-site navigation.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
BlocklistedFeaturesTracking_SameSite_NonSticky) {
|
|
ASSERT_TRUE(CreateHttpsServer()->Start());
|
|
|
|
// 1) Navigate to an empty page.
|
|
GURL url_1(https_server()->GetURL("/title1.html"));
|
|
GURL url_2(https_server()->GetURL("/title2.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_1));
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
RenderFrameHostImpl* rfh_1 = current_frame_host();
|
|
// 2) Use BroadcastChannel (a non-sticky blocklisted feature).
|
|
EXPECT_TRUE(ExecJs(rfh_1, "window.foo = new BroadcastChannel('foo');"));
|
|
scoped_refptr<SiteInstanceImpl> site_instance_1 =
|
|
static_cast<SiteInstanceImpl*>(
|
|
web_contents()->GetMainFrame()->GetSiteInstance());
|
|
|
|
// 3) Navigate same-site.
|
|
// The previous page won't get into the back-forward cache because of the
|
|
// blocklisted feature.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_2));
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
// Because we only used non-sticky blocklisted features, we will still do a
|
|
// proactive BrowsingInstance swap.
|
|
EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(
|
|
web_contents()->GetMainFrame()->GetSiteInstance()));
|
|
|
|
// 4) Go back.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
// Because the RenderFrameHostManager changed, the blocklisted features will
|
|
// be tracked in RenderFrameHostManager::UnloadOldFrame.
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kBlocklistedFeatures},
|
|
{blink::scheduler::WebSchedulerTrackedFeature::kBroadcastChannel}, {}, {},
|
|
FROM_HERE);
|
|
}
|
|
|
|
// Flaky on Android, see crbug.com/1135601 and on other platforms, see
|
|
// crbug.com/1128772.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
DISABLED_LogIpcPostedToCachedFrame) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
// 1) Navigate to a page.
|
|
GURL url(embedded_test_server()->GetURL("/title1.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
|
|
// 2) Navigate away. The first page should be in the cache.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
|
|
|
|
// 3) Post IPC tasks to the page, testing both mojo remote and associated
|
|
// remote objects.
|
|
|
|
// Send a message via an associated interface - which will post a task with an
|
|
// IPC hash and will be routed to the per-thread task queue.
|
|
base::RunLoop run_loop;
|
|
rfh_a->RequestTextSurroundingSelection(
|
|
base::BindOnce(
|
|
[](base::RepeatingClosure quit_closure, const std::u16string& str,
|
|
uint32_t num, uint32_t num2) { quit_closure.Run(); },
|
|
run_loop.QuitClosure()),
|
|
1);
|
|
run_loop.Run();
|
|
|
|
// Post a non-associated interface. Will be routed to a frame-specific task
|
|
// queue with IPC set in SimpleWatcher.
|
|
base::RunLoop run_loop2;
|
|
rfh_a->GetHighPriorityLocalFrame()->DispatchBeforeUnload(
|
|
false,
|
|
base::BindOnce([](base::RepeatingClosure quit_closure, bool proceed,
|
|
base::TimeTicks start_time,
|
|
base::TimeTicks end_time) { quit_closure.Run(); },
|
|
run_loop2.QuitClosure()));
|
|
run_loop2.Run();
|
|
|
|
// 4) Check the histogram.
|
|
std::vector<base::HistogramBase::Sample> samples = {
|
|
base::HistogramBase::Sample(
|
|
base::TaskAnnotator::ScopedSetIpcHash::MD5HashMetricName(
|
|
"blink.mojom.HighPriorityLocalFrame")),
|
|
base::HistogramBase::Sample(
|
|
base::TaskAnnotator::ScopedSetIpcHash::MD5HashMetricName(
|
|
"blink.mojom.LocalFrame"))};
|
|
|
|
for (base::HistogramBase::Sample sample : samples) {
|
|
FetchHistogramsFromChildProcesses();
|
|
EXPECT_TRUE(HistogramContainsIntValue(
|
|
sample, histogram_tester_.GetAllSamples(
|
|
"BackForwardCache.Experimental."
|
|
"UnexpectedIPCMessagePostedToCachedFrame.MethodHash")));
|
|
}
|
|
}
|
|
|
|
class MockAppBannerService : public blink::mojom::AppBannerService {
|
|
public:
|
|
MockAppBannerService() = default;
|
|
~MockAppBannerService() override = default;
|
|
|
|
void Bind(mojo::ScopedMessagePipeHandle handle) {
|
|
receiver_.Bind(mojo::PendingReceiver<blink::mojom::AppBannerService>(
|
|
std::move(handle)));
|
|
}
|
|
|
|
mojo::Remote<blink::mojom::AppBannerController>& controller() {
|
|
return controller_;
|
|
}
|
|
|
|
void OnBannerPromptRequested(bool) {}
|
|
|
|
void SendBannerPromptRequest() {
|
|
blink::mojom::AppBannerController* controller_ptr = controller_.get();
|
|
base::OnceCallback<void(bool)> callback = base::BindOnce(
|
|
&MockAppBannerService::OnBannerPromptRequested, base::Unretained(this));
|
|
controller_ptr->BannerPromptRequest(
|
|
receiver_.BindNewPipeAndPassRemote(),
|
|
event_.BindNewPipeAndPassReceiver(), {"web"},
|
|
base::BindOnce(&MockAppBannerService::OnBannerPromptReply,
|
|
base::Unretained(this), std::move(callback)));
|
|
}
|
|
|
|
void OnBannerPromptReply(base::OnceCallback<void(bool)> callback,
|
|
blink::mojom::AppBannerPromptReply reply) {
|
|
std::move(callback).Run(reply ==
|
|
blink::mojom::AppBannerPromptReply::CANCEL);
|
|
}
|
|
|
|
// blink::mojom::AppBannerService:
|
|
void DisplayAppBanner() override {}
|
|
|
|
private:
|
|
mojo::Receiver<blink::mojom::AppBannerService> receiver_{this};
|
|
mojo::Remote<blink::mojom::AppBannerEvent> event_;
|
|
mojo::Remote<blink::mojom::AppBannerController> controller_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(MockAppBannerService);
|
|
};
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, DoesNotCacheIfAppBanner) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
// 1) Navigate to A and request a PWA app banner.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("a.com", "/title1.html")));
|
|
|
|
// Connect the MockAppBannerService mojom to the renderer's frame.
|
|
MockAppBannerService mock_app_banner_service;
|
|
web_contents()->GetMainFrame()->GetRemoteInterfaces()->GetInterface(
|
|
mock_app_banner_service.controller().BindNewPipeAndPassReceiver());
|
|
// Send the request to the renderer's frame.
|
|
mock_app_banner_service.SendBannerPromptRequest();
|
|
|
|
RenderFrameDeletedObserver delete_observer_rfh(current_frame_host());
|
|
|
|
// 2) Navigate away. Page A requested a PWA app banner, and thus not cached.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
|
|
delete_observer_rfh.WaitUntilDeleted();
|
|
|
|
// 3) Go back to A.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kBlocklistedFeatures},
|
|
{blink::scheduler::WebSchedulerTrackedFeature::kAppBanner}, {}, {},
|
|
FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, DoesNotCacheIfWebDatabase) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
// 1) Navigate to a page with WebDatabase usage.
|
|
GURL url(embedded_test_server()->GetURL("/simple_database.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver deleted(rfh_a);
|
|
|
|
// 2) Navigate away.
|
|
shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
// The page uses WebDatabase so it should be deleted.
|
|
deleted.WaitUntilDeleted();
|
|
|
|
// 3) Go back to the page with WebDatabase.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kBlocklistedFeatures},
|
|
{blink::scheduler::WebSchedulerTrackedFeature::kWebDatabase}, {}, {},
|
|
FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
DoesNotCacheIfPageUnreachable) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
GURL error_url(embedded_test_server()->GetURL("a.com", "/empty.html"));
|
|
GURL url(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
std::unique_ptr<URLLoaderInterceptor> url_interceptor =
|
|
URLLoaderInterceptor::SetupRequestFailForURL(error_url,
|
|
net::ERR_DNS_TIMED_OUT);
|
|
|
|
// Start with a successful navigation to a document.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
EXPECT_EQ(net::HTTP_OK, current_frame_host()->last_http_status_code());
|
|
|
|
// Navigate to an error page.
|
|
NavigationHandleObserver observer(shell()->web_contents(), error_url);
|
|
EXPECT_FALSE(NavigateToURL(shell(), error_url));
|
|
EXPECT_TRUE(observer.is_error());
|
|
EXPECT_EQ(net::ERR_DNS_TIMED_OUT, observer.net_error_code());
|
|
EXPECT_EQ(
|
|
GURL(kUnreachableWebDataURL),
|
|
shell()->web_contents()->GetMainFrame()->GetSiteInstance()->GetSiteURL());
|
|
EXPECT_EQ(net::OK, current_frame_host()->last_http_status_code());
|
|
|
|
RenderFrameDeletedObserver delete_rfh_a(current_frame_host());
|
|
|
|
// Navigate away.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
|
|
// The page had a networking error, so it shouldn't have been cached.
|
|
delete_rfh_a.WaitUntilDeleted();
|
|
|
|
// Go back.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_FALSE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kHTTPStatusNotOK}, {}, {},
|
|
{}, FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
DisableBackforwardCacheForTesting) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
// Disable the BackForwardCache.
|
|
web_contents()->GetController().GetBackForwardCache().DisableForTesting(
|
|
BackForwardCacheImpl::TEST_ASSUMES_NO_CACHING);
|
|
|
|
// Navigate to a page that would normally be cacheable.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("a.com", "/title1.html")));
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(current_frame_host());
|
|
|
|
// Navigate away.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
|
|
|
|
// The page should be deleted (not cached).
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
}
|
|
|
|
// Navigate from A to B, then cause JavaScript execution on A, then go back.
|
|
// Test the RenderFrameHost in the cache is evicted by JavaScript.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
EvictionOnJavaScriptExecution) {
|
|
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));
|
|
RenderFrameHostImpl* rfh_b = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b);
|
|
|
|
EXPECT_FALSE(delete_observer_rfh_a.deleted());
|
|
EXPECT_FALSE(delete_observer_rfh_b.deleted());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
EXPECT_FALSE(rfh_b->IsInBackForwardCache());
|
|
|
|
// 3) Execute JavaScript on A.
|
|
EvictByJavaScript(rfh_a);
|
|
|
|
// RenderFrameHost A is evicted from the BackForwardCache:
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
|
|
// 4) Go back to A.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kJavaScriptExecution}, {},
|
|
{}, {}, FROM_HERE);
|
|
}
|
|
|
|
// Similar to BackForwardCacheBrowserTest.EvictionOnJavaScriptExecution.
|
|
// Test case: A(B) -> C -> JS on B -> A(B)
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
EvictionOnJavaScriptExecutionIframe) {
|
|
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(B).
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b);
|
|
|
|
// 2) Navigate to C.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_c));
|
|
RenderFrameHostImpl* rfh_c = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_c(rfh_c);
|
|
|
|
EXPECT_FALSE(delete_observer_rfh_a.deleted());
|
|
EXPECT_FALSE(delete_observer_rfh_b.deleted());
|
|
EXPECT_FALSE(delete_observer_rfh_c.deleted());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
EXPECT_TRUE(rfh_b->IsInBackForwardCache());
|
|
EXPECT_FALSE(rfh_c->IsInBackForwardCache());
|
|
|
|
// 3) Execute JavaScript on B.
|
|
//
|
|
EvictByJavaScript(rfh_b);
|
|
|
|
// The A(B) page is evicted. So A and B are removed:
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
delete_observer_rfh_b.WaitUntilDeleted();
|
|
|
|
// 4) Go back to A(B).
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kJavaScriptExecution}, {},
|
|
{}, {}, FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
EvictionOnJavaScriptExecutionInAnotherWorld) {
|
|
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) Execute JavaScript on A in a new world. This ensures a new world.
|
|
const int32_t kNewWorldId = content::ISOLATED_WORLD_ID_CONTENT_END + 1;
|
|
EXPECT_TRUE(ExecJs(rfh_a, "console.log('hi');",
|
|
EXECUTE_SCRIPT_DEFAULT_OPTIONS, kNewWorldId));
|
|
|
|
// 3) Navigate to 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_FALSE(delete_observer_rfh_b.deleted());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
EXPECT_FALSE(rfh_b->IsInBackForwardCache());
|
|
|
|
// 4) Execute JavaScript on A in the new world.
|
|
EXPECT_FALSE(ExecJs(rfh_a, "console.log('hi');",
|
|
EXECUTE_SCRIPT_DEFAULT_OPTIONS, kNewWorldId));
|
|
|
|
// RenderFrameHost A is evicted from the BackForwardCache:
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
|
|
// 5) Go back to A.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kJavaScriptExecution}, {},
|
|
{}, {}, FROM_HERE);
|
|
}
|
|
|
|
// Tests the events are fired when going back from the cache.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, Events) {
|
|
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);
|
|
StartRecordingEvents(rfh_a);
|
|
|
|
// 2) Navigate to 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_FALSE(delete_observer_rfh_b.deleted());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
EXPECT_FALSE(rfh_b->IsInBackForwardCache());
|
|
// TODO(yuzus): Post message to the frozen page, and make sure that the
|
|
// messages arrive after the page visibility events, not before them.
|
|
|
|
// 3) Go back to A. Confirm that expected events are fired.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_FALSE(delete_observer_rfh_a.deleted());
|
|
EXPECT_FALSE(delete_observer_rfh_b.deleted());
|
|
EXPECT_EQ(rfh_a, current_frame_host());
|
|
// visibilitychange events are added twice per each because it is fired for
|
|
// both window and document.
|
|
MatchEventList(
|
|
rfh_a,
|
|
ListValueOf("window.pagehide.persisted", "document.visibilitychange",
|
|
"window.visibilitychange", "document.freeze",
|
|
"document.resume", "document.visibilitychange",
|
|
"window.visibilitychange", "window.pageshow.persisted"));
|
|
}
|
|
|
|
// Tests the events are fired for subframes when going back from the cache.
|
|
// Test case: a(b) -> c -> a(b)
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, EventsForSubframes) {
|
|
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(B).
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b);
|
|
StartRecordingEvents(rfh_a);
|
|
StartRecordingEvents(rfh_b);
|
|
|
|
// 2) Navigate to C.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_c));
|
|
RenderFrameHostImpl* rfh_c = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_c(rfh_c);
|
|
EXPECT_FALSE(delete_observer_rfh_a.deleted());
|
|
EXPECT_FALSE(delete_observer_rfh_b.deleted());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
EXPECT_TRUE(rfh_b->IsInBackForwardCache());
|
|
EXPECT_FALSE(rfh_c->IsInBackForwardCache());
|
|
// TODO(yuzus): Post message to the frozen page, and make sure that the
|
|
// messages arrive after the page visibility events, not before them.
|
|
|
|
// 3) Go back to A(B). Confirm that expected events are fired on the subframe.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_FALSE(delete_observer_rfh_a.deleted());
|
|
EXPECT_FALSE(delete_observer_rfh_b.deleted());
|
|
EXPECT_FALSE(delete_observer_rfh_c.deleted());
|
|
EXPECT_EQ(rfh_a, current_frame_host());
|
|
EXPECT_FALSE(rfh_a->IsInBackForwardCache());
|
|
EXPECT_FALSE(rfh_b->IsInBackForwardCache());
|
|
EXPECT_TRUE(rfh_c->IsInBackForwardCache());
|
|
// visibilitychange events are added twice per each because it is fired for
|
|
// both window and document.
|
|
MatchEventList(
|
|
rfh_a,
|
|
ListValueOf("window.pagehide.persisted", "document.visibilitychange",
|
|
"window.visibilitychange", "document.freeze",
|
|
"document.resume", "document.visibilitychange",
|
|
"window.visibilitychange", "window.pageshow.persisted"));
|
|
MatchEventList(
|
|
rfh_b,
|
|
ListValueOf("window.pagehide.persisted", "document.visibilitychange",
|
|
"window.visibilitychange", "document.freeze",
|
|
"document.resume", "document.visibilitychange",
|
|
"window.visibilitychange", "window.pageshow.persisted"));
|
|
}
|
|
|
|
// Tests the events are fired when going back from the cache.
|
|
// Same as: BackForwardCacheBrowserTest.Events, but with a document-initiated
|
|
// navigation. This is a regression test for https://crbug.com/1000324
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
EventsAfterDocumentInitiatedNavigation) {
|
|
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);
|
|
StartRecordingEvents(rfh_a);
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(ExecJs(shell(), JsReplace("location = $1;", url_b.spec())));
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
RenderFrameHostImpl* rfh_b = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b);
|
|
|
|
EXPECT_FALSE(delete_observer_rfh_a.deleted());
|
|
EXPECT_FALSE(delete_observer_rfh_b.deleted());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
EXPECT_FALSE(rfh_b->IsInBackForwardCache());
|
|
// TODO(yuzus): Post message to the frozen page, and make sure that the
|
|
// messages arrive after the page visibility events, not before them.
|
|
|
|
// 3) Go back to A. Confirm that expected events are fired.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_FALSE(delete_observer_rfh_a.deleted());
|
|
EXPECT_FALSE(delete_observer_rfh_b.deleted());
|
|
EXPECT_EQ(rfh_a, current_frame_host());
|
|
// visibilitychange events are added twice per each because it is fired for
|
|
// both window and document.
|
|
MatchEventList(
|
|
rfh_a,
|
|
ListValueOf("window.pagehide.persisted", "document.visibilitychange",
|
|
"window.visibilitychange", "document.freeze",
|
|
"document.resume", "document.visibilitychange",
|
|
"window.visibilitychange", "window.pageshow.persisted"));
|
|
}
|
|
|
|
class BackForwardCacheBrowserTestShouldConsiderPagehideForEligibility
|
|
: public BackForwardCacheBrowserTest {
|
|
public:
|
|
BackForwardCacheBrowserTestShouldConsiderPagehideForEligibility() = default;
|
|
~BackForwardCacheBrowserTestShouldConsiderPagehideForEligibility() override =
|
|
default;
|
|
|
|
protected:
|
|
void SetUpCommandLine(base::CommandLine* command_line) override {
|
|
check_eligibility_after_pagehide_ = true;
|
|
BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
|
|
}
|
|
};
|
|
|
|
IN_PROC_BROWSER_TEST_F(
|
|
BackForwardCacheBrowserTestShouldConsiderPagehideForEligibility,
|
|
DoesNotCacheIfBroadcastChannelStillOpen) {
|
|
ASSERT_TRUE(CreateHttpsServer()->Start());
|
|
|
|
// 1) Navigate to an empty page.
|
|
GURL url_a(https_server()->GetURL(
|
|
"a.com", "/back_forward_cache/page_with_broadcastchannel.html"));
|
|
GURL url_b(https_server()->GetURL("b.com", "/title1.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
// 2) Use BroadcastChannel (a non-sticky blocklisted feature).
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
EXPECT_TRUE(ExecJs(rfh_a, "acquireBroadcastChannel();"));
|
|
EXPECT_TRUE(ExecJs(rfh_a, "setShouldCloseChannelInPageHide(false);"));
|
|
|
|
// 3) Navigate cross-site, browser-initiated.
|
|
// The previous page won't get into the back-forward cache because of the
|
|
// blocklisted feature.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
// 4) Go back.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
// Because the RenderFrameHostManager changed, the blocklisted features will
|
|
// be tracked in RenderFrameHostManager::UnloadOldFrame.
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kBlocklistedFeatures},
|
|
{blink::scheduler::WebSchedulerTrackedFeature::kBroadcastChannel}, {}, {},
|
|
FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(
|
|
BackForwardCacheBrowserTestShouldConsiderPagehideForEligibility,
|
|
CacheIfBroadcastChannelIsClosedInPagehide) {
|
|
ASSERT_TRUE(CreateHttpsServer()->Start());
|
|
|
|
// 1) Navigate to an empty page.
|
|
GURL url_a(https_server()->GetURL(
|
|
"a.com", "/back_forward_cache/page_with_broadcastchannel.html"));
|
|
GURL url_b(https_server()->GetURL("b.com", "/title1.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
// 2) Use BroadcastChannel (a non-sticky blocklisted feature).
|
|
EXPECT_TRUE(ExecJs(rfh_a, "acquireBroadcastChannel();"));
|
|
EXPECT_TRUE(ExecJs(rfh_a, "setShouldCloseChannelInPageHide(true);"));
|
|
|
|
// 3) Navigate cross-site, browser-initiated.
|
|
// The previous page won't get into the back-forward cache because of the
|
|
// blocklisted feature.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
// 4) Go back.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectRestored(FROM_HERE);
|
|
}
|
|
|
|
// Navigates from page A -> page B -> page C -> page B -> page C. Page B becomes
|
|
// ineligible for bfcache in pagehide handler, so Page A stays in bfcache
|
|
// without being evicted even after the navigation to Page C.
|
|
IN_PROC_BROWSER_TEST_F(
|
|
BackForwardCacheBrowserTestShouldConsiderPagehideForEligibility,
|
|
PagehideMakesPageIneligibleForBackForwardCacheAndNotCountedInCacheSize) {
|
|
ASSERT_TRUE(CreateHttpsServer()->Start());
|
|
GURL url_a(https_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_b(https_server()->GetURL(
|
|
"b.com", "/back_forward_cache/page_with_broadcastchannel.html"));
|
|
GURL url_c(https_server()->GetURL("c.com", "/title1.html"));
|
|
|
|
// 1) Navigate to a.com.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
|
|
// 2) Navigate to b.com.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
RenderFrameHostImpl* rfh_b = current_frame_host();
|
|
RenderFrameDeletedObserver deleted_observer_rfh_b(rfh_b);
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
// Acquire broadcast in pagehide. Now b.com is not eligible for bfcache.
|
|
EXPECT_TRUE(
|
|
ExecJs(rfh_b, "setShouldAcquireBroadcastChannelInPageHide(true);"));
|
|
|
|
// 3) Navigate to c.com.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_c));
|
|
// RenderFrameHostImpl* rfh_c = current_frame_host();
|
|
// Since the b.com is not eligible for bfcache, |rfh_a| should stay in
|
|
// bfcache.
|
|
deleted_observer_rfh_b.WaitUntilDeleted();
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
|
|
// 4) Navigate back to b.com.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kBlocklistedFeatures},
|
|
{blink::scheduler::WebSchedulerTrackedFeature::kBroadcastChannel}, {}, {},
|
|
FROM_HERE);
|
|
RenderFrameHostImpl* rfh_b_2 = current_frame_host();
|
|
// Do not acquire broadcast channel. Now b.com is eligible for bfcache.
|
|
EXPECT_TRUE(
|
|
ExecJs(rfh_b_2, "setShouldAcquireBroadcastChannelInPageHide(false);"));
|
|
|
|
// 5) Navigate forward to c.com.
|
|
web_contents()->GetController().GoForward();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectRestored(FROM_HERE);
|
|
// b.com was eligible for bfcache and should stay in bfcache.
|
|
EXPECT_TRUE(rfh_b_2->IsInBackForwardCache());
|
|
}
|
|
|
|
// Track the events dispatched when a page is deemed ineligible for back-forward
|
|
// cache after we've dispatched the 'pagehide' event with persisted set to true.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
EventsForPageIneligibleAfterPagehidePersisted) {
|
|
ASSERT_TRUE(CreateHttpsServer()->Start());
|
|
GURL url_1(https_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_2(https_server()->GetURL("a.com", "/title2.html"));
|
|
|
|
// 1) Navigate to |url_1|.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_1));
|
|
RenderFrameHostImpl* rfh_1 = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_1(rfh_1);
|
|
// 2) Use BroadcastChannel (a non-sticky blocklisted feature), so that we
|
|
// would still do a RFH swap on same-site navigation and fire the 'pagehide'
|
|
// event during commit of the new page with 'persisted' set to true, but the
|
|
// page will not be eligible for back-forward cache after commit.
|
|
EXPECT_TRUE(ExecJs(rfh_1, "window.foo = new BroadcastChannel('foo');"));
|
|
|
|
EXPECT_TRUE(ExecJs(rfh_1, R"(
|
|
window.onpagehide = (e) => {
|
|
if (e.persisted) {
|
|
window.domAutomationController.send('pagehide.persisted');
|
|
}
|
|
}
|
|
document.onvisibilitychange = () => {
|
|
if (document.visibilityState == 'hidden') {
|
|
window.domAutomationController.send('visibilitychange.hidden');
|
|
}
|
|
}
|
|
window.onunload = () => {
|
|
window.domAutomationController.send('unload');
|
|
}
|
|
)"));
|
|
|
|
DOMMessageQueue dom_message_queue(shell()->web_contents());
|
|
// 3) Navigate to |url_2|.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_2));
|
|
// |rfh_1| will not get into the back-forward cache and eventually get deleted
|
|
// because it uses a blocklisted feature.
|
|
delete_observer_rfh_1.WaitUntilDeleted();
|
|
|
|
// Only the pagehide and visibilitychange events will be dispatched.
|
|
int num_messages_received = 0;
|
|
std::string expected_messages[] = {"\"pagehide.persisted\"",
|
|
"\"visibilitychange.hidden\""};
|
|
std::string message;
|
|
while (dom_message_queue.PopMessage(&message)) {
|
|
EXPECT_EQ(expected_messages[num_messages_received], message);
|
|
num_messages_received++;
|
|
}
|
|
EXPECT_EQ(num_messages_received, 2);
|
|
}
|
|
|
|
// Track the events dispatched when a page is deemed ineligible for back-forward
|
|
// cache before we've dispatched the pagehide event on it.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
EventsForPageIneligibleBeforePagehide) {
|
|
ASSERT_TRUE(CreateHttpsServer()->Start());
|
|
GURL url_1(https_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_2(https_server()->GetURL("b.com", "/title2.html"));
|
|
|
|
// 1) Navigate to |url_1|.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_1));
|
|
RenderFrameHostImpl* rfh_1 = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_1(rfh_1);
|
|
// 2) Use keyboard lock (a sticky blocklisted feature), so that the page is
|
|
// known to be ineligible for bfcache at commit time, before we dispatch the
|
|
// pagehide event.
|
|
EXPECT_TRUE(ExecJs(rfh_1, R"(
|
|
new Promise(resolve => {
|
|
navigator.keyboard.lock();
|
|
resolve();
|
|
});
|
|
)"));
|
|
|
|
EXPECT_TRUE(ExecJs(rfh_1, R"(
|
|
window.onpagehide = (e) => {
|
|
if (!e.persisted) {
|
|
window.domAutomationController.send('pagehide.not_persisted');
|
|
}
|
|
}
|
|
document.onvisibilitychange = () => {
|
|
if (document.visibilityState == 'hidden') {
|
|
window.domAutomationController.send('visibilitychange.hidden');
|
|
}
|
|
}
|
|
window.onunload = () => {
|
|
window.domAutomationController.send('unload');
|
|
}
|
|
)"));
|
|
|
|
DOMMessageQueue dom_message_queue(shell()->web_contents());
|
|
// 3) Navigate to |url_2|.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_2));
|
|
// |rfh_1| will not get into the back-forward cache and eventually get deleted
|
|
// because it uses a blocklisted feature.
|
|
delete_observer_rfh_1.WaitUntilDeleted();
|
|
|
|
// "pagehide", "visibilitychange", and "unload" events will be dispatched.
|
|
int num_messages_received = 0;
|
|
std::string expected_messages[] = {"\"pagehide.not_persisted\"",
|
|
"\"visibilitychange.hidden\"",
|
|
"\"unload\""};
|
|
std::string message;
|
|
while (dom_message_queue.PopMessage(&message)) {
|
|
EXPECT_EQ(expected_messages[num_messages_received], message);
|
|
num_messages_received++;
|
|
}
|
|
EXPECT_EQ(num_messages_received, 3);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, EvictPageWithInfiniteLoop) {
|
|
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();
|
|
|
|
ExecuteScriptAsync(rfh_a, R"(
|
|
let i = 0;
|
|
while (true) { i++; }
|
|
)");
|
|
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
RenderProcessHost* process = rfh_a->GetProcess();
|
|
RenderProcessHostWatcher destruction_observer(
|
|
process, RenderProcessHostWatcher::WATCH_FOR_HOST_DESTRUCTION);
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
RenderFrameHostImpl* rfh_b = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b);
|
|
|
|
// rfh_a should be destroyed (not kept in the cache).
|
|
destruction_observer.Wait();
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
|
|
// rfh_b should still be the current frame.
|
|
EXPECT_EQ(current_frame_host(), rfh_b);
|
|
EXPECT_FALSE(delete_observer_rfh_b.deleted());
|
|
|
|
// 3) Go back to A.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kTimeoutPuttingInCache}, {},
|
|
{}, {}, FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
NavigationCancelledAtWillStartRequest) {
|
|
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));
|
|
RenderFrameHostImpl* rfh_b = current_frame_host();
|
|
EXPECT_FALSE(delete_observer_rfh_a.deleted());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
|
|
// Cancel all navigation attempts.
|
|
content::TestNavigationThrottleInserter throttle_inserter(
|
|
shell()->web_contents(),
|
|
base::BindLambdaForTesting(
|
|
[&](NavigationHandle* handle) -> std::unique_ptr<NavigationThrottle> {
|
|
auto throttle = std::make_unique<TestNavigationThrottle>(handle);
|
|
throttle->SetResponse(TestNavigationThrottle::WILL_START_REQUEST,
|
|
TestNavigationThrottle::SYNCHRONOUS,
|
|
NavigationThrottle::CANCEL_AND_IGNORE);
|
|
return throttle;
|
|
}));
|
|
|
|
// 3) Go back to A, which will be cancelled by the NavigationThrottle.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
// We should still be showing page B.
|
|
EXPECT_EQ(rfh_b, current_frame_host());
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
NavigationCancelledAtWillProcessResponse) {
|
|
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));
|
|
RenderFrameHostImpl* rfh_b = current_frame_host();
|
|
EXPECT_FALSE(delete_observer_rfh_a.deleted());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
|
|
// Cancel all navigation attempts.
|
|
content::TestNavigationThrottleInserter throttle_inserter(
|
|
shell()->web_contents(),
|
|
base::BindLambdaForTesting(
|
|
[&](NavigationHandle* handle) -> std::unique_ptr<NavigationThrottle> {
|
|
auto throttle = std::make_unique<TestNavigationThrottle>(handle);
|
|
throttle->SetResponse(TestNavigationThrottle::WILL_PROCESS_RESPONSE,
|
|
TestNavigationThrottle::SYNCHRONOUS,
|
|
NavigationThrottle::CANCEL_AND_IGNORE);
|
|
return throttle;
|
|
}));
|
|
|
|
// 3) Go back to A, which will be cancelled by the NavigationThrottle.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
// We should still be showing page B.
|
|
EXPECT_EQ(rfh_b, current_frame_host());
|
|
}
|
|
|
|
// Test the race condition where a document is evicted from the BackForwardCache
|
|
// while it is in the middle of being restored and before URL loader starts a
|
|
// response.
|
|
//
|
|
// ┌───────┐ ┌────────┐
|
|
// │Browser│ │Renderer│
|
|
// └───┬───┘ └───┬────┘
|
|
// (Freeze & store the cache) │
|
|
// │────────────────────────>│
|
|
// │ │
|
|
// (Navigate to cached document) │
|
|
// │──┐ │
|
|
// │ │ │
|
|
// │EvictFromBackForwardCache│
|
|
// │<────────────────────────│
|
|
// │ │ │
|
|
// │ x Navigation cancelled │
|
|
// │ and reissued │
|
|
// ┌───┴───┐ ┌───┴────┐
|
|
// │Browser│ │Renderer│
|
|
// └───────┘ └────────┘
|
|
//
|
|
// When the eviction occurs, the in flight NavigationRequest to the cached
|
|
// document should be reissued (cancelled and replaced by a normal navigation).
|
|
//
|
|
// Flaky on most platforms (see crbug.com/1136683)
|
|
IN_PROC_BROWSER_TEST_F(
|
|
BackForwardCacheBrowserTest,
|
|
DISABLED_ReissuesNavigationIfEvictedDuringNavigation_BeforeResponse) {
|
|
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 page A.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
|
|
// 2) Navigate to page 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_NE(rfh_a, rfh_b);
|
|
|
|
// 3) Start navigation to page A, and cause the document to be evicted during
|
|
// the navigation immediately before navigation makes any meaningful progress.
|
|
web_contents()->GetController().GoBack();
|
|
EvictByJavaScript(rfh_a);
|
|
|
|
// rfh_a should have been deleted, and page A navigated to normally.
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
RenderFrameHostImpl* rfh_a2 = current_frame_host();
|
|
EXPECT_NE(rfh_a2, rfh_b);
|
|
EXPECT_EQ(rfh_a2->GetLastCommittedURL(), url_a);
|
|
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kJavaScriptExecution}, {},
|
|
{}, {}, FROM_HERE);
|
|
}
|
|
|
|
// Similar to ReissuesNavigationIfEvictedDuringNavigation, except that
|
|
// BackForwardCache::Flush is the source of the eviction.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
FlushCacheDuringNavigationToCachedPage) {
|
|
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 page A.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a1 = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a1(rfh_a1);
|
|
|
|
// 2) Navigate to page B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
RenderFrameHostImpl* rfh_b2 = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_b2(rfh_b2);
|
|
EXPECT_FALSE(delete_observer_rfh_a1.deleted());
|
|
EXPECT_TRUE(rfh_a1->IsInBackForwardCache());
|
|
EXPECT_NE(rfh_a1, rfh_b2);
|
|
|
|
// 3) Start navigation to page A, and flush the cache during the navigation.
|
|
TestNavigationManager navigation_manager(shell()->web_contents(), url_a);
|
|
web_contents()->GetController().GoBack();
|
|
|
|
EXPECT_TRUE(navigation_manager.WaitForRequestStart());
|
|
|
|
// Flush the cache, which contains the document being navigated to.
|
|
web_contents()->GetController().GetBackForwardCache().Flush();
|
|
|
|
// The navigation should get canceled, then reissued; ultimately resulting in
|
|
// a successful navigation using a new RenderFrameHost.
|
|
navigation_manager.WaitForNavigationFinished();
|
|
|
|
// rfh_a should have been deleted, and page A navigated to normally.
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
delete_observer_rfh_a1.WaitUntilDeleted();
|
|
EXPECT_TRUE(rfh_b2->IsInBackForwardCache());
|
|
RenderFrameHostImpl* rfh_a3 = current_frame_host();
|
|
EXPECT_EQ(rfh_a3->GetLastCommittedURL(), url_a);
|
|
}
|
|
|
|
// Test that if the renderer process crashes while a document is in the
|
|
// BackForwardCache, it gets evicted.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
EvictsFromCacheIfRendererProcessCrashes) {
|
|
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));
|
|
RenderFrameHostImpl* rfh_b = current_frame_host();
|
|
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
|
|
// 3) Crash A's renderer process while it is in the cache.
|
|
{
|
|
RenderProcessHost* process = rfh_a->GetProcess();
|
|
RenderProcessHostWatcher crash_observer(
|
|
process, RenderProcessHostWatcher::WATCH_FOR_HOST_DESTRUCTION);
|
|
EXPECT_TRUE(process->Shutdown(0));
|
|
crash_observer.Wait();
|
|
}
|
|
|
|
// rfh_b should still be the current frame.
|
|
EXPECT_EQ(current_frame_host(), rfh_b);
|
|
|
|
// 4) Go back to A.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kRendererProcessKilled}, {},
|
|
{}, {}, FROM_HERE);
|
|
}
|
|
|
|
// The test is simulating a race condition. The scheduler tracked features are
|
|
// updated during the "freeze" event in a way that would have prevented the
|
|
// document from entering the BackForwardCache in the first place.
|
|
//
|
|
// TODO(https://crbug.com/996267): The document should be evicted.
|
|
//
|
|
// ┌───────┐ ┌────────┐
|
|
// │browser│ │renderer│
|
|
// └───┬───┘ └────┬───┘
|
|
// (enter cache) │
|
|
// │ Freeze() │
|
|
// │─────────────────────────────>│
|
|
// │ (onfreeze)
|
|
// │OnSchedulerTrackedFeaturesUsed│
|
|
// │<─────────────────────────────│
|
|
// │ (frozen)
|
|
// │ │
|
|
// ┌───┴───┐ ┌────┴───┐
|
|
// │browser│ │renderer│
|
|
// └───────┘ └────────┘
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
SchedulerTrackedFeaturesUpdatedWhileStoring) {
|
|
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);
|
|
|
|
// When the page will enter the BackForwardCache, just before being frozen,
|
|
// use a feature that would have been prevented the document from being
|
|
// cached.
|
|
EXPECT_TRUE(ExecJs(rfh_a, R"(
|
|
document.addEventListener('freeze', event => {
|
|
window.foo = new BroadcastChannel('foo');
|
|
});
|
|
)"));
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
// rfh_a should be evicted from the cache and destroyed.
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
}
|
|
|
|
// A fetch request starts during the "freeze" event. The current behavior is to
|
|
// send the request anyway. However evicting the page from the BackForwardCache
|
|
// might be a better behavior.
|
|
//
|
|
// ┌───────┐┌────────┐ ┌───────────────┐
|
|
// │browser││renderer│ │network service│
|
|
// └───┬───┘└───┬────┘ └───────┬───────┘
|
|
// │Freeze()│ │
|
|
// │───────>│ │
|
|
// │ (onfreeze) │
|
|
// │ │CreateLoaderAndStart│
|
|
// │ │───────────────────>│
|
|
// │ (frozen) │
|
|
// ┌───┴───┐┌───┴────┐ ┌───────┴───────┐
|
|
// │browser││renderer│ │network service│
|
|
// └───────┘└────────┘ └───────────────┘
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, FetchWhileStoring) {
|
|
net::test_server::ControllableHttpResponse fetch_response(
|
|
embedded_test_server(), "/fetch");
|
|
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);
|
|
|
|
// Use "fetch" immediately before being frozen.
|
|
EXPECT_TRUE(ExecJs(rfh_a, R"(
|
|
document.addEventListener('freeze', event => {
|
|
my_fetch = fetch('/fetch', { keepalive: true});
|
|
});
|
|
)"));
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
fetch_response.WaitForRequest();
|
|
fetch_response.Send(net::HTTP_OK, "text/html", "TheResponse");
|
|
fetch_response.Done();
|
|
|
|
// 3) Go back to A.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
}
|
|
|
|
class BackForwardCacheBrowserTestWithUnfreezableLoading
|
|
: public BackForwardCacheBrowserTest {
|
|
public:
|
|
BackForwardCacheBrowserTestWithUnfreezableLoading() = default;
|
|
~BackForwardCacheBrowserTestWithUnfreezableLoading() override = default;
|
|
|
|
protected:
|
|
void SetUpCommandLine(base::CommandLine* command_line) override {
|
|
EnableFeatureAndSetParams(
|
|
blink::features::kLoadingTasksUnfreezable, "max_buffered_bytes",
|
|
base::NumberToString(kMaxBufferedBytesPerRequest));
|
|
EnableFeatureAndSetParams(
|
|
blink::features::kLoadingTasksUnfreezable,
|
|
"max_buffered_bytes_per_process",
|
|
base::NumberToString(kMaxBufferedBytesPerProcess));
|
|
EnableFeatureAndSetParams(
|
|
blink::features::kLoadingTasksUnfreezable,
|
|
"grace_period_to_finish_loading_in_seconds",
|
|
base::NumberToString(kGracePeriodToFinishLoading.InSeconds()));
|
|
BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
|
|
}
|
|
|
|
// Navigates to a page at |page_url| with an img element with src set to
|
|
// "image.png".
|
|
RenderFrameHostImpl* 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.
|
|
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;
|
|
}
|
|
|
|
const int kMaxBufferedBytesPerRequest = 7000;
|
|
const int kMaxBufferedBytesPerProcess = 10000;
|
|
const base::TimeDelta kGracePeriodToFinishLoading =
|
|
base::TimeDelta::FromSeconds(5);
|
|
};
|
|
|
|
// When loading task is unfreezable with the feature flag
|
|
// kLoadingTaskUnfreezable, a page will keep processing the in-flight network
|
|
// requests while the page is frozen in BackForwardCache.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithUnfreezableLoading,
|
|
FetchWhileStoring) {
|
|
net::test_server::ControllableHttpResponse fetch_response(
|
|
embedded_test_server(), "/fetch");
|
|
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);
|
|
|
|
// Use "fetch" immediately before being frozen.
|
|
EXPECT_TRUE(ExecJs(rfh_a, R"(
|
|
document.addEventListener('freeze', event => {
|
|
my_fetch = fetch('/fetch', { keepalive: true});
|
|
});
|
|
)"));
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
fetch_response.WaitForRequest();
|
|
fetch_response.Send(net::HTTP_OK, "text/html", "TheResponse");
|
|
fetch_response.Done();
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
EXPECT_FALSE(delete_observer_rfh_a.deleted());
|
|
|
|
// 3) Go back to A.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectRestored(FROM_HERE);
|
|
}
|
|
|
|
// Eviction is triggered when a normal fetch request gets redirected while the
|
|
// page is in back-forward cache.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithUnfreezableLoading,
|
|
FetchRedirectedWhileStoring) {
|
|
net::test_server::ControllableHttpResponse fetch_response(
|
|
embedded_test_server(), "/fetch");
|
|
net::test_server::ControllableHttpResponse fetch2_response(
|
|
embedded_test_server(), "/fetch2");
|
|
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);
|
|
|
|
// Trigger a fetch.
|
|
ExecuteScriptAsync(rfh_a, "my_fetch = fetch('/fetch');");
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
// Page A is initially stored in the back-forward cache.
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
|
|
// Respond the fetch with a redirect.
|
|
fetch_response.WaitForRequest();
|
|
fetch_response.Send(
|
|
"HTTP/1.1 302 Moved Temporarily\r\n"
|
|
"Location: /fetch2");
|
|
fetch_response.Done();
|
|
|
|
// Ensure that the request to /fetch2 was never sent (because the page is
|
|
// immediately evicted) by checking after 3 seconds.
|
|
base::RunLoop loop;
|
|
base::OneShotTimer timer;
|
|
timer.Start(FROM_HERE, base::TimeDelta::FromSeconds(3), loop.QuitClosure());
|
|
loop.Run();
|
|
EXPECT_EQ(nullptr, fetch2_response.http_request());
|
|
|
|
// Page A should be evicted from the back-forward cache.
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
|
|
// 3) Go back to A.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kNetworkRequestRedirected},
|
|
{}, {}, {}, FROM_HERE);
|
|
}
|
|
|
|
// Eviction is triggered when a keepalive fetch request gets redirected while
|
|
// the page is in back-forward cache.
|
|
// TODO(https://crbug.com/1137682): We should not trigger eviction on redirects
|
|
// of keepalive fetches.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithUnfreezableLoading,
|
|
KeepAliveFetchRedirectedWhileStoring) {
|
|
net::test_server::ControllableHttpResponse fetch_response(
|
|
embedded_test_server(), "/fetch");
|
|
net::test_server::ControllableHttpResponse fetch2_response(
|
|
embedded_test_server(), "/fetch2");
|
|
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);
|
|
|
|
// Trigger a keepalive fetch.
|
|
ExecuteScriptAsync(rfh_a, "my_fetch = fetch('/fetch', { keepalive: true });");
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
// Page A is initially stored in the back-forward cache.
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
|
|
// Respond the fetch with a redirect.
|
|
fetch_response.WaitForRequest();
|
|
fetch_response.Send(
|
|
"HTTP/1.1 302 Moved Temporarily\r\n"
|
|
"Location: /fetch2");
|
|
fetch_response.Done();
|
|
|
|
// Ensure that the request to /fetch2 was never sent (because the page is
|
|
// immediately evicted) by checking after 3 seconds.
|
|
// TODO(https://crbug.com/1137682): We should not trigger eviction on
|
|
// redirects of keepalive fetches and the redirect request should be sent.
|
|
base::RunLoop loop;
|
|
base::OneShotTimer timer;
|
|
timer.Start(FROM_HERE, base::TimeDelta::FromSeconds(3), loop.QuitClosure());
|
|
loop.Run();
|
|
EXPECT_EQ(nullptr, fetch2_response.http_request());
|
|
|
|
// Page A should be evicted from the back-forward cache.
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
|
|
// 3) Go back to A.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kNetworkRequestRedirected},
|
|
{}, {}, {}, FROM_HERE);
|
|
}
|
|
|
|
// Tests the case when the header was received before the page is frozen,
|
|
// but parts of the response body is received when the page is frozen.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithUnfreezableLoading,
|
|
PageWithDrainedDatapipeRequestsForFetchShouldBeEvicted) {
|
|
net::test_server::ControllableHttpResponse fetch_response(
|
|
embedded_test_server(), "/fetch");
|
|
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);
|
|
|
|
// Call fetch before navigating away.
|
|
EXPECT_TRUE(ExecJs(rfh_a, R"(
|
|
var fetch_response_promise = my_fetch = fetch('/fetch').then(response => {
|
|
return response.text();
|
|
});
|
|
)"));
|
|
// Send response header and a piece of the body before navigating away.
|
|
fetch_response.WaitForRequest();
|
|
fetch_response.Send(net::HTTP_OK, "text/plain");
|
|
fetch_response.Send("body");
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
|
|
// 3) Go back to A.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason::
|
|
kNetworkRequestDatapipeDrainedAsBytesConsumer},
|
|
{}, {}, {}, FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(
|
|
BackForwardCacheBrowserTestWithUnfreezableLoading,
|
|
PageWithDrainedDatapipeRequestsForScriptStreamerShouldNotBeEvicted) {
|
|
net::test_server::ControllableHttpResponse response(embedded_test_server(),
|
|
"/small_script.js");
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
GURL url_a(embedded_test_server()->GetURL("a.com", "/empty.html"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/empty.html"));
|
|
|
|
// 1) Navigate to A.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
// Append the script tag.
|
|
EXPECT_TRUE(ExecJs(shell(), R"(
|
|
var script = document.createElement('script');
|
|
script.src = 'small_script.js'
|
|
document.body.appendChild(script);
|
|
)"));
|
|
|
|
response.WaitForRequest();
|
|
// Send the small_script.js but not complete, so that the datapipe is passed
|
|
// to ScriptStreamer upon bfcache entrance.
|
|
const char kHttpResponseHeader[] =
|
|
"HTTP/1.1 200 OK\r\n"
|
|
"Content-Type: text/html; charset=utf-8\r\n"
|
|
"\r\n";
|
|
response.Send(kHttpResponseHeader);
|
|
response.Send("alert('more than 4 bytes');");
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
// Complete the response after navigating away.
|
|
response.Send("alert('more than 4 bytes');");
|
|
response.Done();
|
|
|
|
// 3) Go back to A.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectRestored(FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(
|
|
BackForwardCacheBrowserTestWithUnfreezableLoading,
|
|
PageWithDrainedDatapipeRequestsForScriptStreamerShouldBeEvictedIfStreamedTooMuch) {
|
|
net::test_server::ControllableHttpResponse response(embedded_test_server(),
|
|
"/small_script.js");
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
GURL url_a(embedded_test_server()->GetURL("a.com", "/empty.html"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/empty.html"));
|
|
|
|
// 1) Navigate to A.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
// Append the script tag.
|
|
EXPECT_TRUE(ExecJs(shell(), R"(
|
|
var script = document.createElement('script');
|
|
script.src = 'small_script.js'
|
|
document.body.appendChild(script);
|
|
)"));
|
|
|
|
response.WaitForRequest();
|
|
// Send the small_script.js but not complete, so that the datapipe is passed
|
|
// to ScriptStreamer upon bfcache entrance.
|
|
const char kHttpResponseHeader[] =
|
|
"HTTP/1.1 200 OK\r\n"
|
|
"Content-Type: text/html; charset=utf-8\r\n"
|
|
"\r\n";
|
|
response.Send(kHttpResponseHeader);
|
|
response.Send("alert('more than 4 bytes');");
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
// Complete the response after navigating away.
|
|
std::string body(kMaxBufferedBytesPerRequest + 1, '*');
|
|
response.Send(body);
|
|
response.Done();
|
|
|
|
// 3) Go back to A.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kNetworkExceedsBufferLimit},
|
|
{}, {}, {}, FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithUnfreezableLoading,
|
|
ImageStillLoading_ResponseStartedWhileFrozen) {
|
|
net::test_server::ControllableHttpResponse image_response(
|
|
embedded_test_server(), "/image.png");
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
// 1) Navigate to a page with an image with src == "image.png".
|
|
RenderFrameHostImpl* rfh_1 = NavigateToPageWithImage(
|
|
embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
image_response.WaitForRequest();
|
|
|
|
// 2) Navigate away.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("b.com", "/title2.html")));
|
|
// The page was still loading when we navigated away, but it's still eligible
|
|
// for back-forward cache.
|
|
EXPECT_TRUE(rfh_1->IsInBackForwardCache());
|
|
|
|
// Start sending the image body while in the back-forward cache.
|
|
image_response.Send(net::HTTP_OK, "image/png");
|
|
image_response.Send("image_body");
|
|
image_response.Done();
|
|
|
|
// 3) Go back to the first page. We should restore the page from the
|
|
// back-forward cache.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectRestored(FROM_HERE);
|
|
|
|
// Wait until the deferred body is processed. Since it's not a valid image
|
|
// value, we'll get the "error" event.
|
|
EXPECT_EQ("error", EvalJs(rfh_1, "image_load_status"));
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(
|
|
BackForwardCacheBrowserTestWithUnfreezableLoading,
|
|
ImageStillLoading_ResponseStartedWhileFrozen_ExceedsPerRequestBytesLimit) {
|
|
net::test_server::ControllableHttpResponse image_response(
|
|
embedded_test_server(), "/image.png");
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
// 1) Navigate to a page with an image with src == "image.png".
|
|
RenderFrameHostImpl* rfh_1 = NavigateToPageWithImage(
|
|
embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
|
|
// Wait for the image request, but don't send anything yet.
|
|
image_response.WaitForRequest();
|
|
|
|
// 2) Navigate away.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("b.com", "/title2.html")));
|
|
// The page was still loading when we navigated away, but it's still eligible
|
|
// for back-forward cache.
|
|
EXPECT_TRUE(rfh_1->IsInBackForwardCache());
|
|
|
|
RenderFrameDeletedObserver delete_observer(rfh_1);
|
|
// Start sending the image response while in the back-forward cache.
|
|
image_response.Send(net::HTTP_OK, "image/png");
|
|
std::string body(kMaxBufferedBytesPerRequest + 1, '*');
|
|
image_response.Send(body);
|
|
image_response.Done();
|
|
delete_observer.WaitUntilDeleted();
|
|
|
|
// 3) Go back to the first page. We should not restore the page from the
|
|
// back-forward cache.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kNetworkExceedsBufferLimit},
|
|
{}, {}, {}, FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(
|
|
BackForwardCacheBrowserTestWithUnfreezableLoading,
|
|
ImageStillLoading_ResponseStartedWhileRestoring_DoNotTriggerEviction) {
|
|
net::test_server::ControllableHttpResponse image_response(
|
|
embedded_test_server(), "/image.png");
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
// 1) Navigate to a page with an image with src == "image.png".
|
|
GURL url(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
RenderFrameHostImpl* rfh_1 = NavigateToPageWithImage(url);
|
|
|
|
// Wait for the image request, but don't send anything yet.
|
|
image_response.WaitForRequest();
|
|
|
|
// 2) Navigate away.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("b.com", "/title2.html")));
|
|
// The page was still loading when we navigated away, but it's still eligible
|
|
// for back-forward cache.
|
|
EXPECT_TRUE(rfh_1->IsInBackForwardCache());
|
|
|
|
// 3) Go back to the first page using TestNavigationManager so that we split
|
|
// the navigation into stages.
|
|
// web_contents()->GetController().GoBack();
|
|
TestNavigationManager navigation_manager_back(shell()->web_contents(), url);
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(navigation_manager_back.WaitForResponse());
|
|
|
|
// Before we try to commit the navigation, BFCache will defer to wait
|
|
// asynchronously for renderers to reply that they've unfrozen. Finish the
|
|
// image response in that time.
|
|
navigation_manager_back.ResumeNavigation();
|
|
ASSERT_FALSE(navigation_manager_back.GetNavigationHandle()->HasCommitted());
|
|
|
|
image_response.Send(net::HTTP_OK, "image/png");
|
|
std::string body(kMaxBufferedBytesPerRequest + 1, '*');
|
|
image_response.Send(body);
|
|
image_response.Done();
|
|
|
|
// Finish the navigation.
|
|
navigation_manager_back.WaitForNavigationFinished();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectRestored(FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(
|
|
BackForwardCacheBrowserTestWithUnfreezableLoading,
|
|
ImageStillLoading_ResponseStartedWhileFrozen_ExceedsPerProcessBytesLimit) {
|
|
net::test_server::ControllableHttpResponse image1_response(
|
|
embedded_test_server(), "/image1.png");
|
|
net::test_server::ControllableHttpResponse image2_response(
|
|
embedded_test_server(), "/image2.png");
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
// 1) Navigate to a page with 2 images.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("a.com", "/title1.html")));
|
|
RenderFrameHostImpl* rfh_1 = 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.
|
|
WaitForDOMContentLoaded(rfh_1);
|
|
|
|
EXPECT_TRUE(ExecJs(rfh_1, R"(
|
|
var image1 = document.createElement("img");
|
|
image1.src = "image1.png";
|
|
document.body.appendChild(image1);
|
|
var image2 = document.createElement("img");
|
|
image2.src = "image2.png";
|
|
document.body.appendChild(image1);
|
|
|
|
var image1_load_status = new Promise((resolve, reject) => {
|
|
image1.onload = () => { resolve("loaded"); }
|
|
image1.onerror = () => { resolve("error"); }
|
|
});
|
|
|
|
var image2_load_status = new Promise((resolve, reject) => {
|
|
image2.onload = () => { resolve("loaded"); }
|
|
image2.onerror = () => { resolve("error"); }
|
|
});
|
|
)"));
|
|
|
|
// Wait for the image requests, but don't send anything yet.
|
|
image1_response.WaitForRequest();
|
|
image2_response.WaitForRequest();
|
|
|
|
// 2) Navigate away.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("b.com", "/title2.html")));
|
|
// The page was still loading when we navigated away, but it's still eligible
|
|
// for back-forward cache.
|
|
EXPECT_TRUE(rfh_1->IsInBackForwardCache());
|
|
|
|
RenderFrameDeletedObserver delete_observer(rfh_1);
|
|
// Start sending the image responses while in the back-forward cache. The
|
|
// body size of the responses individually is less than the per-request limit,
|
|
// but together they surpass the per-process limit.
|
|
const int image_body_size = kMaxBufferedBytesPerProcess / 2 + 1;
|
|
DCHECK_LT(image_body_size, kMaxBufferedBytesPerRequest);
|
|
std::string body(image_body_size, '*');
|
|
image1_response.Send(net::HTTP_OK, "image/png");
|
|
image1_response.Send(body);
|
|
image1_response.Done();
|
|
image2_response.Send(net::HTTP_OK, "image/png");
|
|
image2_response.Send(body);
|
|
image2_response.Done();
|
|
delete_observer.WaitUntilDeleted();
|
|
|
|
// 3) Go back to the first page. We should not restore the page from the
|
|
// back-forward cache.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kNetworkExceedsBufferLimit},
|
|
{}, {}, {}, FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(
|
|
BackForwardCacheBrowserTestWithUnfreezableLoading,
|
|
ImageStillLoading_ResponseStartedWhileFrozen_ExceedsPerProcessBytesLimit_SameSiteSubframe) {
|
|
net::test_server::ControllableHttpResponse image1_response(
|
|
embedded_test_server(), "/image1.png");
|
|
net::test_server::ControllableHttpResponse image2_response(
|
|
embedded_test_server(), "/image2.png");
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
// 1) Navigate main frame to a page with 1 image.
|
|
EXPECT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL(
|
|
"a.com", "/page_with_iframe.html")));
|
|
RenderFrameHostImpl* main_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.
|
|
WaitForDOMContentLoaded(main_rfh);
|
|
|
|
EXPECT_TRUE(ExecJs(main_rfh, R"(
|
|
var image1 = document.createElement("img");
|
|
image1.src = "image1.png";
|
|
document.body.appendChild(image1);
|
|
var image1_load_status = new Promise((resolve, reject) => {
|
|
image1.onload = () => { resolve("loaded"); }
|
|
image1.onerror = () => { resolve("error"); }
|
|
});
|
|
)"));
|
|
|
|
// 2) Add 1 image to the subframe.
|
|
RenderFrameHostImpl* subframe_rfh =
|
|
main_rfh->child_at(0)->current_frame_host();
|
|
|
|
// First, wait for the subframe document to load DOM to ensure that kLoading
|
|
// is not one of the reasons why the document wasn't cached.
|
|
WaitForDOMContentLoaded(subframe_rfh);
|
|
|
|
EXPECT_TRUE(ExecJs(subframe_rfh, R"(
|
|
var image2 = document.createElement("img");
|
|
image2.src = "image2.png";
|
|
document.body.appendChild(image2);
|
|
var image2_load_status = new Promise((resolve, reject) => {
|
|
image2.onload = () => { resolve("loaded"); }
|
|
image2.onerror = () => { resolve("error"); }
|
|
});
|
|
)"));
|
|
|
|
// Wait for the image requests, but don't send anything yet.
|
|
image1_response.WaitForRequest();
|
|
image2_response.WaitForRequest();
|
|
|
|
// 3) Navigate away on the main frame.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("b.com", "/title2.html")));
|
|
// The page was still loading images when we navigated away, but it's still
|
|
// eligible for back-forward cache.
|
|
EXPECT_TRUE(main_rfh->IsInBackForwardCache());
|
|
EXPECT_TRUE(subframe_rfh->IsInBackForwardCache());
|
|
|
|
RenderFrameDeletedObserver delete_observer_1(main_rfh);
|
|
RenderFrameDeletedObserver delete_observer_2(subframe_rfh);
|
|
// Start sending the image responses while in the back-forward cache. The
|
|
// body size of the responses individually is less than the per-request limit,
|
|
// but together they surpass the per-process limit since both the main frame
|
|
// and the subframe are put in the same renderer process (because they're
|
|
// same-site).
|
|
const int image_body_size = kMaxBufferedBytesPerProcess / 2 + 1;
|
|
DCHECK_LT(image_body_size, kMaxBufferedBytesPerRequest);
|
|
std::string body(image_body_size, '*');
|
|
image1_response.Send(net::HTTP_OK, "image/png");
|
|
image1_response.Send(body);
|
|
image1_response.Done();
|
|
image2_response.Send(net::HTTP_OK, "image/png");
|
|
image2_response.Send(body);
|
|
image2_response.Done();
|
|
delete_observer_1.WaitUntilDeleted();
|
|
delete_observer_2.WaitUntilDeleted();
|
|
|
|
// 3) Go back to the first page. We should not restore the page from the
|
|
// back-forward cache.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kNetworkExceedsBufferLimit},
|
|
{}, {}, {}, FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(
|
|
BackForwardCacheBrowserTestWithUnfreezableLoading,
|
|
ImageStillLoading_ResponseStartedWhileFrozen_ExceedsPerProcessBytesLimit_ResetOnRestore) {
|
|
net::test_server::ControllableHttpResponse image1_response(
|
|
embedded_test_server(), "/image.png");
|
|
net::test_server::ControllableHttpResponse image2_response(
|
|
embedded_test_server(), "/image2.png");
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
// 1) Navigate to a page with an image with src == "image.png".
|
|
RenderFrameHostImpl* rfh_1 = NavigateToPageWithImage(
|
|
embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
|
|
// Wait for the image request, but don't send anything yet.
|
|
image1_response.WaitForRequest();
|
|
|
|
// 2) Navigate away on the main frame.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("a.com", "/title2.html")));
|
|
RenderFrameHostImpl* rfh_2 = current_frame_host();
|
|
WaitForDOMContentLoaded(rfh_2);
|
|
|
|
// The first page was still loading images when we navigated away, but it's
|
|
// still eligible for back-forward cache.
|
|
EXPECT_TRUE(rfh_1->IsInBackForwardCache());
|
|
|
|
// 3) Add 1 image to the second page.
|
|
EXPECT_TRUE(ExecJs(rfh_2, R"(
|
|
var image2 = document.createElement("img");
|
|
image2.src = "image2.png";
|
|
document.body.appendChild(image2);
|
|
var image2_load_status = new Promise((resolve, reject) => {
|
|
image2.onload = () => { resolve("loaded"); }
|
|
image2.onerror = () => { resolve("error"); }
|
|
});
|
|
)"));
|
|
image2_response.WaitForRequest();
|
|
|
|
// Start sending the image response for the first page while in the
|
|
// back-forward cache. The body size of the response is half of the
|
|
// per-process limit.
|
|
const int image_body_size = kMaxBufferedBytesPerProcess / 2 + 1;
|
|
DCHECK_LT(image_body_size, kMaxBufferedBytesPerRequest);
|
|
std::string body(image_body_size, '*');
|
|
image1_response.Send(net::HTTP_OK, "image/png");
|
|
image1_response.Send(body);
|
|
image1_response.Done();
|
|
|
|
// 4) Go back to the first page. We should restore the page from the
|
|
// back-forward cache.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectRestored(FROM_HERE);
|
|
|
|
// The second page was still loading images when we navigated away, but it's
|
|
// still eligible for back-forward cache.
|
|
EXPECT_TRUE(rfh_2->IsInBackForwardCache());
|
|
|
|
// Start sending the image response for the second page's image request.
|
|
// The second page should still stay in the back-forward cache since the
|
|
// per-process buffer limit is reset back to 0 after the first page gets
|
|
// restored from the back-forward cache, so we wouldn't go over the
|
|
// per-process buffer limit even when the total body size buffered during the
|
|
// lifetime of the test actually exceeds the per-process buffer limit.
|
|
image2_response.Send(net::HTTP_OK, "image/png");
|
|
image2_response.Send(body);
|
|
image2_response.Done();
|
|
|
|
EXPECT_TRUE(rfh_2->IsInBackForwardCache());
|
|
|
|
// 5) Go forward. We should restore the second page from the back-forward
|
|
// cache.
|
|
web_contents()->GetController().GoForward();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectRestored(FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(
|
|
BackForwardCacheBrowserTestWithUnfreezableLoading,
|
|
ImageStillLoading_ResponseStartedWhileFrozen_ExceedsPerProcessBytesLimit_ResetOnDetach) {
|
|
net::test_server::ControllableHttpResponse image1_response(
|
|
embedded_test_server(), "/image.png");
|
|
net::test_server::ControllableHttpResponse image2_response(
|
|
embedded_test_server(), "/image2.png");
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
// 1) Navigate to a page with an image with src == "image.png".
|
|
RenderFrameHostImpl* rfh_1 = NavigateToPageWithImage(
|
|
embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
|
|
// Wait for the image request, but don't send anything yet.
|
|
image1_response.WaitForRequest();
|
|
|
|
// 2) Navigate away on the main frame.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("a.com", "/title2.html")));
|
|
RenderFrameHostImpl* rfh_2 = current_frame_host();
|
|
WaitForDOMContentLoaded(rfh_2);
|
|
|
|
// The first page was still loading images when we navigated away, but it's
|
|
// still eligible for back-forward cache.
|
|
EXPECT_TRUE(rfh_1->IsInBackForwardCache());
|
|
|
|
// 3) Add 1 image to the second page.
|
|
EXPECT_TRUE(ExecJs(rfh_2, R"(
|
|
var image2 = document.createElement("img");
|
|
image2.src = "image2.png";
|
|
document.body.appendChild(image2);
|
|
var image2_load_status = new Promise((resolve, reject) => {
|
|
image2.onload = () => { resolve("loaded"); }
|
|
image2.onerror = () => { resolve("error"); }
|
|
});
|
|
)"));
|
|
image2_response.WaitForRequest();
|
|
|
|
RenderFrameDeletedObserver delete_observer_1(rfh_1);
|
|
// Start sending an image response that's larger than the per-process and
|
|
// per-request buffer limit, causing the page to get evicted from the
|
|
// back-forward cache.
|
|
std::string body(kMaxBufferedBytesPerProcess + 1, '*');
|
|
image1_response.Send(net::HTTP_OK, "image/png");
|
|
image1_response.Send(body);
|
|
image1_response.Done();
|
|
delete_observer_1.WaitUntilDeleted();
|
|
|
|
// 4) Go back to the first page. We should not restore the page from the
|
|
// back-forward cache.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kNetworkExceedsBufferLimit},
|
|
{}, {}, {}, FROM_HERE);
|
|
|
|
// The second page was still loading images when we navigated away, but it's
|
|
// still eligible for back-forward cache.
|
|
EXPECT_TRUE(rfh_2->IsInBackForwardCache());
|
|
|
|
// Start sending a small image response for the second page's image request.
|
|
// The second page should still stay in the back-forward cache since the
|
|
// per-process buffer limit is reset back to 0 after the first page gets
|
|
// evicted and deleted
|
|
image2_response.Send(net::HTTP_OK, "image/png");
|
|
image2_response.Send("*");
|
|
image2_response.Done();
|
|
|
|
EXPECT_TRUE(rfh_2->IsInBackForwardCache());
|
|
|
|
// 5) Go forward. We should restore the second page from the back-forward
|
|
// cache.
|
|
web_contents()->GetController().GoForward();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectRestored(FROM_HERE);
|
|
|
|
// Wait until the deferred body is processed. Since it's not a valid image
|
|
// value, we'll get the "error" event.
|
|
EXPECT_EQ("error", EvalJs(rfh_2, "image2_load_status"));
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithUnfreezableLoading,
|
|
ImageStillLoading_ResponseStartedWhileFrozen_Timeout) {
|
|
net::test_server::ControllableHttpResponse image_response(
|
|
embedded_test_server(), "/image.png");
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
// 1) Navigate to a page with an image with src == "image.png".
|
|
RenderFrameHostImpl* rfh_1 = NavigateToPageWithImage(
|
|
embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
|
|
// Wait for the image request, but don't send anything yet.
|
|
image_response.WaitForRequest();
|
|
|
|
// 2) Navigate away.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("b.com", "/title2.html")));
|
|
// The page was still loading when we navigated away, but it's still eligible
|
|
// for back-forward cache.
|
|
EXPECT_TRUE(rfh_1->IsInBackForwardCache());
|
|
|
|
RenderFrameDeletedObserver delete_observer(rfh_1);
|
|
// Start sending the image response while in the back-forward cache, but never
|
|
// finish the request. Eventually the page will get deleted due to network
|
|
// request timeout.
|
|
image_response.Send(net::HTTP_OK, "image/png");
|
|
std::string body(kMaxBufferedBytesPerRequest + 1, '*');
|
|
delete_observer.WaitUntilDeleted();
|
|
|
|
// 3) Go back to the first page. We should not restore the page from the
|
|
// back-forward cache.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kNetworkRequestTimeout}, {},
|
|
{}, {}, FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(
|
|
BackForwardCacheBrowserTestWithUnfreezableLoading,
|
|
ImageStillLoading_ResponseStartedBeforeFreezing_ExceedsPerRequestBytesLimit) {
|
|
net::test_server::ControllableHttpResponse image_response(
|
|
embedded_test_server(), "/image.png");
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
// 1) Navigate to a page with an image with src == "image.png".
|
|
RenderFrameHostImpl* rfh_1 = NavigateToPageWithImage(
|
|
embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
|
|
// Start sending response before the page gets in the back-forward cache.
|
|
image_response.WaitForRequest();
|
|
image_response.Send(net::HTTP_OK, "image/png");
|
|
image_response.Send(" ");
|
|
// Run some script to ensure the renderer processed its pending tasks.
|
|
EXPECT_TRUE(ExecJs(rfh_1, "var foo = 42;"));
|
|
|
|
// 2) Navigate away.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("b.com", "/title2.html")));
|
|
// The page was still loading when we navigated away, but it's still eligible
|
|
// for back-forward cache.
|
|
EXPECT_TRUE(rfh_1->IsInBackForwardCache());
|
|
|
|
// Send the image response body while in the back-forward cache.
|
|
RenderFrameDeletedObserver delete_observer(rfh_1);
|
|
std::string body(kMaxBufferedBytesPerRequest + 1, '*');
|
|
image_response.Send(body);
|
|
image_response.Done();
|
|
delete_observer.WaitUntilDeleted();
|
|
|
|
// 3) Go back to the first page. We should not restore the page from the
|
|
// back-forward cache.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kNetworkExceedsBufferLimit},
|
|
{}, {}, {}, FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(
|
|
BackForwardCacheBrowserTestWithUnfreezableLoading,
|
|
ImageStillLoading_ResponseStartedBeforeFreezing_ExceedsPerProcessBytesLimit) {
|
|
net::test_server::ControllableHttpResponse image1_response(
|
|
embedded_test_server(), "/image1.png");
|
|
net::test_server::ControllableHttpResponse image2_response(
|
|
embedded_test_server(), "/image2.png");
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
// 1) Navigate to a page with 2 images.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("a.com", "/title1.html")));
|
|
RenderFrameHostImpl* rfh_1 = 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.
|
|
WaitForDOMContentLoaded(rfh_1);
|
|
|
|
EXPECT_TRUE(ExecJs(rfh_1, R"(
|
|
var image1 = document.createElement("img");
|
|
image1.src = "image1.png";
|
|
document.body.appendChild(image1);
|
|
var image2 = document.createElement("img");
|
|
image2.src = "image2.png";
|
|
document.body.appendChild(image1);
|
|
|
|
var image1_load_status = new Promise((resolve, reject) => {
|
|
image1.onload = () => { resolve("loaded"); }
|
|
image1.onerror = () => { resolve("error"); }
|
|
});
|
|
|
|
var image2_load_status = new Promise((resolve, reject) => {
|
|
image2.onload = () => { resolve("loaded"); }
|
|
image2.onerror = () => { resolve("error"); }
|
|
});
|
|
)"));
|
|
|
|
// Wait for the image requests, but don't send anything yet.
|
|
|
|
// Start sending response before the page gets in the back-forward cache.
|
|
image1_response.WaitForRequest();
|
|
image1_response.Send(net::HTTP_OK, "image/png");
|
|
image1_response.Send(" ");
|
|
image2_response.WaitForRequest();
|
|
image2_response.Send(net::HTTP_OK, "image/png");
|
|
image2_response.Send(" ");
|
|
// Run some script to ensure the renderer processed its pending tasks.
|
|
EXPECT_TRUE(ExecJs(rfh_1, "var foo = 42;"));
|
|
|
|
// 2) Navigate away.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("b.com", "/title2.html")));
|
|
// The page was still loading when we navigated away, but it's still eligible
|
|
// for back-forward cache.
|
|
EXPECT_TRUE(rfh_1->IsInBackForwardCache());
|
|
|
|
RenderFrameDeletedObserver delete_observer(rfh_1);
|
|
// Send the image response body while in the back-forward cache. The body size
|
|
// of the responses individually is less than the per-request limit, but
|
|
// together they surpass the per-process limit.
|
|
const int image_body_size = kMaxBufferedBytesPerProcess / 2 + 1;
|
|
DCHECK_LT(image_body_size, kMaxBufferedBytesPerRequest);
|
|
std::string body(image_body_size, '*');
|
|
image1_response.Send(body);
|
|
image1_response.Done();
|
|
image2_response.Send(body);
|
|
image2_response.Done();
|
|
delete_observer.WaitUntilDeleted();
|
|
|
|
// 3) Go back to the first page. We should not restore the page from the
|
|
// back-forward cache.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kNetworkExceedsBufferLimit},
|
|
{}, {}, {}, FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithUnfreezableLoading,
|
|
TimeoutNotTriggeredAfterDone) {
|
|
net::test_server::ControllableHttpResponse image_response(
|
|
embedded_test_server(), "/image.png");
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
// 1) Navigate to a page with an image with src == "image.png".
|
|
RenderFrameHostImpl* rfh_1 = NavigateToPageWithImage(
|
|
embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
|
|
// Wait for the image request, but don't send anything yet.
|
|
image_response.WaitForRequest();
|
|
|
|
// 2) Navigate away.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("b.com", "/title2.html")));
|
|
// The page was still loading when we navigated away, but it's still eligible
|
|
// for back-forward cache.
|
|
EXPECT_TRUE(rfh_1->IsInBackForwardCache());
|
|
|
|
RenderFrameDeletedObserver delete_observer(rfh_1);
|
|
// Start sending the image response while in the back-forward cache and finish
|
|
// the request before the active request timeout hits.
|
|
image_response.Send(net::HTTP_OK, "image/png");
|
|
image_response.Send(" ");
|
|
image_response.Done();
|
|
|
|
// Make sure enough time passed to trigger network request eviction if the
|
|
// load above didn't finish.
|
|
base::RunLoop run_loop;
|
|
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
|
|
FROM_HERE, run_loop.QuitClosure(),
|
|
kGracePeriodToFinishLoading + base::TimeDelta::FromSeconds(1));
|
|
run_loop.Run();
|
|
|
|
// Ensure that the page is still in bfcache.
|
|
EXPECT_FALSE(delete_observer.deleted());
|
|
EXPECT_TRUE(rfh_1->IsInBackForwardCache());
|
|
|
|
// 3) Go back to the first page. We should restore the page from the
|
|
// back-forward cache.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectRestored(FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(
|
|
BackForwardCacheBrowserTestWithUnfreezableLoading,
|
|
TimeoutNotTriggeredAfterDone_ResponseStartedBeforeFreezing) {
|
|
net::test_server::ControllableHttpResponse image_response(
|
|
embedded_test_server(), "/image.png");
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
// 1) Navigate to a page with an image with src == "image.png".
|
|
RenderFrameHostImpl* rfh_1 = NavigateToPageWithImage(
|
|
embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
|
|
// Start sending response before the page gets in the back-forward cache.
|
|
image_response.WaitForRequest();
|
|
image_response.Send(net::HTTP_OK, "image/png");
|
|
image_response.Send(" ");
|
|
|
|
// 2) Navigate away.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("b.com", "/title2.html")));
|
|
// The page was still loading when we navigated away, but it's still eligible
|
|
// for back-forward cache.
|
|
EXPECT_TRUE(rfh_1->IsInBackForwardCache());
|
|
|
|
RenderFrameDeletedObserver delete_observer(rfh_1);
|
|
// Finish the request before the active request timeout hits.
|
|
image_response.Done();
|
|
|
|
// Make sure enough time passed to trigger network request eviction if the
|
|
// load above didn't finish.
|
|
base::RunLoop run_loop;
|
|
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
|
|
FROM_HERE, run_loop.QuitClosure(),
|
|
kGracePeriodToFinishLoading + base::TimeDelta::FromSeconds(1));
|
|
run_loop.Run();
|
|
|
|
// Ensure that the page is still in bfcache.
|
|
EXPECT_FALSE(delete_observer.deleted());
|
|
EXPECT_TRUE(rfh_1->IsInBackForwardCache());
|
|
|
|
// 3) Go back to the first page. We should restore the page from the
|
|
// back-forward cache.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectRestored(FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithUnfreezableLoading,
|
|
ImageStillLoading_ResponseStartedBeforeFreezing) {
|
|
net::test_server::ControllableHttpResponse image_response(
|
|
embedded_test_server(), "/image.png");
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
// 1) Navigate to a page with an image with src == "image.png".
|
|
RenderFrameHostImpl* rfh_1 = NavigateToPageWithImage(
|
|
embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
|
|
// Start sending response before the page gets in the back-forward cache.
|
|
image_response.WaitForRequest();
|
|
image_response.Send(net::HTTP_OK, "image/png");
|
|
image_response.Send(" ");
|
|
// Run some script to ensure the renderer processed its pending tasks.
|
|
EXPECT_TRUE(ExecJs(rfh_1, "var foo = 42;"));
|
|
|
|
// 2) Navigate away.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("b.com", "/title2.html")));
|
|
// The page was still loading when we navigated away, but it's still eligible
|
|
// for back-forward cache.
|
|
EXPECT_TRUE(rfh_1->IsInBackForwardCache());
|
|
|
|
// Send body while in the back-forward cache.
|
|
image_response.Send("image_body");
|
|
image_response.Done();
|
|
|
|
// 3) Go back to the first page. We should restore the page from the
|
|
// back-forward cache.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectRestored(FROM_HERE);
|
|
|
|
// Wait until the deferred body is processed. Since it's not a valid image
|
|
// value, we'll get the "error" event.
|
|
EXPECT_EQ("error", EvalJs(rfh_1, "image_load_status"));
|
|
}
|
|
|
|
// Disabled on Android, since we have problems starting up the websocket test
|
|
// server in the host
|
|
// TODO(crbug.com/1200285): Fix flakiness on macOS.
|
|
#if defined(OS_ANDROID) || defined(OS_MAC)
|
|
#define MAYBE_WebSocketNotCached DISABLED_WebSocketNotCached
|
|
#else
|
|
#define MAYBE_WebSocketNotCached WebSocketNotCached
|
|
#endif
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, MAYBE_WebSocketNotCached) {
|
|
net::SpawnedTestServer ws_server(net::SpawnedTestServer::TYPE_WS,
|
|
net::GetWebSocketTestDataDirectory());
|
|
ASSERT_TRUE(ws_server.Start());
|
|
|
|
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));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
|
|
// Open a WebSocket.
|
|
const char script[] = R"(
|
|
new Promise(resolve => {
|
|
const socket = new WebSocket($1);
|
|
socket.addEventListener('open', () => resolve());
|
|
});)";
|
|
ASSERT_TRUE(ExecJs(
|
|
rfh_a, JsReplace(script, ws_server.GetURL("echo-with-no-extension"))));
|
|
|
|
// 2) Navigate to B.
|
|
ASSERT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
// Confirm A is evicted.
|
|
delete_observer_rfh_a.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.com", "/title1.html"));
|
|
GURL https_url(https_server()->GetURL("a.com", "/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));
|
|
RenderFrameHostImpl* rfh = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer(rfh);
|
|
|
|
// 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(delete_observer.deleted());
|
|
EXPECT_TRUE(rfh->IsInBackForwardCache());
|
|
continue;
|
|
}
|
|
|
|
// On Android, navigations to about:blank keeps the same RenderFrameHost.
|
|
// Obviously, it can't enter the BackForwardCache, because it is still used
|
|
// to display the current document.
|
|
if (test_case.url == blank_url && !AreStrictSiteInstancesEnabled()) {
|
|
EXPECT_FALSE(delete_observer.deleted());
|
|
EXPECT_FALSE(rfh->IsInBackForwardCache());
|
|
EXPECT_EQ(rfh, current_frame_host());
|
|
continue;
|
|
}
|
|
|
|
delete_observer.WaitUntilDeleted();
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
|
|
void RegisterServiceWorker(RenderFrameHostImpl* rfh) {
|
|
EXPECT_EQ("success", EvalJs(rfh, R"(
|
|
let controller_changed_promise = new Promise(resolve_controller_change => {
|
|
navigator.serviceWorker.oncontrollerchange = resolve_controller_change;
|
|
});
|
|
|
|
new Promise(async resolve => {
|
|
try {
|
|
await navigator.serviceWorker.register(
|
|
"./service-worker.js", {scope: "./"})
|
|
} catch (e) {
|
|
resolve("error: registration has failed");
|
|
}
|
|
|
|
await controller_changed_promise;
|
|
|
|
if (navigator.serviceWorker.controller) {
|
|
resolve("success");
|
|
} else {
|
|
resolve("error: not controlled by service worker");
|
|
}
|
|
});
|
|
)"));
|
|
}
|
|
|
|
// Returns a unique script for each request, to test service worker update.
|
|
std::unique_ptr<net::test_server::HttpResponse> RequestHandlerForUpdateWorker(
|
|
const net::test_server::HttpRequest& request) {
|
|
if (request.relative_url != "/back_forward_cache/service-worker.js")
|
|
return nullptr;
|
|
static int counter = 0;
|
|
auto http_response = std::make_unique<net::test_server::BasicHttpResponse>();
|
|
http_response->set_code(net::HTTP_OK);
|
|
const char script[] = R"(
|
|
// counter = $1
|
|
self.addEventListener('activate', function(event) {
|
|
event.waitUntil(self.clients.claim());
|
|
});
|
|
)";
|
|
http_response->set_content(JsReplace(script, counter++));
|
|
http_response->set_content_type("text/javascript");
|
|
http_response->AddCustomHeader("Cache-Control",
|
|
"no-cache, no-store, must-revalidate");
|
|
return http_response;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
class BackForwardCacheBrowserTestWithVibration
|
|
: public BackForwardCacheBrowserTest,
|
|
public device::mojom::VibrationManager {
|
|
public:
|
|
BackForwardCacheBrowserTestWithVibration() {
|
|
OverrideVibrationManagerBinderForTesting(base::BindRepeating(
|
|
&BackForwardCacheBrowserTestWithVibration::BindVibrationManager,
|
|
base::Unretained(this)));
|
|
}
|
|
|
|
~BackForwardCacheBrowserTestWithVibration() override {
|
|
OverrideVibrationManagerBinderForTesting(base::NullCallback());
|
|
}
|
|
|
|
void BindVibrationManager(
|
|
mojo::PendingReceiver<device::mojom::VibrationManager> receiver) {
|
|
receiver_.Bind(std::move(receiver));
|
|
}
|
|
|
|
bool TriggerVibrate(RenderFrameHostImpl* rfh,
|
|
int duration,
|
|
base::OnceClosure vibrate_done) {
|
|
vibrate_done_ = std::move(vibrate_done);
|
|
return EvalJs(rfh, JsReplace("navigator.vibrate($1)", duration))
|
|
.ExtractBool();
|
|
}
|
|
|
|
bool TriggerShortVibrationSequence(RenderFrameHostImpl* rfh,
|
|
base::OnceClosure vibrate_done) {
|
|
vibrate_done_ = std::move(vibrate_done);
|
|
return EvalJs(rfh, "navigator.vibrate([10] * 1000)").ExtractBool();
|
|
}
|
|
|
|
bool IsCancelled() { return cancelled_; }
|
|
|
|
private:
|
|
// device::mojom::VibrationManager:
|
|
void Vibrate(int64_t milliseconds, VibrateCallback callback) override {
|
|
cancelled_ = false;
|
|
std::move(callback).Run();
|
|
std::move(vibrate_done_).Run();
|
|
}
|
|
|
|
void Cancel(CancelCallback callback) override {
|
|
cancelled_ = true;
|
|
std::move(callback).Run();
|
|
}
|
|
|
|
bool cancelled_ = false;
|
|
base::OnceClosure vibrate_done_;
|
|
mojo::Receiver<device::mojom::VibrationManager> receiver_{this};
|
|
};
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithVibration,
|
|
VibrationStopsAfterEnteringCache) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
// 1) Navigate to a page with a long vibration.
|
|
GURL url(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
base::RunLoop run_loop;
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
ASSERT_TRUE(TriggerVibrate(rfh_a, 10000, run_loop.QuitClosure()));
|
|
EXPECT_FALSE(IsCancelled());
|
|
|
|
// 2) Navigate away and expect the vibration to be canceled.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
|
|
EXPECT_NE(current_frame_host(), rfh_a);
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
EXPECT_TRUE(IsCancelled());
|
|
|
|
// 3) Go back to A.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectRestored(FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithVibration,
|
|
ShortVibrationSequenceStopsAfterEnteringCache) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
// 1) Navigate to a page with a long vibration.
|
|
GURL url(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
base::RunLoop run_loop;
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
ASSERT_TRUE(TriggerShortVibrationSequence(rfh_a, run_loop.QuitClosure()));
|
|
EXPECT_FALSE(IsCancelled());
|
|
|
|
// 2) Navigate away and expect the vibration to be canceled.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
|
|
EXPECT_NE(current_frame_host(), rfh_a);
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
EXPECT_TRUE(IsCancelled());
|
|
|
|
// 3) Go back to A.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectRestored(FROM_HERE);
|
|
}
|
|
|
|
class BackForwardCacheBrowserTestWithServiceWorkerEnabled
|
|
: public BackForwardCacheBrowserTest {
|
|
public:
|
|
BackForwardCacheBrowserTestWithServiceWorkerEnabled() = default;
|
|
~BackForwardCacheBrowserTestWithServiceWorkerEnabled() override = default;
|
|
|
|
protected:
|
|
void SetUpCommandLine(base::CommandLine* command_line) override {
|
|
EnableFeatureAndSetParams(features::kBackForwardCache,
|
|
"service_worker_supported", "true");
|
|
BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
|
|
}
|
|
};
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithServiceWorkerEnabled,
|
|
CachedPagesWithServiceWorkers) {
|
|
CreateHttpsServer();
|
|
SetupCrossSiteRedirector(https_server());
|
|
ASSERT_TRUE(https_server()->Start());
|
|
|
|
// 1) Navigate to A.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(),
|
|
https_server()->GetURL("a.com", "/back_forward_cache/empty.html")));
|
|
|
|
// Register a service worker.
|
|
RegisterServiceWorker(current_frame_host());
|
|
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver deleted(rfh_a);
|
|
|
|
// 2) Navigate away.
|
|
EXPECT_TRUE(
|
|
NavigateToURL(shell(), https_server()->GetURL("b.com", "/title1.html")));
|
|
|
|
EXPECT_FALSE(deleted.deleted());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
|
|
// 3) Go back to A. The navigation should be served from the cache.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_FALSE(deleted.deleted());
|
|
EXPECT_EQ(rfh_a, current_frame_host());
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithServiceWorkerEnabled,
|
|
EvictIfCacheBlocksServiceWorkerVersionActivation) {
|
|
CreateHttpsServer();
|
|
https_server()->RegisterRequestHandler(
|
|
base::BindRepeating(&RequestHandlerForUpdateWorker));
|
|
SetupCrossSiteRedirector(https_server());
|
|
ASSERT_TRUE(https_server()->Start());
|
|
Shell* tab_x = shell();
|
|
Shell* tab_y = CreateBrowser();
|
|
// 1) Navigate to A in tab X.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
tab_x,
|
|
https_server()->GetURL("a.com", "/back_forward_cache/empty.html")));
|
|
// 2) Register a service worker.
|
|
RegisterServiceWorker(current_frame_host());
|
|
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver deleted(rfh_a);
|
|
// 3) Navigate away to B in tab X.
|
|
EXPECT_TRUE(
|
|
NavigateToURL(tab_x, https_server()->GetURL("b.com", "/title1.html")));
|
|
EXPECT_FALSE(deleted.deleted());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
// 4) Navigate to A in tab Y.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
tab_y,
|
|
https_server()->GetURL("a.com", "/back_forward_cache/empty.html")));
|
|
// 5) Close tab Y to activate a service worker version.
|
|
// This should evict |rfh_a| from the cache.
|
|
tab_y->Close();
|
|
deleted.WaitUntilDeleted();
|
|
// 6) Navigate to A in tab X.
|
|
tab_x->web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(tab_x->web_contents()));
|
|
ExpectNotRestored(
|
|
{
|
|
BackForwardCacheMetrics::NotRestoredReason::
|
|
kServiceWorkerVersionActivation,
|
|
},
|
|
{}, {}, {}, FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithServiceWorkerEnabled,
|
|
EvictWithPostMessageToCachedClient) {
|
|
net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
|
|
https_server.RegisterRequestHandler(
|
|
base::BindRepeating(&RequestHandlerForUpdateWorker));
|
|
https_server.AddDefaultHandlers(GetTestDataFilePath());
|
|
https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_OK);
|
|
SetupCrossSiteRedirector(&https_server);
|
|
ASSERT_TRUE(https_server.Start());
|
|
Shell* tab_to_execute_service_worker = shell();
|
|
Shell* tab_to_be_bfcached = CreateBrowser();
|
|
|
|
// Observe the new WebContents to trace the navigtion ID.
|
|
WebContentsObserver::Observe(tab_to_be_bfcached->web_contents());
|
|
|
|
// 1) Navigate to A in |tab_to_execute_service_worker|.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
tab_to_execute_service_worker,
|
|
https_server.GetURL(
|
|
"a.com", "/back_forward_cache/service_worker_post_message.html")));
|
|
|
|
// 2) Register a service worker.
|
|
EXPECT_EQ("DONE", EvalJs(tab_to_execute_service_worker,
|
|
"register('service_worker_post_message.js')"));
|
|
|
|
// 3) Navigate to A in |tab_to_be_bfcached|.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
tab_to_be_bfcached,
|
|
https_server.GetURL(
|
|
"a.com", "/back_forward_cache/service_worker_post_message.html")));
|
|
const std::string script_to_store =
|
|
"executeCommandOnServiceWorker('StoreClients')";
|
|
EXPECT_EQ("DONE", EvalJs(tab_to_execute_service_worker, script_to_store));
|
|
RenderFrameHostImpl* rfh =
|
|
static_cast<WebContentsImpl*>(tab_to_be_bfcached->web_contents())
|
|
->GetFrameTree()
|
|
->root()
|
|
->current_frame_host();
|
|
RenderFrameDeletedObserver deleted_observer_rfh(rfh);
|
|
|
|
// 4) Navigate away to B in |tab_to_be_bfcached|.
|
|
EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached,
|
|
https_server.GetURL("b.com", "/title1.html")));
|
|
EXPECT_FALSE(deleted_observer_rfh.deleted());
|
|
EXPECT_TRUE(rfh->IsInBackForwardCache());
|
|
|
|
// 5) Trigger client.postMessage via |tab_to_execute_service_worker|. Cache in
|
|
// |tab_to_be_bfcached| will be evicted.
|
|
const std::string script_to_post_message =
|
|
"executeCommandOnServiceWorker('PostMessageToStoredClients')";
|
|
EXPECT_EQ("DONE",
|
|
EvalJs(tab_to_execute_service_worker, script_to_post_message));
|
|
deleted_observer_rfh.WaitUntilDeleted();
|
|
|
|
// 6) Go back to A in |tab_to_be_bfcached|.
|
|
tab_to_be_bfcached->web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(tab_to_be_bfcached->web_contents()));
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kServiceWorkerPostMessage},
|
|
{}, {}, {}, FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithServiceWorkerEnabled,
|
|
EvictOnServiceWorkerClaim) {
|
|
net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
|
|
https_server.RegisterRequestHandler(
|
|
base::BindRepeating(&RequestHandlerForUpdateWorker));
|
|
https_server.AddDefaultHandlers(GetTestDataFilePath());
|
|
https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_OK);
|
|
SetupCrossSiteRedirector(&https_server);
|
|
ASSERT_TRUE(https_server.Start());
|
|
|
|
Shell* tab_to_be_bfcached = shell();
|
|
Shell* tab_to_execute_service_worker = CreateBrowser();
|
|
|
|
// 1) Navigate to A in |tab_to_be_bfcached|.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
tab_to_be_bfcached,
|
|
https_server.GetURL(
|
|
"a.com", "/back_forward_cache/service_worker_registration.html")));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver deleted(rfh_a);
|
|
|
|
// 2) Navigate away to B in |tab_to_be_bfcached|.
|
|
EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached,
|
|
https_server.GetURL("b.com", "/title1.html")));
|
|
EXPECT_FALSE(deleted.deleted());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
|
|
// 3) Navigate to A in |tab_to_execute_service_worker|.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
tab_to_execute_service_worker,
|
|
https_server.GetURL(
|
|
"a.com", "/back_forward_cache/service_worker_registration.html")));
|
|
|
|
// 4) Register a service worker for |tab_to_execute_service_worker|.
|
|
EXPECT_EQ("DONE", EvalJs(tab_to_execute_service_worker,
|
|
"register('service_worker_registration.js')"));
|
|
|
|
// 5) The service worker calls clients.claim(). |rfh_a| would normally be
|
|
// claimed but because it's in bfcache, it is evicted from the cache.
|
|
EXPECT_EQ("DONE", EvalJs(tab_to_execute_service_worker, "claim()"));
|
|
|
|
// 6) Navigate to A in |tab_to_be_bfcached|.
|
|
tab_to_be_bfcached->web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(tab_to_be_bfcached->web_contents()));
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_TRUE(deleted.deleted());
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kServiceWorkerClaim}, {}, {},
|
|
{}, FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, CachePagesWithBeacon) {
|
|
constexpr char kKeepalivePath[] = "/keepalive";
|
|
|
|
net::test_server::ControllableHttpResponse keepalive(embedded_test_server(),
|
|
kKeepalivePath);
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_ping(embedded_test_server()->GetURL("a.com", kKeepalivePath));
|
|
|
|
// 1) Navigate to A.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
|
|
EXPECT_TRUE(
|
|
ExecJs(shell(), JsReplace(R"(navigator.sendBeacon($1, "");)", url_ping)));
|
|
|
|
// 2) Navigate to B.
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
// Ensure that the keepalive request is sent.
|
|
keepalive.WaitForRequest();
|
|
// Don't actually send the response.
|
|
|
|
// Page A should be in the cache.
|
|
EXPECT_FALSE(delete_observer_rfh_a.deleted());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
}
|
|
|
|
// 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.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
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.
|
|
web_contents()->GetController().GoToIndex(0);
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
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).
|
|
web_contents()->GetController().GoToIndex(0);
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
// D3 takes A2(B(C))'s place in the cache.
|
|
EXPECT_TRUE(rfh_d3->IsInBackForwardCache());
|
|
delete_rfh_a2.WaitUntilDeleted();
|
|
}
|
|
|
|
class BackForwardCacheBrowserTestWithSameSiteDisabled
|
|
: public BackForwardCacheBrowserTest {
|
|
public:
|
|
BackForwardCacheBrowserTestWithSameSiteDisabled() = default;
|
|
~BackForwardCacheBrowserTestWithSameSiteDisabled() override = default;
|
|
|
|
protected:
|
|
void SetUpCommandLine(base::CommandLine* command_line) override {
|
|
same_site_back_forward_cache_enabled_ = false;
|
|
DisableFeature(features::kProactivelySwapBrowsingInstance);
|
|
BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
|
|
}
|
|
};
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithSameSiteDisabled,
|
|
ConflictingBrowsingInstances) {
|
|
// This test assumes navigation from A1 to A2 will not switch
|
|
// BrowsingInstances, which is not true when either BackForwardCache or
|
|
// ProactivelySwapBrowsingInstance is enabled on same-site navigations.
|
|
DCHECK(!CanSameSiteMainFrameNavigationsChangeSiteInstances());
|
|
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();
|
|
// Make B3 ineligible for caching, so that navigating doesn't evict A2
|
|
// due to the cache size limit.
|
|
DisableForRenderFrameHostForTesting(rfh_b3);
|
|
|
|
// 4) Do a history navigation back to A1. At this point, A1 is going to have
|
|
// the same BrowsingInstance as A2. This should cause A2 to get
|
|
// evicted from the BackForwardCache due to its conflicting BrowsingInstance.
|
|
web_contents()->GetController().GoToIndex(0);
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_EQ(current_frame_host()->GetLastCommittedURL(), url_a1);
|
|
delete_rfh_a2.WaitUntilDeleted();
|
|
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kBrowsingInstanceNotSwapped},
|
|
{}, {ShouldSwapBrowsingInstance::kNo_SameSiteNavigation}, {}, FROM_HERE);
|
|
|
|
// 5) Go to A2.
|
|
web_contents()->GetController().GoForward();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
ExpectNotRestored(
|
|
{
|
|
BackForwardCacheMetrics::NotRestoredReason::
|
|
kConflictingBrowsingInstance,
|
|
},
|
|
{}, {}, {}, FROM_HERE);
|
|
}
|
|
|
|
// When same-site bfcache is disabled, we should not cache on same-site
|
|
// navigations.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithSameSiteDisabled,
|
|
DoesNotCacheOnSameSiteNavigation) {
|
|
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_a3(
|
|
embedded_test_server()->GetURL("subdomain.a.com", "/title3.html"));
|
|
|
|
// 1) Navigate to A1.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a1));
|
|
RenderFrameHostImpl* rfh_a1 = current_frame_host();
|
|
RenderFrameDeletedObserver delete_rfh_a1(rfh_a1);
|
|
int browsing_instance_id = rfh_a1->GetSiteInstance()->GetBrowsingInstanceId();
|
|
|
|
// 2) Navigate same-site and same-origin to A2.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a2));
|
|
RenderFrameHostImpl* rfh_a2 = current_frame_host();
|
|
// The BrowsingInstance shouldn't have changed.
|
|
EXPECT_EQ(browsing_instance_id,
|
|
rfh_a2->GetSiteInstance()->GetBrowsingInstanceId());
|
|
// The previous page should not be cached.
|
|
EXPECT_FALSE(rfh_a1->IsInBackForwardCache());
|
|
|
|
// 2) Navigate same-site but cross-origin to A3.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a3));
|
|
RenderFrameHostImpl* rfh_a3 = current_frame_host();
|
|
// The BrowsingInstance shouldn't have changed.
|
|
EXPECT_EQ(browsing_instance_id,
|
|
rfh_a3->GetSiteInstance()->GetBrowsingInstanceId());
|
|
// The previous page should not be cached.
|
|
EXPECT_FALSE(rfh_a2->IsInBackForwardCache());
|
|
}
|
|
|
|
// Check that during a same-RenderFrameHost cross-document navigation, the
|
|
// disabled reasons is still tracked.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithSameSiteDisabled,
|
|
DisableForRenderFrameHostPersistsAcrossNavigations) {
|
|
// This test assumes navigation from A1 to A2 will not switch
|
|
// RenderFrameHosts which is not true when BackForwardCache,
|
|
// ProactivelySwapBrowsingInstance or RenderDocument is enabled on same-site
|
|
// main frame navigations.
|
|
DCHECK(!CanSameSiteMainFrameNavigationsChangeRenderFrameHosts());
|
|
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));
|
|
RenderFrameHostImpl* rfh_a1 = current_frame_host();
|
|
RenderFrameDeletedObserver deleted_observer_rfh_a1(rfh_a1);
|
|
// Disable back-forward cache for A.
|
|
DisableForRenderFrameHostForTesting(rfh_a1);
|
|
|
|
// 2) Navigate to A2.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a2));
|
|
EXPECT_FALSE(deleted_observer_rfh_a1.deleted());
|
|
EXPECT_EQ(rfh_a1, current_frame_host());
|
|
|
|
// 3) Navigate to B3.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b3));
|
|
deleted_observer_rfh_a1.WaitUntilDeleted();
|
|
|
|
// 4) Go back to A2.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason::
|
|
kDisableForRenderFrameHostCalled},
|
|
{}, {}, {RenderFrameHostDisabledForTestingReason()},
|
|
FROM_HERE);
|
|
}
|
|
|
|
// The BackForwardCache caches same-website navigations.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, SameSiteNavigationCaching) {
|
|
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"));
|
|
|
|
// 1) Navigate to A1.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a1));
|
|
RenderFrameHostImpl* rfh_a1 = current_frame_host();
|
|
RenderFrameDeletedObserver delete_rfh_a1(rfh_a1);
|
|
int browsing_instance_id = rfh_a1->GetSiteInstance()->GetBrowsingInstanceId();
|
|
|
|
// 2) Navigate to A2.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a2));
|
|
RenderFrameHostImpl* rfh_a2 = current_frame_host();
|
|
EXPECT_NE(browsing_instance_id,
|
|
rfh_a2->GetSiteInstance()->GetBrowsingInstanceId());
|
|
EXPECT_TRUE(rfh_a1->IsInBackForwardCache());
|
|
EXPECT_NE(rfh_a1, rfh_a2);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(HighCacheSizeBackForwardCacheBrowserTest,
|
|
CanCacheMultiplesPagesOnSameDomain) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a1(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_b2(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
GURL url_a3(embedded_test_server()->GetURL("a.com", "/title2.html"));
|
|
GURL url_b4(embedded_test_server()->GetURL("b.com", "/title2.html"));
|
|
|
|
// 1) Navigate to A1.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a1));
|
|
RenderFrameHostImpl* rfh_a1 = current_frame_host();
|
|
|
|
// 2) Navigate to B2.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b2));
|
|
RenderFrameHostImpl* rfh_b2 = current_frame_host();
|
|
EXPECT_TRUE(rfh_a1->IsInBackForwardCache());
|
|
|
|
// 3) Navigate to A3.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a3));
|
|
RenderFrameHostImpl* rfh_a3 = current_frame_host();
|
|
EXPECT_TRUE(rfh_a1->IsInBackForwardCache());
|
|
EXPECT_TRUE(rfh_b2->IsInBackForwardCache());
|
|
// A1 and A3 shouldn't be treated as the same site instance.
|
|
EXPECT_NE(rfh_a1->GetSiteInstance(), rfh_a3->GetSiteInstance());
|
|
|
|
// 4) Navigate to B4.
|
|
// Make sure we can store A1 and A3 in the cache at the same time.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b4));
|
|
RenderFrameHostImpl* rfh_b4 = current_frame_host();
|
|
EXPECT_TRUE(rfh_a1->IsInBackForwardCache());
|
|
EXPECT_TRUE(rfh_b2->IsInBackForwardCache());
|
|
EXPECT_TRUE(rfh_a3->IsInBackForwardCache());
|
|
|
|
// 5) Go back to A3.
|
|
// Make sure we can restore A3, while A1 remains in the cache.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_TRUE(rfh_a1->IsInBackForwardCache());
|
|
EXPECT_TRUE(rfh_b2->IsInBackForwardCache());
|
|
EXPECT_TRUE(rfh_b4->IsInBackForwardCache());
|
|
EXPECT_EQ(rfh_a3, current_frame_host());
|
|
// B2 and B4 shouldn't be treated as the same site instance.
|
|
EXPECT_NE(rfh_b2->GetSiteInstance(), rfh_b4->GetSiteInstance());
|
|
|
|
// 6) Do a history navigation back to A1.
|
|
// Make sure we can restore A1, while coming from A3.
|
|
web_contents()->GetController().GoToIndex(0);
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_TRUE(rfh_b2->IsInBackForwardCache());
|
|
EXPECT_TRUE(rfh_b4->IsInBackForwardCache());
|
|
EXPECT_TRUE(rfh_a3->IsInBackForwardCache());
|
|
EXPECT_EQ(rfh_a1, current_frame_host());
|
|
}
|
|
|
|
class BackForwardCacheBrowserTestSkipSameSiteUnload
|
|
: public BackForwardCacheBrowserTest {
|
|
public:
|
|
BackForwardCacheBrowserTestSkipSameSiteUnload() = default;
|
|
~BackForwardCacheBrowserTestSkipSameSiteUnload() override = default;
|
|
|
|
protected:
|
|
void SetUpCommandLine(base::CommandLine* command_line) override {
|
|
skip_same_site_if_unload_exists_ = true;
|
|
BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
|
|
}
|
|
};
|
|
|
|
// We won't cache pages with unload handler on same-site navigations when
|
|
// skip_same_site_if_unload_exists is set to true.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestSkipSameSiteUnload,
|
|
SameSiteNavigationFromPageWithUnload) {
|
|
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"));
|
|
|
|
// 1) Navigate to A1 and add an unload handler.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a1));
|
|
|
|
RenderFrameHostImpl* rfh_a1 = current_frame_host();
|
|
EXPECT_TRUE(ExecJs(rfh_a1, "window.onunload = () => {} "));
|
|
|
|
// 2) Navigate to A2.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a2));
|
|
RenderFrameHostImpl* rfh_a2 = current_frame_host();
|
|
// We should not swap RFHs and A1 should not be in the back-forward cache.
|
|
EXPECT_EQ(rfh_a1, rfh_a2);
|
|
EXPECT_FALSE(rfh_a1->IsInBackForwardCache());
|
|
}
|
|
|
|
// We won't cache pages with an unload handler in a same-SiteInstance subframe
|
|
// on same-site navigations when skip_same_site_if_unload_exists is set to true.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestSkipSameSiteUnload,
|
|
SameSiteNavigationFromPageWithUnloadInSameSiteSubframe) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a1(embedded_test_server()->GetURL(
|
|
"a.com", "/cross_site_iframe_factory.html?a(b(a))"));
|
|
GURL url_a2(embedded_test_server()->GetURL("a.com", "/title2.html"));
|
|
|
|
// 1) Navigate to A1 and add an unload handler to a.com subframe.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a1));
|
|
RenderFrameHostImpl* rfh_a_main = current_frame_host();
|
|
RenderFrameHostImpl* rfh_b = rfh_a_main->child_at(0)->current_frame_host();
|
|
RenderFrameHostImpl* rfh_a_subframe =
|
|
rfh_b->child_at(0)->current_frame_host();
|
|
EXPECT_TRUE(ExecJs(rfh_a_subframe, "window.onunload = () => {} "));
|
|
|
|
// 2) Navigate to A2.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a2));
|
|
RenderFrameHostImpl* rfh_a2 = current_frame_host();
|
|
// We should not swap RFHs and A1 should not be in the back-forward cache.
|
|
EXPECT_EQ(rfh_a_main, rfh_a2);
|
|
EXPECT_FALSE(rfh_a_main->IsInBackForwardCache());
|
|
}
|
|
|
|
// We won't cache pages with an unload handler in a cross-site subframe on
|
|
// same-site navigations when skip_same_site_if_unload_exists is set to true
|
|
// iff the cross-site subframe is in the same SiteInstance as the mainframe.
|
|
IN_PROC_BROWSER_TEST_F(
|
|
BackForwardCacheBrowserTestSkipSameSiteUnload,
|
|
SameSiteNavigationFromPageWithUnloadInCrossSiteSubframe) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
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", "/title2.html"));
|
|
|
|
// 1) Navigate to A1 and add an unload handler to b.com subframe.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a1));
|
|
RenderFrameHostImpl* rfh_a1 = current_frame_host();
|
|
RenderFrameHostImpl* rfh_b = rfh_a1->child_at(0)->current_frame_host();
|
|
EXPECT_TRUE(ExecJs(rfh_b, "window.onunload = () => {} "));
|
|
EXPECT_EQ(AreStrictSiteInstancesEnabled(),
|
|
rfh_a1->GetSiteInstance() != rfh_b->GetSiteInstance());
|
|
|
|
// 2) Navigate to A2.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a2));
|
|
RenderFrameHostImpl* rfh_a2 = current_frame_host();
|
|
if (AreStrictSiteInstancesEnabled()) {
|
|
// We should swap RFH & BIs and A1 should be in the back-forward cache.
|
|
EXPECT_NE(rfh_a1, rfh_a2);
|
|
EXPECT_FALSE(rfh_a1->GetSiteInstance()->IsRelatedSiteInstance(
|
|
rfh_a2->GetSiteInstance()));
|
|
EXPECT_TRUE(rfh_a1->IsInBackForwardCache());
|
|
} else {
|
|
// We should not swap RFHs and A1 should not be in the back-forward cache.
|
|
EXPECT_EQ(rfh_a1, rfh_a2);
|
|
EXPECT_FALSE(rfh_a1->IsInBackForwardCache());
|
|
}
|
|
}
|
|
|
|
// We will cache pages with unload handler on cross-site navigations even when
|
|
// skip_same_site_if_unload_exists is set to true.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestSkipSameSiteUnload,
|
|
CrossSiteNavigationFromPageWithUnload) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a1(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_a2(embedded_test_server()->GetURL("b.com", "/title2.html"));
|
|
|
|
// 1) Navigate to A and add an unload handler.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a1));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
EXPECT_TRUE(ExecJs(rfh_a, "window.onunload = () => {} "));
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a2));
|
|
RenderFrameHostImpl* rfh_b = current_frame_host();
|
|
// We should swap RFHs and A should be in the back-forward cache.
|
|
EXPECT_NE(rfh_a, rfh_b);
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
}
|
|
|
|
// 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(BackForwardCacheBrowserTest, 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.
|
|
}
|
|
|
|
class GeolocationBackForwardCacheBrowserTest
|
|
: public BackForwardCacheBrowserTest {
|
|
protected:
|
|
GeolocationBackForwardCacheBrowserTest() : geo_override_(0.0, 0.0) {}
|
|
|
|
void SetUpCommandLine(base::CommandLine* command_line) override {
|
|
EnableFeatureAndSetParams(features::kBackForwardCache,
|
|
"geolocation_supported", "true");
|
|
|
|
BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
|
|
}
|
|
|
|
device::ScopedGeolocationOverrider geo_override_;
|
|
};
|
|
|
|
// Test that a page which has queried geolocation in the past, but have no
|
|
// active geolocation query, can be bfcached.
|
|
IN_PROC_BROWSER_TEST_F(GeolocationBackForwardCacheBrowserTest,
|
|
CacheAfterGeolocationRequest) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a(embedded_test_server()->GetURL("/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();
|
|
|
|
// Query current position, and wait for the query to complete.
|
|
EXPECT_EQ("received", EvalJs(rfh_a, R"(
|
|
new Promise(resolve => {
|
|
navigator.geolocation.getCurrentPosition(() => resolve('received'));
|
|
});
|
|
)"));
|
|
|
|
RenderFrameDeletedObserver deleted(rfh_a);
|
|
|
|
// 2) Navigate away.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
// The page has no inflight geolocation request when we navigated away,
|
|
// so it should have been cached.
|
|
EXPECT_FALSE(deleted.deleted());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
}
|
|
|
|
// Test that a page which has an inflight geolocation query can be bfcached,
|
|
// and verify that the page does not observe any geolocation while the page
|
|
// was inside bfcache.
|
|
// The test is flaky on multiple platforms: crbug.com/1033270
|
|
IN_PROC_BROWSER_TEST_F(GeolocationBackForwardCacheBrowserTest,
|
|
DISABLED_CancelGeolocationRequestInFlight) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a(embedded_test_server()->GetURL("/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();
|
|
|
|
// Continuously query current geolocation.
|
|
EXPECT_TRUE(ExecJs(rfh_a, R"(
|
|
window.longitude_log = [];
|
|
window.err_log = [];
|
|
window.wait_for_first_position = new Promise(resolve => {
|
|
navigator.geolocation.watchPosition(
|
|
pos => {
|
|
window.longitude_log.push(pos.coords.longitude);
|
|
resolve("resolved");
|
|
},
|
|
err => window.err_log.push(err)
|
|
);
|
|
})
|
|
)"));
|
|
geo_override_.UpdateLocation(0.0, 0.0);
|
|
EXPECT_EQ("resolved", EvalJs(rfh_a, "window.wait_for_first_position"));
|
|
|
|
// Pause resolving Geoposition queries to keep the request inflight.
|
|
geo_override_.Pause();
|
|
geo_override_.UpdateLocation(1.0, 1.0);
|
|
EXPECT_EQ(1u, geo_override_.GetGeolocationInstanceCount());
|
|
|
|
// 2) Navigate away.
|
|
base::RunLoop loop_until_close;
|
|
geo_override_.SetGeolocationCloseCallback(loop_until_close.QuitClosure());
|
|
|
|
RenderFrameDeletedObserver deleted(rfh_a);
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
loop_until_close.Run();
|
|
|
|
// The page has no inflight geolocation request when we navigated away,
|
|
// so it should have been cached.
|
|
EXPECT_FALSE(deleted.deleted());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
|
|
// Resume resolving Geoposition queries.
|
|
geo_override_.Resume();
|
|
|
|
// We update the location while the page is BFCached, but this location should
|
|
// not be observed.
|
|
geo_override_.UpdateLocation(2.0, 2.0);
|
|
|
|
// 3) Navigate back to A.
|
|
|
|
// The location when navigated back can be observed
|
|
geo_override_.UpdateLocation(3.0, 3.0);
|
|
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_EQ(rfh_a, current_frame_host());
|
|
EXPECT_FALSE(rfh_a->IsInBackForwardCache());
|
|
|
|
// Wait for an update after the user navigates back to A.
|
|
EXPECT_EQ("resolved", EvalJs(rfh_a, R"(
|
|
window.wait_for_position_after_resume = new Promise(resolve => {
|
|
navigator.geolocation.watchPosition(
|
|
pos => {
|
|
window.longitude_log.push(pos.coords.longitude);
|
|
resolve("resolved");
|
|
},
|
|
err => window.err_log.push(err)
|
|
);
|
|
})
|
|
)"));
|
|
|
|
EXPECT_LE(0, EvalJs(rfh_a, "longitude_log.indexOf(0.0)").ExtractInt())
|
|
<< "Geoposition before the page is put into BFCache should be visible";
|
|
EXPECT_EQ(-1, EvalJs(rfh_a, "longitude_log.indexOf(1.0)").ExtractInt())
|
|
<< "Geoposition while the page is put into BFCache should be invisible";
|
|
EXPECT_EQ(-1, EvalJs(rfh_a, "longitude_log.indexOf(2.0)").ExtractInt())
|
|
<< "Geoposition while the page is put into BFCache should be invisible";
|
|
EXPECT_LT(0, EvalJs(rfh_a, "longitude_log.indexOf(3.0)").ExtractInt())
|
|
<< "Geoposition when the page is restored from BFCache should be visible";
|
|
EXPECT_EQ(0, EvalJs(rfh_a, "err_log.length"))
|
|
<< "watchPosition API should have reported no errors";
|
|
}
|
|
|
|
// Test that documents are evicted correctly from BackForwardCache after time to
|
|
// live.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, TimedEviction) {
|
|
// Inject mock time task runner to be used in the eviction timer, so we can,
|
|
// check for the functionality we are interested before and after the time to
|
|
// live. We don't replace ThreadTaskRunnerHandle::Get to ensure that it
|
|
// doesn't affect other unrelated callsites.
|
|
scoped_refptr<base::TestMockTimeTaskRunner> task_runner =
|
|
base::MakeRefCounted<base::TestMockTimeTaskRunner>();
|
|
|
|
web_contents()->GetController().GetBackForwardCache().SetTaskRunnerForTesting(
|
|
task_runner);
|
|
|
|
base::TimeDelta time_to_live_in_back_forward_cache =
|
|
BackForwardCacheImpl::GetTimeToLiveInBackForwardCache();
|
|
// This should match the value we set in EnableFeatureAndSetParams.
|
|
EXPECT_EQ(time_to_live_in_back_forward_cache,
|
|
base::TimeDelta::FromSeconds(3600));
|
|
|
|
base::TimeDelta delta = base::TimeDelta::FromMilliseconds(1);
|
|
|
|
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));
|
|
RenderFrameHostImpl* rfh_b = current_frame_host();
|
|
|
|
// 3) Fast forward to just before eviction is due.
|
|
task_runner->FastForwardBy(time_to_live_in_back_forward_cache - delta);
|
|
|
|
// 4) Confirm A is still in BackForwardCache.
|
|
ASSERT_FALSE(delete_observer_rfh_a.deleted());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
|
|
// 5) Fast forward to when eviction is due.
|
|
task_runner->FastForwardBy(delta);
|
|
|
|
// 6) Confirm A is evicted.
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
EXPECT_EQ(current_frame_host(), rfh_b);
|
|
|
|
// 7) Go back to A.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason::kTimeout}, {},
|
|
{}, {}, FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(
|
|
BackForwardCacheBrowserTest,
|
|
DisableBackForwardCachePreventsDocumentsFromBeingCached) {
|
|
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"));
|
|
url::Origin origin_a = url::Origin::Create(url_a);
|
|
url::Origin origin_b = url::Origin::Create(url_b);
|
|
|
|
// 1) Navigate to A.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
DisableForRenderFrameHostForTesting(rfh_a);
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
|
|
// 3) Go back to A.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason::
|
|
kDisableForRenderFrameHostCalled},
|
|
{}, {}, {RenderFrameHostDisabledForTestingReason()},
|
|
FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
DisableBackForwardIsNoOpIfRfhIsGone) {
|
|
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"));
|
|
url::Origin origin_a = url::Origin::Create(url_a);
|
|
url::Origin origin_b = url::Origin::Create(url_b);
|
|
|
|
// 1) Navigate to A.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
GlobalFrameRoutingId rfh_a_id = rfh_a->GetGlobalFrameRoutingId();
|
|
DisableForRenderFrameHostForTesting(rfh_a_id);
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
|
|
// This should not die
|
|
DisableForRenderFrameHostForTesting(rfh_a_id);
|
|
|
|
// 3) Go back to A.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason::
|
|
kDisableForRenderFrameHostCalled},
|
|
{}, {}, {RenderFrameHostDisabledForTestingReason()},
|
|
FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
DisableBackForwardCacheIframe) {
|
|
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("b.com", "/title1.html"));
|
|
|
|
// 1) Navigate to A.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b);
|
|
|
|
DisableForRenderFrameHostForTesting(rfh_b);
|
|
|
|
// 2) Navigate to C. A and B are deleted.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_c));
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
delete_observer_rfh_b.WaitUntilDeleted();
|
|
|
|
// 3) Go back to A.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason::
|
|
kDisableForRenderFrameHostCalled},
|
|
{}, {}, {RenderFrameHostDisabledForTestingReason()},
|
|
FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
DisableBackForwardEvictsIfAlreadyInCache) {
|
|
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"));
|
|
url::Origin origin_a = url::Origin::Create(url_a);
|
|
url::Origin origin_b = url::Origin::Create(url_b);
|
|
|
|
// 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));
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
EXPECT_FALSE(rfh_a->is_evicted_from_back_forward_cache());
|
|
|
|
DisableForRenderFrameHostForTesting(rfh_a);
|
|
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
|
|
// 3) Go back to A.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason::
|
|
kDisableForRenderFrameHostCalled},
|
|
{}, {}, {RenderFrameHostDisabledForTestingReason()},
|
|
FROM_HERE);
|
|
}
|
|
|
|
// Confirm that same-document navigation and not history-navigation does not
|
|
// record metrics.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, MetricsNotRecorded) {
|
|
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_b2(embedded_test_server()->GetURL("b.com", "/title1.html#2"));
|
|
|
|
// 1) Navigate to A.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
// 3) Navigate to B#2 (same document navigation).
|
|
EXPECT_TRUE(NavigateToURLFromRenderer(shell(), url_b2));
|
|
|
|
// 4) Go back to B.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectOutcomeDidNotChange(FROM_HERE);
|
|
|
|
// 5) Navigate to A.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectOutcomeDidNotChange(FROM_HERE);
|
|
}
|
|
|
|
// Test for functionality of domain specific controls in back-forward cache.
|
|
class BackForwardCacheBrowserTestWithDomainControlEnabled
|
|
: public BackForwardCacheBrowserTest {
|
|
protected:
|
|
void SetUpCommandLine(base::CommandLine* command_line) override {
|
|
// Sets the allowed websites for testing, additionally adding the params
|
|
// used by BackForwardCacheBrowserTest.
|
|
std::string allowed_websites =
|
|
"https://a.allowed/back_forward_cache/, "
|
|
"https://b.allowed/back_forward_cache/allowed_path.html";
|
|
EnableFeatureAndSetParams(features::kBackForwardCache, "allowed_websites",
|
|
allowed_websites);
|
|
|
|
BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
|
|
}
|
|
};
|
|
|
|
// Check the RenderFrameHost allowed to enter the BackForwardCache are the ones
|
|
// matching with the "allowed_websites" feature params.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithDomainControlEnabled,
|
|
CachePagesWithMatchedURLs) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a(embedded_test_server()->GetURL(
|
|
"a.allowed", "/back_forward_cache/allowed_path.html"));
|
|
GURL url_b(embedded_test_server()->GetURL(
|
|
"b.allowed", "/back_forward_cache/allowed_path.html?query=bar"));
|
|
|
|
// 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));
|
|
RenderFrameHostImpl* rfh_b = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b);
|
|
|
|
// 3) Check if rfh_a is stored in back-forward cache, since it matches to
|
|
// the list of allowed urls, it should be stored.
|
|
EXPECT_FALSE(delete_observer_rfh_a.deleted());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
|
|
// 4) Now go back to the last stored page, which in our case should be A.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_EQ(rfh_a, current_frame_host());
|
|
|
|
// 5) Check if rfh_b is stored in back-forward cache, since it matches to
|
|
// the list of allowed urls, it should be stored.
|
|
EXPECT_FALSE(delete_observer_rfh_b.deleted());
|
|
EXPECT_TRUE(rfh_b->IsInBackForwardCache());
|
|
}
|
|
|
|
// We don't want to allow websites which doesn't match "allowed_websites" of
|
|
// feature params to be stored in back-forward cache.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithDomainControlEnabled,
|
|
DoNotCachePagesWithUnMatchedURLs) {
|
|
DisableCheckingMetricsForAllSites();
|
|
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a(embedded_test_server()->GetURL(
|
|
"a.disallowed", "/back_forward_cache/disallowed_path.html"));
|
|
GURL url_b(embedded_test_server()->GetURL(
|
|
"b.allowed", "/back_forward_cache/disallowed_path.html"));
|
|
GURL url_c(embedded_test_server()->GetURL(
|
|
"c.disallowed", "/back_forward_cache/disallowed_path.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));
|
|
RenderFrameHostImpl* rfh_b = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b);
|
|
|
|
// 3) Since url of A doesn't match to the the list of allowed urls it should
|
|
// not be stored in back-forward cache.
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
|
|
// 4) Navigate to C.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_c));
|
|
|
|
// 5) Since url of B doesn't match to the the list of allowed urls it should
|
|
// not be stored in back-forward cache.
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
delete_observer_rfh_b.WaitUntilDeleted();
|
|
|
|
// 6) Go back to B.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
// Nothing is recorded when the domain does not match.
|
|
ExpectOutcomeDidNotChange(FROM_HERE);
|
|
ExpectNotRestoredDidNotChange(FROM_HERE);
|
|
}
|
|
|
|
// Test the "blocked_websites" feature params in back-forward cache.
|
|
class BackForwardCacheBrowserTestWithBlockedWebsites
|
|
: public BackForwardCacheBrowserTest {
|
|
protected:
|
|
void SetUpCommandLine(base::CommandLine* command_line) override {
|
|
// Sets the blocked websites for testing, additionally adding the params
|
|
// used by BackForwardCacheBrowserTest.
|
|
std::string blocked_websites =
|
|
"https://a.blocked/, "
|
|
"https://b.blocked/";
|
|
EnableFeatureAndSetParams(features::kBackForwardCache, "blocked_websites",
|
|
blocked_websites);
|
|
|
|
BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
|
|
}
|
|
};
|
|
|
|
// Check the disallowed page isn't bfcached when it's navigated from allowed
|
|
// page.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithBlockedWebsites,
|
|
NavigateFromAllowedPageToDisallowedPage) {
|
|
// Skip checking the AllSites metrics since BackForwardCacheMetrics stop
|
|
// recording except BackForwardCache.AllSites.* metrics when the target URL is
|
|
// disallowed by allowed_websites or blocked_websites.
|
|
DisableCheckingMetricsForAllSites();
|
|
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a(embedded_test_server()->GetURL(
|
|
"a.allowed", "/back_forward_cache/allowed_path.html"));
|
|
GURL url_b(embedded_test_server()->GetURL(
|
|
"b.blocked", "/back_forward_cache/disallowed_path.html"));
|
|
|
|
// 1) Navigate to A.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
RenderFrameHostImpl* rfh_b = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b);
|
|
|
|
// 3) Check if rfh_a is stored in back-forward cache, since it doesn't match
|
|
// to the blocked_websites, and allowed_websites are empty, so it should
|
|
// be stored.
|
|
EXPECT_FALSE(delete_observer_rfh_a.deleted());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
|
|
// 4) Now go back to the last stored page, which in our case should be A.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_EQ(rfh_a, current_frame_host());
|
|
ExpectRestored(FROM_HERE);
|
|
|
|
// 5) Check if rfh_b is not stored in back-forward cache, since it matches to
|
|
// the blocked_websites.
|
|
delete_observer_rfh_b.WaitUntilDeleted();
|
|
EXPECT_TRUE(delete_observer_rfh_b.deleted());
|
|
|
|
// 6) Go forward to B. B should not restored from the back-forward cache.
|
|
web_contents()->GetController().GoForward();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
// Nothing is recorded since B is disallowed.
|
|
ExpectOutcomeDidNotChange(FROM_HERE);
|
|
ExpectNotRestoredDidNotChange(FROM_HERE);
|
|
}
|
|
|
|
// Check the allowed page is bfcached when it's navigated from disallowed
|
|
// page.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithBlockedWebsites,
|
|
NavigateFromDisallowedPageToAllowedPage) {
|
|
// Skip checking the AllSites metrics since BackForwardCacheMetrics stop
|
|
// recording except BackForwardCache.AllSites.* metrics when the target URL is
|
|
// disallowed by allowed_websites or blocked_websites.
|
|
DisableCheckingMetricsForAllSites();
|
|
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a(embedded_test_server()->GetURL(
|
|
"a.blocked", "/back_forward_cache/disallowed_path.html"));
|
|
GURL url_b(embedded_test_server()->GetURL(
|
|
"b.allowed", "/back_forward_cache/allowed_path.html"));
|
|
|
|
// 1) Navigate to A.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
RenderFrameHostImpl* rfh_b = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b);
|
|
|
|
// 3) Check if rfh_a is not stored in back-forward cache, since it matches to
|
|
// the blocked_websites.
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
EXPECT_TRUE(delete_observer_rfh_a.deleted());
|
|
|
|
// 4) Now go back to url_a which is not bfcached.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
// Nothing is recorded since A is disallowed.
|
|
ExpectOutcomeDidNotChange(FROM_HERE);
|
|
ExpectNotRestoredDidNotChange(FROM_HERE);
|
|
|
|
// 5) Check if rfh_b is stored in back-forward cache, since it doesn't match
|
|
// to the blocked_websites, and allowed_websites are empty, so it should
|
|
// be stored.
|
|
EXPECT_FALSE(delete_observer_rfh_b.deleted());
|
|
EXPECT_TRUE(rfh_b->IsInBackForwardCache());
|
|
|
|
// 6) Go forward to url_b which is bfcached.
|
|
web_contents()->GetController().GoForward();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectRestored(FROM_HERE);
|
|
}
|
|
|
|
// Test BackForwardCache::IsAllowed() with several allowed_websites URL
|
|
// patterns.
|
|
class BackForwardCacheBrowserTestForAllowedWebsitesUrlPatterns
|
|
: public BackForwardCacheBrowserTest {
|
|
protected:
|
|
void SetUpCommandLine(base::CommandLine* command_line) override {
|
|
// Sets the allowed websites for testing, additionally adding the params
|
|
// used by BackForwardCacheBrowserTest.
|
|
std::string allowed_websites =
|
|
"https://a.com/,"
|
|
"https://b.com/path,"
|
|
"https://c.com/path/";
|
|
EnableFeatureAndSetParams(features::kBackForwardCache, "allowed_websites",
|
|
allowed_websites);
|
|
|
|
BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
|
|
}
|
|
};
|
|
|
|
// Check if the URLs are allowed when allowed_websites are specified.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestForAllowedWebsitesUrlPatterns,
|
|
AllowedWebsitesUrlPatterns) {
|
|
BackForwardCacheImpl& bfcache =
|
|
web_contents()->GetController().GetBackForwardCache();
|
|
|
|
// Doesn't match with any allowed_websites.
|
|
EXPECT_FALSE(bfcache.IsAllowed(GURL("https://a.org/")));
|
|
|
|
// Exact match with https://a.com/.
|
|
EXPECT_TRUE(bfcache.IsAllowed(GURL("https://a.com/")));
|
|
EXPECT_TRUE(bfcache.IsAllowed(GURL("https://a.com")));
|
|
|
|
// Match with https://a.com/ since we don't take into account the difference
|
|
// on port number.
|
|
EXPECT_TRUE(bfcache.IsAllowed(GURL("https://a.com:123/")));
|
|
|
|
// Match with https://a.com/ since we don't take into account the difference
|
|
// on query.
|
|
EXPECT_TRUE(bfcache.IsAllowed(GURL("https://a.com:123/?x=1")));
|
|
|
|
// Match with https://a.com/ since we don't take into account the difference
|
|
// on scheme.
|
|
EXPECT_TRUE(bfcache.IsAllowed(GURL("http://a.com/")));
|
|
|
|
// Match with https://a.com/ since we are checking the prefix on path.
|
|
EXPECT_TRUE(bfcache.IsAllowed(GURL("https://a.com/path")));
|
|
|
|
// Doesn't match with https://a.com/ since the host doesn't match with a.com.
|
|
EXPECT_FALSE(bfcache.IsAllowed(GURL("https://prefix.a.com/")));
|
|
|
|
// Doesn't match with https://b.com/path since the path prefix doesn't match.
|
|
EXPECT_FALSE(bfcache.IsAllowed(GURL("https://b.com/")));
|
|
|
|
// Exact match with https://b.com/path.
|
|
EXPECT_TRUE(bfcache.IsAllowed(GURL("https://b.com/path")));
|
|
|
|
// Match with https://b.com/path since we are checking the prefix on path.
|
|
EXPECT_TRUE(bfcache.IsAllowed(GURL("https://b.com/path/")));
|
|
EXPECT_TRUE(bfcache.IsAllowed(GURL("https://b.com/path_abc")));
|
|
EXPECT_TRUE(bfcache.IsAllowed(GURL("https://b.com/path_abc?x=1")));
|
|
|
|
// Doesn't match with https://c.com/path/ since the path prefix doesn't match.
|
|
EXPECT_FALSE(bfcache.IsAllowed(GURL("https://c.com/path")));
|
|
}
|
|
|
|
// Test BackForwardCache::IsAllowed() with several blocked_websites URL
|
|
// patterns.
|
|
class BackForwardCacheBrowserTestForBlockedWebsitesUrlPatterns
|
|
: public BackForwardCacheBrowserTest {
|
|
protected:
|
|
void SetUpCommandLine(base::CommandLine* command_line) override {
|
|
// Sets the blocked websites for testing, additionally adding the params
|
|
// used by BackForwardCacheBrowserTest.
|
|
std::string blocked_websites =
|
|
"https://a.com/,"
|
|
"https://b.com/path,"
|
|
"https://c.com/path/";
|
|
EnableFeatureAndSetParams(features::kBackForwardCache, "blocked_websites",
|
|
blocked_websites);
|
|
|
|
BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
|
|
}
|
|
};
|
|
|
|
// Check if the URLs are allowed when blocked_websites are specified.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestForBlockedWebsitesUrlPatterns,
|
|
BlockedWebsitesUrlPatterns) {
|
|
BackForwardCacheImpl& bfcache =
|
|
web_contents()->GetController().GetBackForwardCache();
|
|
|
|
// Doesn't match with any blocked_websites.
|
|
EXPECT_TRUE(bfcache.IsAllowed(GURL("https://a.org/")));
|
|
|
|
// Exact match with https://a.com/.
|
|
EXPECT_FALSE(bfcache.IsAllowed(GURL("https://a.com/")));
|
|
EXPECT_FALSE(bfcache.IsAllowed(GURL("https://a.com")));
|
|
|
|
// Match with https://a.com/ since we don't take into account the difference
|
|
// on port number.
|
|
EXPECT_FALSE(bfcache.IsAllowed(GURL("https://a.com:123/")));
|
|
|
|
// Match with https://a.com/ since we don't take into account the difference
|
|
// on query.
|
|
EXPECT_FALSE(bfcache.IsAllowed(GURL("https://a.com:123/?x=1")));
|
|
|
|
// Match with https://a.com/ since we don't take into account the difference
|
|
// on scheme.
|
|
EXPECT_FALSE(bfcache.IsAllowed(GURL("http://a.com/")));
|
|
|
|
// Match with https://a.com/ since we are checking the prefix on path.
|
|
EXPECT_FALSE(bfcache.IsAllowed(GURL("https://a.com/path")));
|
|
|
|
// Doesn't match with https://a.com/ since the host doesn't match with a.com.
|
|
EXPECT_TRUE(bfcache.IsAllowed(GURL("https://prefix.a.com/")));
|
|
|
|
// Doesn't match with https://b.com/path since the path prefix doesn't match.
|
|
EXPECT_TRUE(bfcache.IsAllowed(GURL("https://b.com/")));
|
|
|
|
// Exact match with https://b.com/path.
|
|
EXPECT_FALSE(bfcache.IsAllowed(GURL("https://b.com/path")));
|
|
|
|
// Match with https://b.com/path since we are checking the prefix on path.
|
|
EXPECT_FALSE(bfcache.IsAllowed(GURL("https://b.com/path/")));
|
|
EXPECT_FALSE(bfcache.IsAllowed(GURL("https://b.com/path_abc")));
|
|
EXPECT_FALSE(bfcache.IsAllowed(GURL("https://b.com/path_abc?x=1")));
|
|
|
|
// Doesn't match with https://c.com/path/ since the path prefix doesn't match.
|
|
EXPECT_TRUE(bfcache.IsAllowed(GURL("https://c.com/path")));
|
|
}
|
|
|
|
// Test BackForwardCache::IsAllowed() with several allowed_websites and
|
|
// blocked_websites URL patterns.
|
|
class BackForwardCacheBrowserTestForWebsitesUrlPatterns
|
|
: public BackForwardCacheBrowserTest {
|
|
protected:
|
|
void SetUpCommandLine(base::CommandLine* command_line) override {
|
|
// Sets the allowed websites for testing, additionally adding the params
|
|
// used by BackForwardCacheBrowserTest.
|
|
std::string allowed_websites = "https://a.com/";
|
|
EnableFeatureAndSetParams(features::kBackForwardCache, "allowed_websites",
|
|
allowed_websites);
|
|
|
|
// Sets the blocked websites for testing, additionally adding the params
|
|
// used by BackForwardCacheBrowserTest.
|
|
std::string blocked_websites = "https://a.com/";
|
|
EnableFeatureAndSetParams(features::kBackForwardCache, "blocked_websites",
|
|
blocked_websites);
|
|
|
|
BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
|
|
}
|
|
};
|
|
|
|
// Check if the URLs are allowed when allowed_websites and blocked_websites are
|
|
// specified.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestForWebsitesUrlPatterns,
|
|
WebsitesUrlPatterns) {
|
|
BackForwardCacheImpl& bfcache =
|
|
web_contents()->GetController().GetBackForwardCache();
|
|
|
|
// https://a.com/ is not allowed since blocked_websites will be prioritized
|
|
// when the same website is specified in allowed_websites and
|
|
// blocked_websites.
|
|
EXPECT_FALSE(bfcache.IsAllowed(GURL("https://a.com/")));
|
|
EXPECT_FALSE(bfcache.IsAllowed(GURL("https://a.com")));
|
|
}
|
|
|
|
// Check that if WebPreferences was changed while a page was bfcached, it will
|
|
// get up-to-date WebPreferences when it was restored.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, WebPreferences) {
|
|
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));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
int browsing_instance_id = rfh_a->GetSiteInstance()->GetBrowsingInstanceId();
|
|
|
|
// A should prefer light color scheme (which is the default).
|
|
EXPECT_EQ(
|
|
true,
|
|
EvalJs(web_contents(),
|
|
"window.matchMedia('(prefers-color-scheme: light)').matches"));
|
|
|
|
// 2) Navigate to B. A should be stored in the back-forward cache.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
RenderFrameHostImpl* rfh_b = current_frame_host();
|
|
EXPECT_NE(browsing_instance_id,
|
|
rfh_b->GetSiteInstance()->GetBrowsingInstanceId());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
EXPECT_NE(rfh_a, rfh_b);
|
|
|
|
blink::web_pref::WebPreferences prefs =
|
|
web_contents()->GetOrCreateWebPreferences();
|
|
prefs.preferred_color_scheme = blink::mojom::PreferredColorScheme::kDark;
|
|
web_contents()->SetWebPreferences(prefs);
|
|
|
|
// 3) Set WebPreferences to prefer dark color scheme.
|
|
EXPECT_EQ(
|
|
true,
|
|
EvalJs(web_contents(),
|
|
"window.matchMedia('(prefers-color-scheme: dark)').matches"));
|
|
|
|
// 4) Go back to A, which should also prefer the dark color scheme now.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_EQ(rfh_a, current_frame_host());
|
|
|
|
EXPECT_EQ(
|
|
true,
|
|
EvalJs(web_contents(),
|
|
"window.matchMedia('(prefers-color-scheme: dark)').matches"));
|
|
}
|
|
|
|
// Check the BackForwardCache is disabled when there is a nested WebContents
|
|
// inside a page.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, NestedWebContents) {
|
|
// 1) Navigate to a page.
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url(embedded_test_server()->GetURL("a.com", "/page_with_iframe.html"));
|
|
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameHostImpl* child = rfh_a->child_at(0)->current_frame_host();
|
|
EXPECT_TRUE(child);
|
|
|
|
// Create and attach an inner WebContents.
|
|
CreateAndAttachInnerContents(child);
|
|
RenderFrameDeletedObserver deleted(rfh_a);
|
|
|
|
// 2) Navigate away.
|
|
shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
// The page has an inner WebContents so it should be deleted.
|
|
deleted.WaitUntilDeleted();
|
|
|
|
// 3) Go back to the page with an inner WebContents.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kHaveInnerContents}, {}, {},
|
|
{}, FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, WebBluetooth) {
|
|
// The test requires a mock Bluetooth adapter to perform a
|
|
// WebBluetooth API call. To avoid conflicts with the default Bluetooth
|
|
// adapter, e.g. Windows adapter, which is configured during Bluetooth
|
|
// initialization, the mock adapter is configured in SetUp().
|
|
|
|
// WebBluetooth requires HTTPS.
|
|
ASSERT_TRUE(CreateHttpsServer()->Start());
|
|
GURL url(https_server()->GetURL("a.com", "/back_forward_cache/empty.html"));
|
|
|
|
ASSERT_TRUE(NavigateToURL(web_contents(), url));
|
|
BackForwardCacheDisabledTester tester;
|
|
|
|
EXPECT_EQ("device not found", EvalJs(current_frame_host(), R"(
|
|
new Promise(resolve => {
|
|
navigator.bluetooth.requestDevice({
|
|
filters: [
|
|
{ services: [0x1802, 0x1803] },
|
|
]
|
|
})
|
|
.then(() => resolve("device found"))
|
|
.catch(() => resolve("device not found"))
|
|
});
|
|
)"));
|
|
auto reason = BackForwardCacheDisable::DisabledReason(
|
|
BackForwardCacheDisable::DisabledReasonId::kWebBluetooth);
|
|
EXPECT_TRUE(tester.IsDisabledForFrameWithReason(
|
|
current_frame_host()->GetProcess()->GetID(),
|
|
current_frame_host()->GetRoutingID(), reason));
|
|
|
|
ASSERT_TRUE(NavigateToURL(web_contents(),
|
|
https_server()->GetURL("b.com", "/title1.html")));
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason::
|
|
kDisableForRenderFrameHostCalled},
|
|
{}, {}, {reason}, FROM_HERE);
|
|
}
|
|
|
|
// Check the BackForwardCache is disabled when the WebUSB feature is used.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, WebUSB) {
|
|
// WebUSB requires HTTPS.
|
|
ASSERT_TRUE(CreateHttpsServer()->Start());
|
|
|
|
auto web_usb_reason = BackForwardCacheDisable::DisabledReason(
|
|
BackForwardCacheDisable::DisabledReasonId::kWebUSB);
|
|
|
|
// Main document.
|
|
{
|
|
content::BackForwardCacheDisabledTester tester;
|
|
GURL url(https_server()->GetURL("a.com", "/title1.html"));
|
|
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
|
|
EXPECT_FALSE(current_frame_host()->IsBackForwardCacheDisabled());
|
|
EXPECT_EQ("Found 0 devices", content::EvalJs(current_frame_host(), R"(
|
|
new Promise(async resolve => {
|
|
let devices = await navigator.usb.getDevices();
|
|
resolve("Found " + devices.length + " devices");
|
|
});
|
|
)"));
|
|
EXPECT_TRUE(current_frame_host()->IsBackForwardCacheDisabled());
|
|
EXPECT_TRUE(tester.IsDisabledForFrameWithReason(
|
|
current_frame_host()->GetProcess()->GetID(),
|
|
current_frame_host()->GetRoutingID(), web_usb_reason));
|
|
}
|
|
|
|
// Nested document.
|
|
{
|
|
content::BackForwardCacheDisabledTester tester;
|
|
GURL url(https_server()->GetURL("c.com",
|
|
"/cross_site_iframe_factory.html?c(d)"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
RenderFrameHostImpl* rfh_c = current_frame_host();
|
|
RenderFrameHostImpl* rfh_d = rfh_c->child_at(0)->current_frame_host();
|
|
|
|
EXPECT_FALSE(rfh_c->IsBackForwardCacheDisabled());
|
|
EXPECT_FALSE(rfh_d->IsBackForwardCacheDisabled());
|
|
EXPECT_EQ("Found 0 devices", content::EvalJs(rfh_c, R"(
|
|
new Promise(async resolve => {
|
|
let devices = await navigator.usb.getDevices();
|
|
resolve("Found " + devices.length + " devices");
|
|
});
|
|
)"));
|
|
EXPECT_TRUE(rfh_c->IsBackForwardCacheDisabled());
|
|
EXPECT_FALSE(rfh_d->IsBackForwardCacheDisabled());
|
|
EXPECT_TRUE(tester.IsDisabledForFrameWithReason(
|
|
rfh_c->GetProcess()->GetID(), rfh_c->GetRoutingID(), web_usb_reason));
|
|
}
|
|
|
|
// Worker.
|
|
{
|
|
content::BackForwardCacheDisabledTester tester;
|
|
GURL url(https_server()->GetURL("e.com", "/title1.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
EXPECT_FALSE(current_frame_host()->IsBackForwardCacheDisabled());
|
|
EXPECT_EQ("Found 0 devices", content::EvalJs(current_frame_host(), R"(
|
|
new Promise(async resolve => {
|
|
const worker = new Worker("/back_forward_cache/webusb/worker.js");
|
|
worker.onmessage = message => resolve(message.data);
|
|
worker.postMessage("Run");
|
|
});
|
|
)"));
|
|
EXPECT_TRUE(current_frame_host()->IsBackForwardCacheDisabled());
|
|
EXPECT_TRUE(tester.IsDisabledForFrameWithReason(
|
|
current_frame_host()->GetProcess()->GetID(),
|
|
current_frame_host()->GetRoutingID(), web_usb_reason));
|
|
}
|
|
|
|
// Nested worker.
|
|
{
|
|
content::BackForwardCacheDisabledTester tester;
|
|
GURL url(https_server()->GetURL("f.com", "/title1.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
EXPECT_FALSE(current_frame_host()->IsBackForwardCacheDisabled());
|
|
EXPECT_EQ("Found 0 devices", content::EvalJs(current_frame_host(), R"(
|
|
new Promise(async resolve => {
|
|
const worker = new Worker(
|
|
"/back_forward_cache/webusb/nested-worker.js");
|
|
worker.onmessage = message => resolve(message.data);
|
|
worker.postMessage("Run");
|
|
});
|
|
)"));
|
|
EXPECT_TRUE(current_frame_host()->IsBackForwardCacheDisabled());
|
|
EXPECT_TRUE(tester.IsDisabledForFrameWithReason(
|
|
current_frame_host()->GetProcess()->GetID(),
|
|
current_frame_host()->GetRoutingID(), web_usb_reason));
|
|
}
|
|
}
|
|
|
|
#if !defined(OS_ANDROID)
|
|
// Check that the back-forward cache is disabled when the Serial API is used.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, Serial) {
|
|
// Serial API requires HTTPS.
|
|
ASSERT_TRUE(CreateHttpsServer()->Start());
|
|
|
|
auto serial_reason = BackForwardCacheDisable::DisabledReason(
|
|
BackForwardCacheDisable::DisabledReasonId::kSerial);
|
|
// Main document.
|
|
{
|
|
content::BackForwardCacheDisabledTester tester;
|
|
GURL url(https_server()->GetURL("a.com", "/title1.html"));
|
|
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
|
|
EXPECT_FALSE(current_frame_host()->IsBackForwardCacheDisabled());
|
|
EXPECT_EQ("Found 0 ports", content::EvalJs(current_frame_host(), R"(
|
|
new Promise(async resolve => {
|
|
let ports = await navigator.serial.getPorts();
|
|
resolve("Found " + ports.length + " ports");
|
|
});
|
|
)"));
|
|
EXPECT_TRUE(current_frame_host()->IsBackForwardCacheDisabled());
|
|
EXPECT_TRUE(tester.IsDisabledForFrameWithReason(
|
|
current_frame_host()->GetProcess()->GetID(),
|
|
current_frame_host()->GetRoutingID(), serial_reason));
|
|
}
|
|
|
|
// Nested document.
|
|
{
|
|
content::BackForwardCacheDisabledTester tester;
|
|
GURL url(https_server()->GetURL("c.com",
|
|
"/cross_site_iframe_factory.html?c(d)"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
RenderFrameHostImpl* rfh_c = current_frame_host();
|
|
RenderFrameHostImpl* rfh_d = rfh_c->child_at(0)->current_frame_host();
|
|
|
|
EXPECT_FALSE(rfh_c->IsBackForwardCacheDisabled());
|
|
EXPECT_FALSE(rfh_d->IsBackForwardCacheDisabled());
|
|
EXPECT_EQ("Found 0 ports", content::EvalJs(rfh_c, R"(
|
|
new Promise(async resolve => {
|
|
let ports = await navigator.serial.getPorts();
|
|
resolve("Found " + ports.length + " ports");
|
|
});
|
|
)"));
|
|
EXPECT_TRUE(rfh_c->IsBackForwardCacheDisabled());
|
|
EXPECT_FALSE(rfh_d->IsBackForwardCacheDisabled());
|
|
EXPECT_TRUE(tester.IsDisabledForFrameWithReason(
|
|
rfh_c->GetProcess()->GetID(), rfh_c->GetRoutingID(), serial_reason));
|
|
}
|
|
|
|
// Worker.
|
|
{
|
|
content::BackForwardCacheDisabledTester tester;
|
|
GURL url(https_server()->GetURL("e.com", "/title1.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
EXPECT_FALSE(current_frame_host()->IsBackForwardCacheDisabled());
|
|
EXPECT_EQ("Found 0 ports", content::EvalJs(current_frame_host(), R"(
|
|
new Promise(async resolve => {
|
|
const worker = new Worker("/back_forward_cache/serial/worker.js");
|
|
worker.onmessage = message => resolve(message.data);
|
|
worker.postMessage("Run");
|
|
});
|
|
)"));
|
|
EXPECT_TRUE(current_frame_host()->IsBackForwardCacheDisabled());
|
|
EXPECT_TRUE(tester.IsDisabledForFrameWithReason(
|
|
current_frame_host()->GetProcess()->GetID(),
|
|
current_frame_host()->GetRoutingID(), serial_reason));
|
|
}
|
|
|
|
// Nested worker.
|
|
{
|
|
content::BackForwardCacheDisabledTester tester;
|
|
GURL url(https_server()->GetURL("f.com", "/title1.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
EXPECT_FALSE(current_frame_host()->IsBackForwardCacheDisabled());
|
|
EXPECT_EQ("Found 0 ports", content::EvalJs(current_frame_host(), R"(
|
|
new Promise(async resolve => {
|
|
const worker = new Worker(
|
|
"/back_forward_cache/serial/nested-worker.js");
|
|
worker.onmessage = message => resolve(message.data);
|
|
worker.postMessage("Run");
|
|
});
|
|
)"));
|
|
EXPECT_TRUE(current_frame_host()->IsBackForwardCacheDisabled());
|
|
EXPECT_TRUE(tester.IsDisabledForFrameWithReason(
|
|
current_frame_host()->GetProcess()->GetID(),
|
|
current_frame_host()->GetRoutingID(), serial_reason));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, Encoding) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a(embedded_test_server()->GetURL(
|
|
"a.com", "/back_forward_cache/charset_windows-1250.html"));
|
|
GURL url_b(embedded_test_server()->GetURL(
|
|
"b.com", "/back_forward_cache/charset_utf-8.html"));
|
|
url::Origin origin_a = url::Origin::Create(url_a);
|
|
url::Origin origin_b = url::Origin::Create(url_b);
|
|
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
EXPECT_EQ(web_contents()->GetEncoding(), "windows-1250");
|
|
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
EXPECT_EQ(web_contents()->GetEncoding(), "UTF-8");
|
|
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_EQ(web_contents()->GetEncoding(), "windows-1250");
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, RestoreWhilePendingCommit) {
|
|
net::test_server::ControllableHttpResponse response(embedded_test_server(),
|
|
"/main_document");
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url1(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url2(embedded_test_server()->GetURL("b.com", "/title2.html"));
|
|
GURL url3(embedded_test_server()->GetURL("c.com", "/main_document"));
|
|
|
|
// Load a page and navigate away from it, so it is stored in the back-forward
|
|
// cache.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url1));
|
|
RenderFrameHost* rfh1 = current_frame_host();
|
|
EXPECT_TRUE(NavigateToURL(shell(), url2));
|
|
|
|
// Try to navigate to a new page, but leave it in a pending state.
|
|
shell()->LoadURL(url3);
|
|
response.WaitForRequest();
|
|
|
|
// Navigate back and restore page from the cache, cancelling the previous
|
|
// navigation.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_EQ(rfh1, current_frame_host());
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
DoesNotCacheCrossSiteHttpPost) {
|
|
SetupCrossSiteRedirector(embedded_test_server());
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
// Note we do a cross-site post because same-site navigations of any kind
|
|
// aren't cached currently.
|
|
GURL form_url(embedded_test_server()->GetURL(
|
|
"a.com", "/form_that_posts_cross_site.html"));
|
|
GURL redirect_target_url(embedded_test_server()->GetURL("x.com", "/echoall"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// Navigate to the page with form that posts via 307 redirection to
|
|
// |redirect_target_url| (cross-site from |form_url|).
|
|
EXPECT_TRUE(NavigateToURL(shell(), form_url));
|
|
|
|
// Submit the form.
|
|
TestNavigationObserver form_post_observer(shell()->web_contents(), 1);
|
|
EXPECT_TRUE(ExecJs(shell(), "document.getElementById('text-form').submit()"));
|
|
form_post_observer.Wait();
|
|
|
|
// Verify that we arrived at the expected, redirected location.
|
|
EXPECT_EQ(redirect_target_url,
|
|
shell()->web_contents()->GetLastCommittedURL());
|
|
RenderFrameDeletedObserver delete_observer_rfh(current_frame_host());
|
|
|
|
// Navigate away. |redirect_target_url|'s page should not be cached.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
delete_observer_rfh.WaitUntilDeleted();
|
|
}
|
|
|
|
namespace {
|
|
|
|
const char kResponseWithNoCache[] =
|
|
"HTTP/1.1 200 OK\r\n"
|
|
"Content-Type: text/html; charset=utf-8\r\n"
|
|
"Cache-Control: no-store\r\n"
|
|
"\r\n"
|
|
"The server speaks HTTP!";
|
|
|
|
} // namespace
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
MainFrameWithNoStoreNotCached) {
|
|
net::test_server::ControllableHttpResponse response(embedded_test_server(),
|
|
"/main_document");
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
GURL url_a(embedded_test_server()->GetURL("a.com", "/main_document"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// 1. Load the document and specify no-store for the main resource.
|
|
TestNavigationObserver observer(web_contents());
|
|
shell()->LoadURL(url_a);
|
|
response.WaitForRequest();
|
|
response.Send(kResponseWithNoCache);
|
|
response.Done();
|
|
observer.Wait();
|
|
|
|
// 2. Navigate away and expect frame to be deleted.
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(current_frame_host());
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
}
|
|
|
|
// Disabled for being flaky. See crbug.com/1116190.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
DISABLED_SubframeWithNoStoreCached) {
|
|
// iframe will try to load title1.html.
|
|
net::test_server::ControllableHttpResponse response(embedded_test_server(),
|
|
"/title1.html");
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
GURL url_a(embedded_test_server()->GetURL("a.com", "/page_with_iframe.html"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title2.html"));
|
|
|
|
// 1) Load the document and specify no-store for the main resource.
|
|
TestNavigationObserver observer(web_contents());
|
|
shell()->LoadURL(url_a);
|
|
response.WaitForRequest();
|
|
response.Send(kResponseWithNoCache);
|
|
response.Done();
|
|
observer.Wait();
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(current_frame_host());
|
|
|
|
// 2) Navigate away.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
// 3) Navigate back and expect everything to be restored.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_FALSE(delete_observer_rfh_a.deleted());
|
|
EXPECT_EQ(rfh_a, current_frame_host());
|
|
}
|
|
|
|
// On windows, the expected value is off by ~20ms. In order to get the
|
|
// feature out to canary, the test is disabled for WIN.
|
|
// TODO(crbug.com/1022191): Fix this for Win.
|
|
#if defined(OS_WIN)
|
|
#define MAYBE_NavigationStart DISABLED_NavigationStart
|
|
#else
|
|
#define MAYBE_NavigationStart NavigationStart
|
|
#endif
|
|
// Make sure we are exposing the duration between back navigation's
|
|
// navigationStart and the page's original navigationStart through pageshow
|
|
// event's timeStamp, and that we aren't modifying
|
|
// performance.timing.navigationStart.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, MAYBE_NavigationStart) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a(embedded_test_server()->GetURL(
|
|
"a.com", "/back_forward_cache/record_navigation_start_time_stamp.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);
|
|
|
|
double initial_page_show_time_stamp =
|
|
EvalJs(shell(), "window.initialPageShowTimeStamp").ExtractDouble();
|
|
EXPECT_DOUBLE_EQ(
|
|
initial_page_show_time_stamp,
|
|
EvalJs(shell(), "window.latestPageShowTimeStamp").ExtractDouble());
|
|
double initial_navigation_start =
|
|
EvalJs(shell(), "window.initialNavigationStart").ExtractDouble();
|
|
|
|
// 2) Navigate to B. A should be in the back forward cache.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
EXPECT_FALSE(delete_observer_rfh_a.deleted());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
|
|
// 3) Navigate back and expect everything to be restored.
|
|
NavigationHandleObserver observer(web_contents(), url_a);
|
|
base::TimeTicks time_before_navigation = base::TimeTicks::Now();
|
|
double js_time_before_navigation =
|
|
EvalJs(shell(), "performance.now()").ExtractDouble();
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
base::TimeTicks time_after_navigation = base::TimeTicks::Now();
|
|
double js_time_after_navigation =
|
|
EvalJs(shell(), "performance.now()").ExtractDouble();
|
|
|
|
// The navigation start time should be between the time we saved just before
|
|
// calling GoBack() and the time we saved just after calling GoBack().
|
|
base::TimeTicks back_navigation_start = observer.navigation_start();
|
|
EXPECT_LT(time_before_navigation, back_navigation_start);
|
|
EXPECT_GT(time_after_navigation, back_navigation_start);
|
|
|
|
// Check JS values. window.initialNavigationStart should not change.
|
|
EXPECT_DOUBLE_EQ(
|
|
initial_navigation_start,
|
|
EvalJs(shell(), "window.initialNavigationStart").ExtractDouble());
|
|
// performance.timing.navigationStart should not change.
|
|
EXPECT_DOUBLE_EQ(
|
|
initial_navigation_start,
|
|
EvalJs(shell(), "performance.timing.navigationStart").ExtractDouble());
|
|
// window.initialPageShowTimeStamp should not change.
|
|
EXPECT_DOUBLE_EQ(
|
|
initial_page_show_time_stamp,
|
|
EvalJs(shell(), "window.initialPageShowTimeStamp").ExtractDouble());
|
|
// window.latestPageShowTimeStamp should be updated with the timestamp of the
|
|
// last pageshow event, which occurs after the page is restored. This should
|
|
// be greater than the initial pageshow event's timestamp.
|
|
double latest_page_show_time_stamp =
|
|
EvalJs(shell(), "window.latestPageShowTimeStamp").ExtractDouble();
|
|
EXPECT_LT(initial_page_show_time_stamp, latest_page_show_time_stamp);
|
|
|
|
// |latest_page_show_time_stamp| should be the duration between initial
|
|
// navigation start and |back_navigation_start|. Note that since
|
|
// performance.timing.navigationStart returns a 64-bit integer instead of
|
|
// double, we might be losing somewhere between 0 to 1 milliseconds of
|
|
// precision, hence the usage of EXPECT_NEAR.
|
|
EXPECT_NEAR(
|
|
(back_navigation_start - base::TimeTicks::UnixEpoch()).InMillisecondsF(),
|
|
latest_page_show_time_stamp + initial_navigation_start, 1.0);
|
|
// Expect that the back navigation start value calculated from the JS results
|
|
// are between time taken before & after navigation, just like
|
|
// |before_navigation_start|.
|
|
EXPECT_LT(js_time_before_navigation, latest_page_show_time_stamp);
|
|
EXPECT_GT(js_time_after_navigation, latest_page_show_time_stamp);
|
|
}
|
|
|
|
// 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.
|
|
IN_PROC_BROWSER_TEST_F(
|
|
BackForwardCacheBrowserTest,
|
|
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.
|
|
EXPECT_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.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
ASSERT_FALSE(delete_observer_rfh_a.deleted());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
WaitForFirstVisuallyNonEmptyPaint(shell()->web_contents());
|
|
|
|
// 3) Navigate to back to A.
|
|
FirstVisuallyNonEmptyPaintObserver observer(web_contents());
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->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());
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
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"));
|
|
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
WaitForFirstVisuallyNonEmptyPaint(web_contents());
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
EXPECT_EQ(web_contents()->GetThemeColor(), 0xFFFF0000u);
|
|
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
WaitForFirstVisuallyNonEmptyPaint(web_contents());
|
|
ASSERT_FALSE(delete_observer_rfh_a.deleted());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
EXPECT_EQ(web_contents()->GetThemeColor(), absl::nullopt);
|
|
|
|
ThemeColorObserver observer(web_contents());
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_TRUE(observer.did_fire());
|
|
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.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_EQ(rfh_a, current_frame_host());
|
|
ExpectRestored(FROM_HERE);
|
|
EXPECT_EQ(web_contents()->GetContentsMimeType(), "text/html");
|
|
}
|
|
|
|
// Check that an audio suspends when the page goes to the cache and can resume
|
|
// after restored.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, AudioSuspendAndResume) {
|
|
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();
|
|
EXPECT_TRUE(ExecJs(rfh_a, R"(
|
|
var audio = document.createElement('audio');
|
|
document.body.appendChild(audio);
|
|
|
|
audio.testObserverEvents = [];
|
|
let event_list = [
|
|
'canplaythrough',
|
|
'pause',
|
|
'play',
|
|
'error',
|
|
];
|
|
for (event_name of event_list) {
|
|
let result = event_name;
|
|
audio.addEventListener(event_name, event => {
|
|
document.title = result;
|
|
audio.testObserverEvents.push(result);
|
|
});
|
|
}
|
|
|
|
audio.src = 'media/bear-opus.ogg';
|
|
|
|
var timeOnFrozen = 0.0;
|
|
audio.addEventListener('pause', () => {
|
|
timeOnFrozen = audio.currentTime;
|
|
});
|
|
)"));
|
|
|
|
// Load the media.
|
|
{
|
|
TitleWatcher title_watcher(shell()->web_contents(), u"canplaythrough");
|
|
title_watcher.AlsoWaitForTitle(u"error");
|
|
EXPECT_EQ(u"canplaythrough", title_watcher.WaitAndGetTitle());
|
|
}
|
|
|
|
EXPECT_TRUE(ExecJs(rfh_a, R"(
|
|
new Promise(async resolve => {
|
|
audio.play();
|
|
while (audio.currentTime === 0)
|
|
await new Promise(r => setTimeout(r, 1));
|
|
resolve();
|
|
});
|
|
)"));
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
|
|
// 3) Navigate back to A.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_EQ(rfh_a, current_frame_host());
|
|
|
|
// Check that the media position is not changed when the page is in cache.
|
|
double duration1 = EvalJs(rfh_a, "timeOnFrozen;").ExtractDouble();
|
|
double duration2 = EvalJs(rfh_a, "audio.currentTime;").ExtractDouble();
|
|
EXPECT_LE(0.0, duration2 - duration1);
|
|
EXPECT_GT(0.01, duration2 - duration1);
|
|
|
|
// Resume the media.
|
|
EXPECT_TRUE(ExecJs(rfh_a, "audio.play();"));
|
|
|
|
// Confirm that the media pauses automatically when going to the cache.
|
|
// TODO(hajimehoshi): Confirm that this media automatically resumes if
|
|
// autoplay attribute exists.
|
|
EXPECT_EQ(ListValueOf("canplaythrough", "play", "pause", "play"),
|
|
EvalJs(rfh_a, "audio.testObserverEvents"));
|
|
}
|
|
|
|
// Check that a video suspends when the page goes to the cache and can resume
|
|
// after restored.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, VideoSuspendAndResume) {
|
|
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();
|
|
EXPECT_TRUE(ExecJs(rfh_a, R"(
|
|
var video = document.createElement('video');
|
|
document.body.appendChild(video);
|
|
|
|
video.testObserverEvents = [];
|
|
let event_list = [
|
|
'canplaythrough',
|
|
'pause',
|
|
'play',
|
|
'error',
|
|
];
|
|
for (event_name of event_list) {
|
|
let result = event_name;
|
|
video.addEventListener(event_name, event => {
|
|
document.title = result;
|
|
// Ignore 'canplaythrough' event as we can randomly get extra
|
|
// 'canplaythrough' events after playing here.
|
|
if (result != 'canplaythrough')
|
|
video.testObserverEvents.push(result);
|
|
});
|
|
}
|
|
|
|
video.src = 'media/bear.webm';
|
|
|
|
var timeOnFrozen = 0.0;
|
|
video.addEventListener('pause', () => {
|
|
timeOnFrozen = video.currentTime;
|
|
});
|
|
)"));
|
|
|
|
// Load the media.
|
|
{
|
|
TitleWatcher title_watcher(shell()->web_contents(), u"canplaythrough");
|
|
title_watcher.AlsoWaitForTitle(u"error");
|
|
EXPECT_EQ(u"canplaythrough", title_watcher.WaitAndGetTitle());
|
|
}
|
|
|
|
EXPECT_TRUE(ExecJs(rfh_a, R"(
|
|
new Promise(async resolve => {
|
|
video.play();
|
|
while (video.currentTime == 0)
|
|
await new Promise(r => setTimeout(r, 1));
|
|
resolve();
|
|
});
|
|
)"));
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
|
|
// 3) Navigate back to A.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_EQ(rfh_a, current_frame_host());
|
|
|
|
// Check that the media position is not changed when the page is in cache.
|
|
double duration1 = EvalJs(rfh_a, "timeOnFrozen;").ExtractDouble();
|
|
double duration2 = EvalJs(rfh_a, "video.currentTime;").ExtractDouble();
|
|
EXPECT_LE(0.0, duration2 - duration1);
|
|
EXPECT_GT(0.02, duration2 - duration1);
|
|
|
|
// Resume the media.
|
|
EXPECT_TRUE(ExecJs(rfh_a, "video.play();"));
|
|
|
|
// Confirm that the media pauses automatically when going to the cache.
|
|
// TODO(hajimehoshi): Confirm that this media automatically resumes if
|
|
// autoplay attribute exists.
|
|
EXPECT_EQ(ListValueOf("play", "pause", "play"),
|
|
EvalJs(rfh_a, "video.testObserverEvents"));
|
|
}
|
|
|
|
class SensorBackForwardCacheBrowserTest : public BackForwardCacheBrowserTest {
|
|
protected:
|
|
SensorBackForwardCacheBrowserTest() {
|
|
SensorProviderProxyImpl::OverrideSensorProviderBinderForTesting(
|
|
base::BindRepeating(
|
|
&SensorBackForwardCacheBrowserTest::BindSensorProvider,
|
|
base::Unretained(this)));
|
|
}
|
|
|
|
~SensorBackForwardCacheBrowserTest() override {
|
|
SensorProviderProxyImpl::OverrideSensorProviderBinderForTesting(
|
|
base::NullCallback());
|
|
}
|
|
|
|
void SetUpOnMainThread() override {
|
|
provider_ = std::make_unique<device::FakeSensorProvider>();
|
|
provider_->SetAccelerometerData(1.0, 2.0, 3.0);
|
|
|
|
BackForwardCacheBrowserTest::SetUpOnMainThread();
|
|
}
|
|
|
|
std::unique_ptr<device::FakeSensorProvider> provider_;
|
|
|
|
private:
|
|
void BindSensorProvider(
|
|
mojo::PendingReceiver<device::mojom::SensorProvider> receiver) {
|
|
provider_->Bind(std::move(receiver));
|
|
}
|
|
};
|
|
|
|
IN_PROC_BROWSER_TEST_F(SensorBackForwardCacheBrowserTest,
|
|
AccelerometerNotCached) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a(embedded_test_server()->GetURL("/title1.html"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// 1) Navigate to A.
|
|
ASSERT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
|
|
EXPECT_TRUE(ExecJs(rfh_a, R"(
|
|
new Promise(resolve => {
|
|
const sensor = new Accelerometer();
|
|
sensor.addEventListener('reading', () => { resolve(); });
|
|
sensor.start();
|
|
})
|
|
)"));
|
|
|
|
// 2) Navigate to B.
|
|
ASSERT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
// - Page A should not be in the cache.
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
|
|
// 3) Go back.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kBlocklistedFeatures},
|
|
{blink::scheduler::WebSchedulerTrackedFeature::
|
|
kRequestedBackForwardCacheBlockedSensors},
|
|
{}, {}, FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(SensorBackForwardCacheBrowserTest, OrientationCached) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a(embedded_test_server()->GetURL("/title1.html"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// 1) Navigate to A.
|
|
ASSERT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
|
|
EXPECT_TRUE(ExecJs(rfh_a, R"(
|
|
window.addEventListener("deviceorientation", () => {});
|
|
)"));
|
|
|
|
// 2) Navigate to B.
|
|
ASSERT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
EXPECT_FALSE(delete_observer_rfh_a.deleted());
|
|
EXPECT_THAT(rfh_a, InBackForwardCache());
|
|
}
|
|
|
|
// Tests that the orientation sensor's events are not delivered to a page in the
|
|
// back-forward cache.
|
|
//
|
|
// This sets some JS functions in the pages to enable the sensors, capture and
|
|
// validate the events. The a-page should only receive events with alpha=0, the
|
|
// b-page is allowed to receive any alpha value. The test captures 3 events in
|
|
// the a-page, then navigates to the b-page and changes the reading to have
|
|
// alpha=1. While on the b-page it captures 3 more events. If the a-page is
|
|
// still receiving events it should receive one or more of these. Finally it
|
|
// resets the reasing back to have alpha=0 and navigates back to the a-page and
|
|
// catpures 3 more events and verifies that all events on the a-page have
|
|
// alpha=1.
|
|
IN_PROC_BROWSER_TEST_F(SensorBackForwardCacheBrowserTest,
|
|
SensorPausedWhileCached) {
|
|
ASSERT_TRUE(CreateHttpsServer()->Start());
|
|
GURL url_a(https_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_b(https_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
provider_->SetRelativeOrientationSensorData(0, 0, 0);
|
|
|
|
// JS to cause a page to listen to, capture and validate orientation events.
|
|
const std::string sensor_js = R"(
|
|
// Collects events that have happened so far.
|
|
var events = [];
|
|
// If set, will be called by handleEvent.
|
|
var pendingResolve = null;
|
|
|
|
// Handles one event, pushing it to |events| and calling |pendingResolve| if
|
|
// set.
|
|
function handleEvent(event) {
|
|
events.push(event);
|
|
if (pendingResolve !== null) {
|
|
pendingResolve('event');
|
|
pendingResolve = null;
|
|
}
|
|
}
|
|
|
|
// Returns a promise that will resolve when the events array has at least
|
|
// |eventCountMin| elements. Returns the number of elements.
|
|
function waitForEventsPromise(eventCountMin) {
|
|
if (events.length >= eventCountMin) {
|
|
return Promise.resolve(events.length);
|
|
}
|
|
return new Promise(resolve => {
|
|
pendingResolve = resolve;
|
|
}).then(() => waitForEventsPromise(eventCountMin));
|
|
}
|
|
|
|
// Pretty print an orientation event.
|
|
function eventToString(event) {
|
|
return `${event.alpha} ${event.beta} ${event.gamma}`;
|
|
}
|
|
|
|
// Ensure that that |expectedAlpha| matches the alpha of all events.
|
|
function validateEvents(expectedAlpha = null) {
|
|
if (expectedAlpha !== null) {
|
|
let count = 0;
|
|
for (event of events) {
|
|
count++;
|
|
if (Math.abs(event.alpha - expectedAlpha) > 0.01) {
|
|
return `fail - ${count}/${events.length}: ` +
|
|
`${expectedAlpha} != ${event.alpha} (${eventToString(event)})`;
|
|
}
|
|
}
|
|
}
|
|
return 'pass';
|
|
}
|
|
|
|
window.addEventListener('deviceorientation', handleEvent);
|
|
)";
|
|
|
|
// 1) Navigate to A.
|
|
ASSERT_TRUE(NavigateToURL(shell(), url_a));
|
|
ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
|
|
ASSERT_TRUE(ExecJs(rfh_a, sensor_js));
|
|
|
|
// Collect 3 orientation events.
|
|
ASSERT_EQ(1, EvalJs(rfh_a, "waitForEventsPromise(1)"));
|
|
provider_->UpdateRelativeOrientationSensorData(0, 0, 0.2);
|
|
ASSERT_EQ(2, EvalJs(rfh_a, "waitForEventsPromise(2)"));
|
|
provider_->UpdateRelativeOrientationSensorData(0, 0, 0.4);
|
|
ASSERT_EQ(3, EvalJs(rfh_a, "waitForEventsPromise(3)"));
|
|
// We should have 3 events with alpha=0.
|
|
ASSERT_EQ("pass", EvalJs(rfh_a, "validateEvents(0)"));
|
|
|
|
// 2) Navigate to B.
|
|
ASSERT_TRUE(NavigateToURL(shell(), url_b));
|
|
ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
RenderFrameHostImpl* rfh_b = current_frame_host();
|
|
|
|
ASSERT_FALSE(delete_observer_rfh_a.deleted());
|
|
ASSERT_THAT(rfh_a, InBackForwardCache());
|
|
ASSERT_NE(rfh_a, rfh_b);
|
|
|
|
ASSERT_TRUE(ExecJs(rfh_b, sensor_js));
|
|
|
|
// Collect 3 orientation events.
|
|
provider_->SetRelativeOrientationSensorData(1, 0, 0);
|
|
ASSERT_EQ(1, EvalJs(rfh_b, "waitForEventsPromise(1)"));
|
|
provider_->UpdateRelativeOrientationSensorData(1, 0, 0.2);
|
|
ASSERT_EQ(2, EvalJs(rfh_b, "waitForEventsPromise(2)"));
|
|
provider_->UpdateRelativeOrientationSensorData(1, 0, 0.4);
|
|
ASSERT_EQ(3, EvalJs(rfh_b, "waitForEventsPromise(3)"));
|
|
// We should have 3 events with alpha=1.
|
|
ASSERT_EQ("pass", EvalJs(rfh_b, "validateEvents()"));
|
|
|
|
// 3) Go back to A.
|
|
provider_->UpdateRelativeOrientationSensorData(0, 0, 0);
|
|
web_contents()->GetController().GoBack();
|
|
ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ASSERT_EQ(rfh_a, current_frame_host());
|
|
|
|
// Collect 3 orientation events.
|
|
provider_->UpdateRelativeOrientationSensorData(0, 0, 0);
|
|
// There are 2 processes so, it's possible that more events crept in. So we
|
|
// capture how many there are at this point and uses to wait for at least 3
|
|
// more.
|
|
int count = EvalJs(rfh_a, "waitForEventsPromise(4)").ExtractInt();
|
|
provider_->UpdateRelativeOrientationSensorData(0, 0, 0.2);
|
|
count++;
|
|
ASSERT_EQ(count, EvalJs(rfh_a, base::StringPrintf("waitForEventsPromise(%d)",
|
|
count)));
|
|
provider_->UpdateRelativeOrientationSensorData(0, 0, 0.4);
|
|
count++;
|
|
ASSERT_EQ(count, EvalJs(rfh_a, base::StringPrintf("waitForEventsPromise(%d)",
|
|
count)));
|
|
|
|
// We should have the earlier 3 plus another 3 events with alpha=0.
|
|
ASSERT_EQ("pass", EvalJs(rfh_a, "validateEvents(0)"));
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
AllowedFeaturesForSubframesDoNotEvict) {
|
|
// The main purpose of this test is to check that when a state of a subframe
|
|
// is updated, CanStoreDocument is still called for the main frame - otherwise
|
|
// we would always evict the document, even when the feature is allowed as
|
|
// CanStoreDocument always returns false for non-main frames.
|
|
|
|
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));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b);
|
|
|
|
// 2) Navigate to C.
|
|
ASSERT_TRUE(NavigateToURL(shell(), url_c));
|
|
|
|
// 3) No-op feature update on a subframe while in cache, should be no-op.
|
|
ASSERT_FALSE(delete_observer_rfh_b.deleted());
|
|
static_cast<blink::mojom::LocalFrameHost*>(rfh_b)
|
|
->DidChangeActiveSchedulerTrackedFeatures(0);
|
|
|
|
// 4) Go back.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_EQ(current_frame_host(), rfh_a);
|
|
|
|
ExpectRestored(FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
IsInactiveAndDisallowActivationIsNoopWhenActive) {
|
|
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));
|
|
EXPECT_FALSE(current_frame_host()->IsInactiveAndDisallowActivation());
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
// 3) Go back to A.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectRestored(FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(
|
|
BackForwardCacheBrowserTest,
|
|
IsInactiveAndDisallowActivationDoesEvictForCachedFrames) {
|
|
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.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
EXPECT_TRUE(rfh_a->IsInactiveAndDisallowActivation());
|
|
|
|
// 3) Go back to A.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kIgnoreEventAndEvict}, {},
|
|
{}, {}, FROM_HERE);
|
|
}
|
|
|
|
// 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.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->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_;
|
|
};
|
|
|
|
// Navigate from A to B and go back.
|
|
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 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 trial when querying bfcache status,
|
|
// which is protected by low-memory setting.
|
|
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();
|
|
|
|
// Nothing is recorded when the memory is less than the threshold value.
|
|
ExpectOutcomeDidNotChange(FROM_HERE);
|
|
ExpectNotRestoredDidNotChange(FROM_HERE);
|
|
|
|
// Ensure that the 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 {
|
|
// 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);
|
|
EnableFeatureAndSetParams(features::kBackForwardCacheMemoryControls,
|
|
"memory_threshold_for_back_forward_cache_in_mb",
|
|
memory_threshold);
|
|
EnableFeatureAndSetParams(blink::features::kLoadingTasksUnfreezable,
|
|
"max_buffered_bytes", "1000");
|
|
|
|
BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
|
|
}
|
|
};
|
|
|
|
// Navigate from A to B and go back.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestForHighMemoryDevices,
|
|
EnableBFCacheForHighMemoryDevices) {
|
|
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());
|
|
}
|
|
|
|
// 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()));
|
|
}
|
|
|
|
// Test scenarios where the "BackForwardCache" content flag is enabled but
|
|
// the command line flag "DisableBackForwardCache" is turned on, resulting in
|
|
// the feature being disabled.
|
|
class BackForwardCacheDisabledThroughCommandLineBrowserTest
|
|
: public BackForwardCacheBrowserTest {
|
|
protected:
|
|
void SetUpCommandLine(base::CommandLine* command_line) override {
|
|
BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
|
|
command_line->AppendSwitch(switches::kDisableBackForwardCache);
|
|
EnableFeatureAndSetParams(blink::features::kLoadingTasksUnfreezable,
|
|
"max_buffered_bytes", "1000");
|
|
}
|
|
};
|
|
|
|
// Ensures that the back-forward cache trial stays inactivated.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheDisabledThroughCommandLineBrowserTest,
|
|
BFCacheDisabled) {
|
|
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 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 trial when querying bfcache status,
|
|
// which is protected by low-memory setting.
|
|
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 it's disabled.
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
|
|
// Nothing is recorded when back-forward cache is disabled.
|
|
ExpectOutcomeDidNotChange(FROM_HERE);
|
|
ExpectNotRestoredDidNotChange(FROM_HERE);
|
|
|
|
// Ensure that the trial still hasn't been activated.
|
|
EXPECT_FALSE(base::FieldTrialList::IsTrialActive(
|
|
base::FeatureList::GetFieldTrial(features::kBackForwardCache)
|
|
->trial_name()));
|
|
}
|
|
|
|
// Ensures that the back-forward cache trial stays inactivated even when
|
|
// renderer code related to back-forward cache runs (in this case, network
|
|
// request loading).
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheDisabledThroughCommandLineBrowserTest,
|
|
BFCacheDisabled_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(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()));
|
|
|
|
// 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 it's disabled.
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
|
|
// Nothing is recorded when back-forward cache is disabled.
|
|
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()));
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(
|
|
BackForwardCacheBrowserTest,
|
|
EvictingDocumentsInRelatedSiteInstancesDoesNotRestartNavigation) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a1(embedded_test_server()->GetURL("a.com", "/title1.html#part1"));
|
|
GURL url_a2(embedded_test_server()->GetURL("a.com", "/title1.html#part2"));
|
|
GURL url_b(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));
|
|
|
|
// 3) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
// 4) Go back to A2, but do not wait for the navigation to commit.
|
|
web_contents()->GetController().GoBack();
|
|
|
|
// 5) Go back to A1.
|
|
// This will attempt to evict A2 from the cache because
|
|
// their navigation entries have related site instances, while a navigation
|
|
// to A2 is in flight. Ensure that we do not try to restart it as it should
|
|
// be superseded by a navigation to A1.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_EQ(url_a1, web_contents()->GetURL());
|
|
}
|
|
|
|
// This tests that even if a page initializes WebRTC, tha page can be cached as
|
|
// long as it doesn't make a connection.
|
|
// On the Android test environments, the test might fail due to IP restrictions.
|
|
// See the discussion at http://crrev.com/c/2564926.
|
|
#if !defined(OS_ANDROID)
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
TrivialRTCPeerConnectionCached) {
|
|
ASSERT_TRUE(CreateHttpsServer()->Start());
|
|
|
|
GURL url_a(https_server()->GetURL("/title1.html"));
|
|
GURL url_b(https_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// 1) Navigate to A.
|
|
ASSERT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
|
|
// Create an RTCPeerConnection without starting a connection.
|
|
EXPECT_TRUE(ExecJs(rfh_a, "const pc1 = new RTCPeerConnection()"));
|
|
|
|
// 2) Navigate to B.
|
|
ASSERT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
// 3) Go back.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectRestored(FROM_HERE);
|
|
|
|
// RTCPeerConnection object, that is created before being put into the cache,
|
|
// is still available.
|
|
EXPECT_EQ("success", EvalJs(rfh_a, R"(
|
|
new Promise(async resolve => {
|
|
const pc1 = new RTCPeerConnection();
|
|
const pc2 = new RTCPeerConnection();
|
|
pc1.onicecandidate = e => {
|
|
if (e.candidate)
|
|
pc2.addIceCandidate(e.candidate);
|
|
}
|
|
pc2.onicecandidate = e => {
|
|
if (e.candidate)
|
|
pc1.addIceCandidate(e.candidate);
|
|
}
|
|
pc1.addTransceiver("audio");
|
|
const connectionEstablished = new Promise((resolve, reject) => {
|
|
pc1.oniceconnectionstatechange = () => {
|
|
const state = pc1.iceConnectionState;
|
|
switch (state) {
|
|
case "connected":
|
|
case "completed":
|
|
resolve();
|
|
break;
|
|
case "failed":
|
|
case "disconnected":
|
|
case "closed":
|
|
reject(state);
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
await pc1.setLocalDescription();
|
|
await pc2.setRemoteDescription(pc1.localDescription);
|
|
await pc2.setLocalDescription();
|
|
await pc1.setRemoteDescription(pc2.localDescription);
|
|
try {
|
|
await connectionEstablished;
|
|
} catch (e) {
|
|
resolve("fail " + e);
|
|
return;
|
|
}
|
|
resolve("success");
|
|
});
|
|
)"));
|
|
}
|
|
#endif // !defined(OS_ANDROID)
|
|
|
|
// This tests that a page using WebRTC and creating actual connections cannot be
|
|
// cached.
|
|
// On the Android test environments, the test might fail due to IP restrictions.
|
|
// See the discussion at http://crrev.com/c/2564926.
|
|
#if !defined(OS_ANDROID)
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
NonTrivialRTCPeerConnectionNotCached) {
|
|
ASSERT_TRUE(CreateHttpsServer()->Start());
|
|
|
|
GURL url_a(https_server()->GetURL("/title1.html"));
|
|
GURL url_b(https_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// 1) Navigate to A.
|
|
ASSERT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
|
|
// Create an RTCPeerConnection with starting a connection.
|
|
EXPECT_EQ("success", EvalJs(rfh_a, R"(
|
|
new Promise(async resolve => {
|
|
const pc1 = new RTCPeerConnection();
|
|
const pc2 = new RTCPeerConnection();
|
|
pc1.onicecandidate = e => {
|
|
if (e.candidate)
|
|
pc2.addIceCandidate(e.candidate);
|
|
}
|
|
pc2.onicecandidate = e => {
|
|
if (e.candidate)
|
|
pc1.addIceCandidate(e.candidate);
|
|
}
|
|
pc1.addTransceiver("audio");
|
|
const connectionEstablished = new Promise(resolve => {
|
|
pc1.oniceconnectionstatechange = () => {
|
|
const state = pc1.iceConnectionState;
|
|
switch (state) {
|
|
case "connected":
|
|
case "completed":
|
|
resolve();
|
|
break;
|
|
case "failed":
|
|
case "disconnected":
|
|
case "closed":
|
|
reject(state);
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
await pc1.setLocalDescription();
|
|
await pc2.setRemoteDescription(pc1.localDescription);
|
|
await pc2.setLocalDescription();
|
|
await pc1.setRemoteDescription(pc2.localDescription);
|
|
await connectionEstablished;
|
|
try {
|
|
await connectionEstablished;
|
|
} catch (e) {
|
|
resolve("fail " + e);
|
|
return;
|
|
}
|
|
resolve("success");
|
|
});
|
|
)"));
|
|
|
|
// 2) Navigate to B.
|
|
ASSERT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
// - Page A should not be in the cache.
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
|
|
// 3) Go back.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kBlocklistedFeatures},
|
|
{blink::scheduler::WebSchedulerTrackedFeature::kWebRTC}, {}, {},
|
|
FROM_HERE);
|
|
}
|
|
#endif // !defined(OS_ANDROID)
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, WebLocksNotCached) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a(embedded_test_server()->GetURL("/title1.html"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// 1) Navigate to A.
|
|
ASSERT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
|
|
// Wait for the page to acquire a lock and ensure that it continues to do so.
|
|
EXPECT_TRUE(ExecJs(rfh_a, R"(
|
|
const never_resolved = new Promise(resolve => {});
|
|
new Promise(continue_test => {
|
|
navigator.locks.request('test', async () => {
|
|
continue_test();
|
|
await never_resolved;
|
|
});
|
|
})
|
|
)"));
|
|
|
|
// 2) Navigate to B.
|
|
ASSERT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
// - Page A should not be in the cache.
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
|
|
// 3) Go back.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kBlocklistedFeatures},
|
|
{blink::scheduler::WebSchedulerTrackedFeature::kWebLocks}, {}, {},
|
|
FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
CanUseCacheWhenNavigatingAwayToErrorPage) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
GURL error_url(embedded_test_server()->GetURL("b.com", "/empty.html"));
|
|
auto url_interceptor = URLLoaderInterceptor::SetupRequestFailForURL(
|
|
error_url, net::ERR_DNS_TIMED_OUT);
|
|
|
|
// 1) Navigate to A.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
|
|
// 2) Navigate to an error page and expect the old page to be stored in
|
|
// bfcache.
|
|
EXPECT_FALSE(NavigateToURL(shell(), error_url));
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
|
|
// 3) Navigate back and expect the page to be restored from bfcache.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
}
|
|
|
|
// 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.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->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.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->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();
|
|
}
|
|
|
|
namespace {
|
|
|
|
class ExecJsInDidFinishNavigation : public WebContentsObserver {
|
|
public:
|
|
explicit ExecJsInDidFinishNavigation(WebContents* web_contents)
|
|
: WebContentsObserver(web_contents) {}
|
|
|
|
void DidFinishNavigation(NavigationHandle* navigation_handle) override {
|
|
if (!navigation_handle->IsInMainFrame() ||
|
|
!navigation_handle->HasCommitted() ||
|
|
navigation_handle->IsSameDocument()) {
|
|
return;
|
|
}
|
|
|
|
ExecuteScriptAsync(navigation_handle->GetRenderFrameHost(),
|
|
"var foo = 42;");
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
// This test checks that the message posted from DidFinishNavigation
|
|
// (ExecuteScriptAsync) is received after the message restoring the page from
|
|
// the back-forward cache (PageMsg_RestorePageFromBackForwardCache).
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
MessageFromDidFinishNavigation) {
|
|
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();
|
|
EXPECT_TRUE(ExecJs(rfh_a, "window.alive = 'I am alive';"));
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
ExecJsInDidFinishNavigation observer(shell()->web_contents());
|
|
|
|
// 3) Go back to A. Expect the page to be restored from the cache.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_EQ("I am alive", EvalJs(rfh_a, "window.alive"));
|
|
|
|
// Make sure that the javascript execution requested from DidFinishNavigation
|
|
// did not result in eviction. If the document was evicted, the document
|
|
// would be reloaded - check that it didn't happen and the tab is not
|
|
// loading.
|
|
EXPECT_FALSE(web_contents()->IsLoading());
|
|
|
|
EXPECT_EQ(rfh_a, current_frame_host());
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, WebMidiNotCached) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a(embedded_test_server()->GetURL("/title1.html"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// 1) Navigate to A.
|
|
ASSERT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
|
|
// Request access to MIDI. This should prevent the page from entering the
|
|
// BackForwardCache.
|
|
EXPECT_TRUE(ExecJs(rfh_a, "navigator.requestMIDIAccess()",
|
|
EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
|
|
|
|
// 2) Navigate to B.
|
|
ASSERT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
// - Page A should not be in the cache.
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
|
|
// 3) Go back.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kBlocklistedFeatures},
|
|
{blink::scheduler::WebSchedulerTrackedFeature::kRequestedMIDIPermission},
|
|
{}, {}, FROM_HERE);
|
|
}
|
|
|
|
#if defined(OS_ANDROID)
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
ChildImportanceTestForBackForwardCachedPagesTest) {
|
|
web_contents()->SetMainFrameImportance(ChildProcessImportance::MODERATE);
|
|
|
|
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));
|
|
ASSERT_FALSE(delete_observer_rfh_a.deleted());
|
|
|
|
// 3) Verify the importance of page after entering back-forward cache to be
|
|
// "NORMAL".
|
|
EXPECT_EQ(ChildProcessImportance::NORMAL,
|
|
rfh_a->GetProcess()->GetEffectiveImportance());
|
|
|
|
// 4) Go back to A.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
// 5) Verify the importance was restored correctly after page leaves
|
|
// back-forward cache.
|
|
EXPECT_EQ(ChildProcessImportance::MODERATE,
|
|
rfh_a->GetProcess()->GetEffectiveImportance());
|
|
}
|
|
#endif
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
PresentationConnectionClosed) {
|
|
ASSERT_TRUE(CreateHttpsServer()->Start());
|
|
GURL url_a(https_server()->GetURL(
|
|
"a.com", "/back_forward_cache/presentation_controller.html"));
|
|
|
|
// Navigate to A (presentation controller page).
|
|
ASSERT_TRUE(NavigateToURL(shell(), url_a));
|
|
auto* rfh_a = current_frame_host();
|
|
// Start a presentation connection in A.
|
|
MockPresentationServiceDelegate mock_presentation_service_delegate;
|
|
auto& presentation_service = rfh_a->GetPresentationServiceForTesting();
|
|
presentation_service.SetControllerDelegateForTesting(
|
|
&mock_presentation_service_delegate);
|
|
EXPECT_CALL(mock_presentation_service_delegate, StartPresentation(_, _, _));
|
|
EXPECT_TRUE(ExecJs(rfh_a, "presentationRequest.start().then(setConnection)",
|
|
EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
|
|
|
|
// Send a mock connection to the renderer.
|
|
MockPresentationConnection mock_controller_connection;
|
|
mojo::Receiver<PresentationConnection> controller_connection_receiver(
|
|
&mock_controller_connection);
|
|
mojo::Remote<PresentationConnection> receiver_connection;
|
|
const std::string presentation_connection_id = "foo";
|
|
presentation_service.OnStartPresentationSucceeded(
|
|
presentation_service.start_presentation_request_id_,
|
|
PresentationConnectionResult::New(
|
|
blink::mojom::PresentationInfo::New(GURL("fake-url"),
|
|
presentation_connection_id),
|
|
controller_connection_receiver.BindNewPipeAndPassRemote(),
|
|
receiver_connection.BindNewPipeAndPassReceiver()));
|
|
|
|
// Navigate to B, make sure that the connection started in A is closed.
|
|
GURL url_b(https_server()->GetURL("b.com", "/title1.html"));
|
|
EXPECT_CALL(
|
|
mock_controller_connection,
|
|
DidClose(blink::mojom::PresentationConnectionCloseReason::WENT_AWAY));
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
ASSERT_TRUE(NavigateToURL(shell(), url_b));
|
|
EXPECT_FALSE(delete_observer_rfh_a.deleted());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
|
|
// Navigate back to A. Ensure that connection state has been updated
|
|
// accordingly.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_FALSE(rfh_a->IsInBackForwardCache());
|
|
EXPECT_EQ(presentation_connection_id, EvalJs(rfh_a, "connection.id"));
|
|
EXPECT_EQ("closed", EvalJs(rfh_a, "connection.state"));
|
|
EXPECT_TRUE(EvalJs(rfh_a, "connectionClosed").ExtractBool());
|
|
|
|
// Try to start another connection, should successfully reach the browser side
|
|
// PresentationServiceDelegate.
|
|
EXPECT_CALL(mock_presentation_service_delegate,
|
|
ReconnectPresentation(_, presentation_connection_id, _, _));
|
|
EXPECT_TRUE(ExecJs(rfh_a, "presentationRequest.reconnect(connection.id);",
|
|
EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
// Reset |presentation_service|'s controller delegate so that it won't try to
|
|
// call Reset() on it on destruction time.
|
|
presentation_service.OnDelegateDestroyed();
|
|
}
|
|
|
|
namespace {
|
|
|
|
// Subclass of FrameServiceBase for test.
|
|
class EchoImpl final : public FrameServiceBase<mojom::Echo> {
|
|
public:
|
|
EchoImpl(RenderFrameHost* render_frame_host,
|
|
mojo::PendingReceiver<mojom::Echo> receiver,
|
|
bool* deleted)
|
|
: FrameServiceBase(render_frame_host, std::move(receiver)),
|
|
deleted_(deleted) {}
|
|
~EchoImpl() final { *deleted_ = true; }
|
|
|
|
// mojom::Echo implementation
|
|
void EchoString(const std::string& input, EchoStringCallback callback) final {
|
|
std::move(callback).Run(input);
|
|
}
|
|
|
|
private:
|
|
bool* deleted_;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, FrameServiceBase) {
|
|
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));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
|
|
mojo::Remote<mojom::Echo> echo_remote;
|
|
bool echo_deleted = false;
|
|
new EchoImpl(rfh_a, echo_remote.BindNewPipeAndPassReceiver(), &echo_deleted);
|
|
|
|
// 2) Navigate to B.
|
|
ASSERT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
// - Page A should be in the cache.
|
|
ASSERT_FALSE(delete_observer_rfh_a.deleted());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
EXPECT_FALSE(echo_deleted);
|
|
|
|
// 3) Go back.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_FALSE(echo_deleted);
|
|
|
|
// 4) Prevent caching and navigate to B.
|
|
DisableForRenderFrameHostForTesting(rfh_a);
|
|
ASSERT_TRUE(NavigateToURL(shell(), url_b));
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
EXPECT_TRUE(echo_deleted);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, OutstandingFetchNotCached) {
|
|
net::test_server::ControllableHttpResponse response(embedded_test_server(),
|
|
"/fetch");
|
|
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);
|
|
// Ensure that there are no lingering requests from page load itself.
|
|
EXPECT_FALSE(rfh_a->scheduler_tracked_features() &
|
|
(1ull << static_cast<size_t>(
|
|
blink::scheduler::WebSchedulerTrackedFeature::
|
|
kOutstandingNetworkRequestFetch)));
|
|
|
|
// 2) Create a fetch() request.
|
|
ExecuteScriptAsync(rfh_a, "fetch('/fetch');");
|
|
response.WaitForRequest();
|
|
|
|
// 3) Navigate to B.
|
|
ASSERT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
// 4) Go back.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kBlocklistedFeatures},
|
|
{blink::scheduler::WebSchedulerTrackedFeature::
|
|
kOutstandingNetworkRequestFetch},
|
|
{}, {}, FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, OutstandingXHRNotCached) {
|
|
net::test_server::ControllableHttpResponse response(embedded_test_server(),
|
|
"/xhr");
|
|
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);
|
|
// Ensure that there are no lingering requests from page load itself.
|
|
EXPECT_FALSE(rfh_a->scheduler_tracked_features() &
|
|
(1ull << static_cast<size_t>(
|
|
blink::scheduler::WebSchedulerTrackedFeature::
|
|
kOutstandingNetworkRequestXHR)));
|
|
|
|
// 2) Create a XMLHttpRequest.
|
|
EXPECT_TRUE(ExecJs(rfh_a, R"(
|
|
var req = new XMLHttpRequest();
|
|
req.open("GET", "/xhr");
|
|
req.send();
|
|
)"));
|
|
response.WaitForRequest();
|
|
|
|
// 3) Navigate to B.
|
|
ASSERT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
// 4) Go back.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kBlocklistedFeatures},
|
|
{blink::scheduler::WebSchedulerTrackedFeature::
|
|
kOutstandingNetworkRequestXHR},
|
|
{}, {}, FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, NotFetchedScriptNotCached) {
|
|
net::test_server::ControllableHttpResponse response(
|
|
embedded_test_server(),
|
|
"/back_forward_cache/script-which-does-not-exist.js");
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
GURL url_a(embedded_test_server()->GetURL(
|
|
"a.com", "/back_forward_cache/page_with_nonexistent_script.html"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// 1) Navigate to A.
|
|
TestNavigationObserver navigation_observer1(web_contents());
|
|
shell()->LoadURL(url_a);
|
|
navigation_observer1.WaitForNavigationFinished();
|
|
response.WaitForRequest();
|
|
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
|
|
// 2) Navigate to B.
|
|
TestNavigationObserver navigation_observer2(web_contents());
|
|
shell()->LoadURL(url_b);
|
|
navigation_observer2.WaitForNavigationFinished();
|
|
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
|
|
// 3) Go back.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kBlocklistedFeatures},
|
|
{blink::scheduler::WebSchedulerTrackedFeature::
|
|
kOutstandingNetworkRequestOthers},
|
|
{}, {}, FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, PageshowMetrics) {
|
|
// TODO(https://crbug.com/1099395): Do not check for unexpected messages
|
|
// because the input task queue is not currently frozen, causing flakes in
|
|
// this test.
|
|
DoNotFailForUnexpectedMessagesWhileCached();
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
const char kHistogramName[] =
|
|
"BackForwardCache.MainFrameHasPageshowListenersOnRestore";
|
|
|
|
const GURL url1(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
const GURL url2(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// 1) Navigate to the page.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url1));
|
|
EXPECT_TRUE(ExecJs(current_frame_host(), R"(
|
|
window.foo = 42;
|
|
)"));
|
|
|
|
// 2) Navigate away and back.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url2));
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
// As we don't get an explicit ACK when the page is restored (yet), force
|
|
// a round-trip to the renderer to effectively flush the queue.
|
|
EXPECT_EQ(42, EvalJs(current_frame_host(), "window.foo"));
|
|
|
|
// Expect the back-forward restore without pageshow to be detected.
|
|
content::FetchHistogramsFromChildProcesses();
|
|
EXPECT_THAT(histogram_tester_.GetAllSamples(kHistogramName),
|
|
ElementsAre(base::Bucket(0, 1)));
|
|
|
|
EXPECT_TRUE(ExecJs(current_frame_host(), R"(
|
|
window.addEventListener("pageshow", () => {});
|
|
)"));
|
|
|
|
// 3) Navigate away and back (again).
|
|
EXPECT_TRUE(NavigateToURL(shell(), url2));
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
// As we don't get an explicit ACK when the page is restored (yet), force
|
|
// a round-trip to the renderer to effectively flush the queue.
|
|
EXPECT_EQ(42, EvalJs(current_frame_host(), "window.foo"));
|
|
|
|
// Expect the back-forward restore with pageshow to be detected.
|
|
content::FetchHistogramsFromChildProcesses();
|
|
EXPECT_THAT(histogram_tester_.GetAllSamples(kHistogramName),
|
|
ElementsAre(base::Bucket(0, 1), base::Bucket(1, 1)));
|
|
}
|
|
|
|
// Navigate from A(B) to C and check IsCurrent status for RenderFrameHost A
|
|
// and B before and after entering back-forward cache.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, CheckIsCurrent) {
|
|
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(B).
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host();
|
|
|
|
EXPECT_TRUE(rfh_a->IsCurrent());
|
|
EXPECT_TRUE(rfh_b->IsCurrent());
|
|
|
|
// 2) Navigate to C.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_c));
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
EXPECT_TRUE(rfh_b->IsInBackForwardCache());
|
|
|
|
EXPECT_FALSE(rfh_a->IsCurrent());
|
|
EXPECT_FALSE(rfh_b->IsCurrent());
|
|
}
|
|
|
|
// Test that LifecycleStateImpl is updated correctly when page enters and
|
|
// restores back from BackForwardCache.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
CheckLifecycleStateTransition) {
|
|
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 and check the LifecycleStateImpl of A.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kActive,
|
|
rfh_a->lifecycle_state());
|
|
EXPECT_EQ(RenderFrameHost::LifecycleState::kActive,
|
|
rfh_a->GetLifecycleState());
|
|
|
|
// 2) Navigate to B, now A enters BackForwardCache. Check the
|
|
// LifecycleStateImpl of both RenderFrameHost A and B.
|
|
{
|
|
testing::NiceMock<MockWebContentsObserver> state_change_observer(
|
|
web_contents());
|
|
EXPECT_CALL(state_change_observer,
|
|
RenderFrameHostStateChanged(
|
|
rfh_a, RenderFrameHost::LifecycleState::kActive,
|
|
RenderFrameHost::LifecycleState::kInBackForwardCache));
|
|
// We don't know |rfh_b| yet, so we'll match any frame.
|
|
EXPECT_CALL(state_change_observer,
|
|
RenderFrameHostStateChanged(
|
|
testing::Not(rfh_a),
|
|
RenderFrameHost::LifecycleState::kPendingCommit,
|
|
RenderFrameHost::LifecycleState::kActive));
|
|
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
}
|
|
RenderFrameHostImpl* rfh_b = current_frame_host();
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kInBackForwardCache,
|
|
rfh_a->lifecycle_state());
|
|
EXPECT_EQ(RenderFrameHost::LifecycleState::kInBackForwardCache,
|
|
rfh_a->GetLifecycleState());
|
|
EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kActive,
|
|
rfh_b->lifecycle_state());
|
|
EXPECT_EQ(RenderFrameHost::LifecycleState::kActive,
|
|
rfh_b->GetLifecycleState());
|
|
|
|
// 3) Go back to A and check again the LifecycleStateImpl of both
|
|
// RenderFrameHost A and B.
|
|
{
|
|
testing::NiceMock<MockWebContentsObserver> state_change_observer(
|
|
web_contents());
|
|
EXPECT_CALL(state_change_observer,
|
|
RenderFrameHostStateChanged(
|
|
rfh_a, RenderFrameHost::LifecycleState::kInBackForwardCache,
|
|
RenderFrameHost::LifecycleState::kActive));
|
|
EXPECT_CALL(state_change_observer,
|
|
RenderFrameHostStateChanged(
|
|
rfh_b, RenderFrameHost::LifecycleState::kActive,
|
|
RenderFrameHost::LifecycleState::kInBackForwardCache));
|
|
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
}
|
|
EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kActive,
|
|
rfh_a->lifecycle_state());
|
|
EXPECT_TRUE(rfh_b->IsInBackForwardCache());
|
|
EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kInBackForwardCache,
|
|
rfh_b->lifecycle_state());
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
CheckLifecycleStateTransitionWithSubframes) {
|
|
IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess());
|
|
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", "/cross_site_iframe_factory.html?c(d)"));
|
|
|
|
// Navigate to A(B) and check the lifecycle states of A and B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host();
|
|
EXPECT_FALSE(rfh_a->IsInBackForwardCache());
|
|
EXPECT_FALSE(rfh_b->IsInBackForwardCache());
|
|
EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kActive,
|
|
rfh_a->lifecycle_state());
|
|
EXPECT_EQ(RenderFrameHost::LifecycleState::kActive,
|
|
rfh_a->GetLifecycleState());
|
|
EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kActive,
|
|
rfh_b->lifecycle_state());
|
|
EXPECT_EQ(RenderFrameHost::LifecycleState::kActive,
|
|
rfh_b->GetLifecycleState());
|
|
|
|
// Navigate to C(D), now A(B) enters BackForwardCache.
|
|
{
|
|
testing::NiceMock<MockWebContentsObserver> state_change_observer(
|
|
web_contents());
|
|
EXPECT_CALL(state_change_observer,
|
|
RenderFrameHostStateChanged(
|
|
rfh_a, RenderFrameHost::LifecycleState::kActive,
|
|
RenderFrameHost::LifecycleState::kInBackForwardCache));
|
|
EXPECT_CALL(state_change_observer,
|
|
RenderFrameHostStateChanged(
|
|
rfh_b, RenderFrameHost::LifecycleState::kActive,
|
|
RenderFrameHost::LifecycleState::kInBackForwardCache));
|
|
// We don't know |rfh_c| and |rfh_d| yet, so we'll match any frame.
|
|
EXPECT_CALL(state_change_observer,
|
|
RenderFrameHostStateChanged(
|
|
testing::Not(testing::AnyOf(rfh_a, rfh_b)),
|
|
RenderFrameHost::LifecycleState::kPendingCommit,
|
|
RenderFrameHost::LifecycleState::kActive))
|
|
.Times(2);
|
|
// Deletion of frame D's initial RFH.
|
|
EXPECT_CALL(state_change_observer,
|
|
RenderFrameHostStateChanged(
|
|
testing::Not(testing::AnyOf(rfh_a, rfh_b)),
|
|
RenderFrameHost::LifecycleState::kActive,
|
|
RenderFrameHost::LifecycleState::kPendingDeletion));
|
|
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_c));
|
|
}
|
|
RenderFrameHostImpl* rfh_c = current_frame_host();
|
|
RenderFrameHostImpl* rfh_d = rfh_c->child_at(0)->current_frame_host();
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
EXPECT_TRUE(rfh_b->IsInBackForwardCache());
|
|
EXPECT_FALSE(rfh_c->IsInBackForwardCache());
|
|
EXPECT_FALSE(rfh_d->IsInBackForwardCache());
|
|
EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kInBackForwardCache,
|
|
rfh_a->lifecycle_state());
|
|
EXPECT_EQ(RenderFrameHost::LifecycleState::kInBackForwardCache,
|
|
rfh_a->GetLifecycleState());
|
|
EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kInBackForwardCache,
|
|
rfh_b->lifecycle_state());
|
|
EXPECT_EQ(RenderFrameHost::LifecycleState::kInBackForwardCache,
|
|
rfh_b->GetLifecycleState());
|
|
EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kActive,
|
|
rfh_c->lifecycle_state());
|
|
EXPECT_EQ(RenderFrameHost::LifecycleState::kActive,
|
|
rfh_c->GetLifecycleState());
|
|
EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kActive,
|
|
rfh_d->lifecycle_state());
|
|
EXPECT_EQ(RenderFrameHost::LifecycleState::kActive,
|
|
rfh_d->GetLifecycleState());
|
|
|
|
// Go back to A(B), A(B) is restored and C(D) enters BackForwardCache.
|
|
{
|
|
testing::NiceMock<MockWebContentsObserver> state_change_observer(
|
|
web_contents());
|
|
EXPECT_CALL(state_change_observer,
|
|
RenderFrameHostStateChanged(
|
|
rfh_a, RenderFrameHost::LifecycleState::kInBackForwardCache,
|
|
RenderFrameHost::LifecycleState::kActive));
|
|
EXPECT_CALL(state_change_observer,
|
|
RenderFrameHostStateChanged(
|
|
rfh_b, RenderFrameHost::LifecycleState::kInBackForwardCache,
|
|
RenderFrameHost::LifecycleState::kActive));
|
|
EXPECT_CALL(state_change_observer,
|
|
RenderFrameHostStateChanged(
|
|
rfh_c, RenderFrameHost::LifecycleState::kActive,
|
|
RenderFrameHost::LifecycleState::kInBackForwardCache));
|
|
EXPECT_CALL(state_change_observer,
|
|
RenderFrameHostStateChanged(
|
|
rfh_d, RenderFrameHost::LifecycleState::kActive,
|
|
RenderFrameHost::LifecycleState::kInBackForwardCache));
|
|
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
}
|
|
EXPECT_FALSE(rfh_a->IsInBackForwardCache());
|
|
EXPECT_FALSE(rfh_b->IsInBackForwardCache());
|
|
EXPECT_TRUE(rfh_c->IsInBackForwardCache());
|
|
EXPECT_TRUE(rfh_d->IsInBackForwardCache());
|
|
EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kActive,
|
|
rfh_a->lifecycle_state());
|
|
EXPECT_EQ(RenderFrameHost::LifecycleState::kActive,
|
|
rfh_a->GetLifecycleState());
|
|
EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kActive,
|
|
rfh_b->lifecycle_state());
|
|
EXPECT_EQ(RenderFrameHost::LifecycleState::kActive,
|
|
rfh_b->GetLifecycleState());
|
|
EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kInBackForwardCache,
|
|
rfh_c->lifecycle_state());
|
|
EXPECT_EQ(RenderFrameHost::LifecycleState::kInBackForwardCache,
|
|
rfh_c->GetLifecycleState());
|
|
EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kInBackForwardCache,
|
|
rfh_d->lifecycle_state());
|
|
EXPECT_EQ(RenderFrameHost::LifecycleState::kInBackForwardCache,
|
|
rfh_d->GetLifecycleState());
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
DoesNotCacheIfSpeechRecognitionIsStarted) {
|
|
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 url_a.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
|
|
// 2) Start SpeechRecognition.
|
|
EXPECT_TRUE(ExecJs(rfh_a, R"(
|
|
new Promise(async resolve => {
|
|
var r = new webkitSpeechRecognition();
|
|
r.start();
|
|
resolve();
|
|
});
|
|
)"));
|
|
|
|
// 3) Navigate away.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
// 4) The page uses SpeechRecognition so it should be deleted.
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
|
|
// 5) Go back to the page with SpeechRecognition.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kBlocklistedFeatures},
|
|
{blink::scheduler::WebSchedulerTrackedFeature::kSpeechRecognizer}, {}, {},
|
|
FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
CanCacheIfSpeechRecognitionIsNotStarted) {
|
|
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 url_a.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
|
|
// 2) Initialise SpeechRecognition but don't start it yet.
|
|
EXPECT_TRUE(ExecJs(rfh_a, R"(
|
|
new Promise(async resolve => {
|
|
var r = new webkitSpeechRecognition();
|
|
resolve();
|
|
});
|
|
)"));
|
|
|
|
// 3) Navigate away.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
// 4) The page didn't start using SpeechRecognition so it shouldn't be deleted
|
|
// and enter BackForwardCache.
|
|
EXPECT_FALSE(delete_observer_rfh_a.deleted());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
|
|
// 5) Go back to the page with SpeechRecognition.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_EQ(rfh_a, current_frame_host());
|
|
|
|
ExpectRestored(FROM_HERE);
|
|
}
|
|
|
|
// This test is not important for Chrome OS if TTS is called in content. For
|
|
// more details refer (content/browser/speech/tts_platform_impl.cc).
|
|
#if BUILDFLAG(IS_CHROMEOS_ASH)
|
|
#define MAYBE_DoesNotCacheIfUsingSpeechSynthesis \
|
|
DISABLED_DoesNotCacheIfUsingSpeechSynthesis
|
|
#else
|
|
#define MAYBE_DoesNotCacheIfUsingSpeechSynthesis \
|
|
DoesNotCacheIfUsingSpeechSynthesis
|
|
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
MAYBE_DoesNotCacheIfUsingSpeechSynthesis) {
|
|
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 page and start using SpeechSynthesis.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver rhf_a_deleted(rfh_a);
|
|
|
|
EXPECT_TRUE(ExecJs(rfh_a, R"(
|
|
new Promise(async resolve => {
|
|
var u = new SpeechSynthesisUtterance(" ");
|
|
speechSynthesis.speak(u);
|
|
resolve();
|
|
});
|
|
)"));
|
|
|
|
// 2) Navigate away.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
// The page uses SpeechSynthesis so it should be deleted.
|
|
rhf_a_deleted.WaitUntilDeleted();
|
|
|
|
// 3) Go back to the page with SpeechSynthesis.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kBlocklistedFeatures},
|
|
{blink::scheduler::WebSchedulerTrackedFeature::kSpeechSynthesis}, {}, {},
|
|
FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
DoesNotCacheIfRunFileChooserIsInvoked) {
|
|
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 url_a and open file chooser.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver deleted_rfh_a(rfh_a);
|
|
content::BackForwardCacheDisabledTester tester;
|
|
|
|
// 2) Bind FileChooser to RenderFrameHost.
|
|
mojo::Remote<blink::mojom::FileChooser> chooser =
|
|
FileChooserImpl::CreateBoundForTesting(rfh_a);
|
|
|
|
auto quit_run_loop = [](base::OnceClosure callback,
|
|
blink::mojom::FileChooserResultPtr result) {
|
|
std::move(callback).Run();
|
|
};
|
|
|
|
// 3) Run OpenFileChooser and wait till its run.
|
|
base::RunLoop run_loop;
|
|
chooser->OpenFileChooser(
|
|
blink::mojom::FileChooserParams::New(),
|
|
base::BindOnce(quit_run_loop, run_loop.QuitClosure()));
|
|
run_loop.Run();
|
|
|
|
// 4) rfh_a should be disabled for BackForwardCache after opening file
|
|
// chooser.
|
|
EXPECT_TRUE(rfh_a->IsBackForwardCacheDisabled());
|
|
auto reason = BackForwardCacheDisable::DisabledReason(
|
|
BackForwardCacheDisable::DisabledReasonId::kFileChooser);
|
|
EXPECT_TRUE(tester.IsDisabledForFrameWithReason(
|
|
rfh_a->GetProcess()->GetID(), rfh_a->GetRoutingID(), reason));
|
|
|
|
// 5) Navigate to B having the file chooser open.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
// The page uses FileChooser so it should be deleted.
|
|
deleted_rfh_a.WaitUntilDeleted();
|
|
|
|
// 6) Go back to the page with FileChooser.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason::
|
|
kDisableForRenderFrameHostCalled},
|
|
{}, {}, {reason}, FROM_HERE);
|
|
}
|
|
|
|
// RenderFrameHostImpl::coep_reporter() must be preserved when doing a back
|
|
// navigation using the BackForwardCache.
|
|
// Regression test for https://crbug.com/1102285.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, CoepReporter) {
|
|
ASSERT_TRUE(CreateHttpsServer()->Start());
|
|
GURL url_a(https_server()->GetURL("a.com",
|
|
"/set-header?"
|
|
"Cross-Origin-Embedder-Policy-Report-Only: "
|
|
"require-corp; report-to%3d\"a\""));
|
|
GURL url_b(https_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// Navigate to a document that set RenderFrameHostImpl::coep_reporter().
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
EXPECT_TRUE(rfh_a->coep_reporter());
|
|
|
|
// Navigate away and back using the BackForwardCache. The
|
|
// RenderFrameHostImpl::coep_reporter() must still be there.
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
EXPECT_FALSE(delete_observer_rfh_a.deleted());
|
|
EXPECT_EQ(rfh_a, current_frame_host());
|
|
|
|
EXPECT_TRUE(rfh_a->coep_reporter());
|
|
}
|
|
|
|
// RenderFrameHostImpl::coop_reporter() must be preserved when doing a back
|
|
// navigation using the BackForwardCache.
|
|
// Regression test for https://crbug.com/1102285.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, CoopReporter) {
|
|
ASSERT_TRUE(CreateHttpsServer()->Start());
|
|
GURL url_a(https_server()->GetURL("a.com",
|
|
"/set-header?"
|
|
"Cross-Origin-Opener-Policy-Report-Only: "
|
|
"same-origin; report-to%3d\"a\""));
|
|
GURL url_b(https_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// Navigate to a document that set RenderFrameHostImpl::coop_reporter().
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
EXPECT_TRUE(rfh_a->coop_reporter());
|
|
|
|
// Navigate away and back using the BackForwardCache. The
|
|
// RenderFrameHostImpl::coop_reporter() must still be there.
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
EXPECT_FALSE(delete_observer_rfh_a.deleted());
|
|
EXPECT_EQ(rfh_a, current_frame_host());
|
|
|
|
EXPECT_TRUE(rfh_a->coop_reporter());
|
|
}
|
|
|
|
// RenderFrameHostImpl::cross_origin_embedder_policy() must be preserved when
|
|
// doing a back navigation using the BackForwardCache.
|
|
// Regression test for https://crbug.com/1021846.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, Coep) {
|
|
ASSERT_TRUE(CreateHttpsServer()->Start());
|
|
GURL url_a(https_server()->GetURL(
|
|
"a.com", "/set-header?Cross-Origin-Embedder-Policy: require-corp"));
|
|
GURL url_b(https_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// Navigate to a document that sets COEP.
|
|
network::CrossOriginEmbedderPolicy coep;
|
|
coep.value = network::mojom::CrossOriginEmbedderPolicyValue::kRequireCorp;
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
EXPECT_EQ(coep, rfh_a->cross_origin_embedder_policy());
|
|
|
|
// Navigate away and back using the BackForwardCache.
|
|
// RenderFrameHostImpl::cross_origin_embedder_policy() should return the same
|
|
// result.
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
EXPECT_FALSE(delete_observer_rfh_a.deleted());
|
|
EXPECT_EQ(rfh_a, current_frame_host());
|
|
|
|
EXPECT_EQ(coep, rfh_a->cross_origin_embedder_policy());
|
|
}
|
|
|
|
namespace {
|
|
|
|
class EchoFakeWithFilter final : public mojom::Echo {
|
|
public:
|
|
explicit EchoFakeWithFilter(mojo::PendingReceiver<mojom::Echo> receiver,
|
|
std::unique_ptr<mojo::MessageFilter> filter)
|
|
: receiver_(this, std::move(receiver)) {
|
|
receiver_.SetFilter(std::move(filter));
|
|
}
|
|
~EchoFakeWithFilter() override = default;
|
|
|
|
// mojom::Echo implementation
|
|
void EchoString(const std::string& input,
|
|
EchoStringCallback callback) override {
|
|
std::move(callback).Run(input);
|
|
}
|
|
|
|
private:
|
|
mojo::Receiver<mojom::Echo> receiver_;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
MessageReceivedOnAssociatedInterfaceWhileCached) {
|
|
DoNotFailForUnexpectedMessagesWhileCached();
|
|
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);
|
|
PageLifecycleStateManagerTestDelegate delegate(
|
|
rfh_a->render_view_host()->GetPageLifecycleStateManager());
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
delegate.WaitForInBackForwardCacheAck();
|
|
ASSERT_FALSE(delete_observer_rfh_a.deleted());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
|
|
mojo::Remote<mojom::Echo> remote;
|
|
EchoFakeWithFilter echo(
|
|
remote.BindNewPipeAndPassReceiver(),
|
|
rfh_a->CreateMessageFilterForAssociatedReceiver(mojom::Echo::Name_));
|
|
|
|
base::RunLoop loop;
|
|
remote->EchoString(
|
|
"", base::BindLambdaForTesting([&](const std::string&) { loop.Quit(); }));
|
|
loop.Run();
|
|
|
|
ExpectBucketCount(
|
|
"BackForwardCache.UnexpectedRendererToBrowserMessage.InterfaceName",
|
|
base::HistogramBase::Sample(
|
|
static_cast<int32_t>(base::HashMetricName(mojom::Echo::Name_))),
|
|
1);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(
|
|
BackForwardCacheBrowserTest,
|
|
MessageReceivedOnAssociatedInterfaceWhileCachedForProcessWithNonCachedPages) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a(embedded_test_server()->GetURL("/title1.html"));
|
|
GURL url_b(embedded_test_server()->GetURL("/title2.html"));
|
|
|
|
// 1) Navigate to A.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
PageLifecycleStateManagerTestDelegate delegate(
|
|
rfh_a->render_view_host()->GetPageLifecycleStateManager());
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
delegate.WaitForInBackForwardCacheAck();
|
|
RenderFrameHostImpl* rfh_b = current_frame_host();
|
|
ASSERT_FALSE(delete_observer_rfh_a.deleted());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
// Make sure both pages are on the same process (they are same site so they
|
|
// should).
|
|
ASSERT_EQ(rfh_a->GetProcess(), rfh_b->GetProcess());
|
|
|
|
mojo::Remote<mojom::Echo> remote;
|
|
EchoFakeWithFilter echo(
|
|
remote.BindNewPipeAndPassReceiver(),
|
|
rfh_a->CreateMessageFilterForAssociatedReceiver(mojom::Echo::Name_));
|
|
|
|
remote->EchoString("", base::NullCallback());
|
|
// Give the killing a chance to run. (We do not expect a kill but need to
|
|
// "wait" for it to not happen)
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
// 3) Go back to A.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
ExpectRestored(FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(
|
|
HighCacheSizeBackForwardCacheBrowserTest,
|
|
MessageReceivedOnAssociatedInterfaceForProcessWithMultipleCachedPages) {
|
|
DoNotFailForUnexpectedMessagesWhileCached();
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a_1(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_a_2(embedded_test_server()->GetURL("a.com", "/title2.html"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// Get url_a_1 and url_a_2 into the cache.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a_1));
|
|
RenderFrameHostImpl* rfh_a_1 = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a_1(rfh_a_1);
|
|
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a_2));
|
|
RenderFrameHostImpl* rfh_a_2 = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a_2(rfh_a_2);
|
|
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
RenderFrameHostImpl* rfh_b = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b);
|
|
|
|
ASSERT_FALSE(delete_observer_rfh_a_1.deleted());
|
|
ASSERT_FALSE(delete_observer_rfh_a_2.deleted());
|
|
EXPECT_TRUE(rfh_a_1->IsInBackForwardCache());
|
|
EXPECT_TRUE(rfh_a_2->IsInBackForwardCache());
|
|
ASSERT_EQ(rfh_a_1->GetProcess(), rfh_a_2->GetProcess());
|
|
|
|
mojo::Remote<mojom::Echo> remote;
|
|
EchoFakeWithFilter echo(
|
|
remote.BindNewPipeAndPassReceiver(),
|
|
rfh_a_1->CreateMessageFilterForAssociatedReceiver(mojom::Echo::Name_));
|
|
|
|
base::RunLoop loop;
|
|
remote->EchoString(
|
|
"", base::BindLambdaForTesting([&](const std::string&) { loop.Quit(); }));
|
|
loop.Run();
|
|
|
|
ExpectBucketCount(
|
|
"BackForwardCache.UnexpectedRendererToBrowserMessage.InterfaceName",
|
|
base::HistogramBase::Sample(
|
|
static_cast<int32_t>(base::HashMetricName(mojom::Echo::Name_))),
|
|
1);
|
|
|
|
EXPECT_FALSE(delete_observer_rfh_b.deleted());
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
MessageReceivedOnAssociatedInterfaceWhileFreezing) {
|
|
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"));
|
|
url::Origin origin_a = url::Origin::Create(url_a);
|
|
url::Origin origin_b = url::Origin::Create(url_b);
|
|
|
|
// 1) Navigate to A.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
PageLifecycleStateManagerTestDelegate delegate(
|
|
rfh_a->render_view_host()->GetPageLifecycleStateManager());
|
|
|
|
mojo::Remote<mojom::Echo> remote;
|
|
EchoFakeWithFilter echo(
|
|
remote.BindNewPipeAndPassReceiver(),
|
|
rfh_a->CreateMessageFilterForAssociatedReceiver(mojom::Echo::Name_));
|
|
|
|
delegate.OnStoreInBackForwardCacheSent(base::BindLambdaForTesting(
|
|
[&]() { remote->EchoString("", base::NullCallback()); }));
|
|
|
|
delegate.OnRestoreFromBackForwardCacheSent(base::BindLambdaForTesting(
|
|
[&]() { remote->EchoString("", base::NullCallback()); }));
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
// 3) Go back to A.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
ExpectRestored(FROM_HERE);
|
|
}
|
|
|
|
// Tests that if a page is already ineligible to be saved in the back-forward
|
|
// cache at navigation time, we shouldn't try to proactively swap
|
|
// BrowsingInstances.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
ShouldNotSwapBrowsingInstanceWhenPageWillNotBeCached) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_1(embedded_test_server()->GetURL("/title1.html"));
|
|
GURL url_2(embedded_test_server()->GetURL("/title2.html"));
|
|
GURL url_3(embedded_test_server()->GetURL("/title3.html"));
|
|
|
|
// 1) Navigate to |url_1| .
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_1));
|
|
RenderFrameHostImpl* rfh_1 = current_frame_host();
|
|
scoped_refptr<SiteInstanceImpl> site_instance_1 =
|
|
static_cast<SiteInstanceImpl*>(rfh_1->GetSiteInstance());
|
|
|
|
// 2) Navigate to |url_2|.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_2));
|
|
RenderFrameHostImpl* rfh_2 = current_frame_host();
|
|
RenderFrameDeletedObserver rfh_2_deleted_observer(rfh_2);
|
|
scoped_refptr<SiteInstanceImpl> site_instance_2 =
|
|
static_cast<SiteInstanceImpl*>(rfh_2->GetSiteInstance());
|
|
|
|
// |rfh_1| should get into the back-forward cache.
|
|
EXPECT_TRUE(rfh_1->IsInBackForwardCache());
|
|
// Check that title1.html and title2.html are in different BrowsingInstances.
|
|
EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(site_instance_2.get()));
|
|
|
|
// Disable the BackForwardCache for |rfh_2|.
|
|
DisableForRenderFrameHostForTesting(rfh_2->GetGlobalFrameRoutingId());
|
|
|
|
// 3) Navigate to |url_3|.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_3));
|
|
RenderFrameHostImpl* rfh_3 = current_frame_host();
|
|
scoped_refptr<SiteInstanceImpl> site_instance_3 =
|
|
static_cast<SiteInstanceImpl*>(rfh_3->GetSiteInstance());
|
|
|
|
// Check that |url_2| and |url_3| are reusing the same SiteInstance (and
|
|
// BrowsingInstance).
|
|
EXPECT_EQ(site_instance_2, site_instance_3);
|
|
if (rfh_2 != rfh_3) {
|
|
// If we aren't reusing the RenderFrameHost then |rfh_2| will eventually
|
|
// get deleted because it's not saved in the back-forward cache.
|
|
rfh_2_deleted_observer.WaitUntilDeleted();
|
|
}
|
|
}
|
|
|
|
// Tests that pagehide and visibilitychange handlers of the old RFH are run for
|
|
// bfcached pages.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
PagehideAndVisibilitychangeRuns) {
|
|
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|.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_1));
|
|
RenderFrameHostImpl* main_frame_1 = web_contents->GetMainFrame();
|
|
|
|
// 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,
|
|
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');
|
|
var dispatched_visibilitychange = false;
|
|
document.onvisibilitychange = function(e) {
|
|
if (dispatched_visibilitychange) {
|
|
// We shouldn't dispatch visibilitychange more than once.
|
|
localStorage.setItem('visibilitychange_storage',
|
|
'dispatched_more_than_once');
|
|
} else if (document.visibilityState != 'hidden') {
|
|
// We should dispatch the event when the visibilityState is 'hidden'.
|
|
localStorage.setItem('visibilitychange_storage', 'not_hidden');
|
|
} else {
|
|
localStorage.setItem('visibilitychange_storage', 'dispatched_once');
|
|
}
|
|
dispatched_visibilitychange = true;
|
|
}
|
|
)"));
|
|
|
|
// 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->GetMainFrame();
|
|
|
|
// Check that the value for 'pagehide_storage' and 'visibilitychange_storage'
|
|
// are set correctly.
|
|
EXPECT_EQ("dispatched_once",
|
|
EvalJs(main_frame_3, "localStorage.getItem('pagehide_storage')"));
|
|
EXPECT_EQ(
|
|
"dispatched_once",
|
|
EvalJs(main_frame_3, "localStorage.getItem('visibilitychange_storage')"));
|
|
}
|
|
|
|
// 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/1170802.
|
|
// TODO(crbug.com/1052397): Revisit once build flag switch of lacros-chrome is
|
|
// complete.
|
|
#if (defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)) || defined(OS_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));
|
|
RenderFrameHostImpl* main_frame_1 = web_contents->GetMainFrame();
|
|
// 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,
|
|
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",
|
|
EvalJs(main_frame_1, "localStorage.getItem('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->GetMainFrame();
|
|
|
|
// Check that the value for 'pagehide_storage' and 'visibilitychange_storage'
|
|
// are set correctly.
|
|
EXPECT_EQ("dispatched_once",
|
|
EvalJs(main_frame_3, "localStorage.getItem('pagehide_storage')"));
|
|
EXPECT_EQ(
|
|
"not_dispatched",
|
|
EvalJs(main_frame_3, "localStorage.getItem('visibilitychange_storage')"));
|
|
}
|
|
|
|
// Tests that we're getting the correct TextInputState and focus updates when a
|
|
// page enters the back-forward cache and when it gets restored.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, 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.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(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);
|
|
}
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
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_EQ(current_frame_host(), 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).
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(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);
|
|
}
|
|
}
|
|
|
|
// We should try to reuse process on same-site renderer-initiated navigations.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
RendererInitiatedSameSiteNavigationReusesProcess) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_1(embedded_test_server()->GetURL("/title1.html"));
|
|
GURL url_2(embedded_test_server()->GetURL("/title2.html"));
|
|
|
|
// Navigate to title1.html.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_1));
|
|
scoped_refptr<SiteInstanceImpl> site_instance_1 =
|
|
web_contents()->GetMainFrame()->GetSiteInstance();
|
|
// Navigate to title2.html. The navigation is document/renderer initiated.
|
|
EXPECT_TRUE(NavigateToURLFromRenderer(shell(), url_2));
|
|
scoped_refptr<SiteInstanceImpl> site_instance_2 =
|
|
web_contents()->GetMainFrame()->GetSiteInstance();
|
|
|
|
// Check that title1.html and title2.html are in different BrowsingInstances
|
|
// but have the same renderer process.
|
|
EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(site_instance_2.get()));
|
|
EXPECT_EQ(site_instance_1->GetProcess(), site_instance_2->GetProcess());
|
|
}
|
|
|
|
// We should try to reuse process on same-site browser-initiated navigations.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
BrowserInitiatedSameSiteNavigationReusesProcess) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_1(embedded_test_server()->GetURL("/title1.html"));
|
|
GURL url_2(embedded_test_server()->GetURL("/title2.html"));
|
|
|
|
// 1) Navigate to title1.html.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_1));
|
|
scoped_refptr<SiteInstanceImpl> site_instance_1 =
|
|
web_contents()->GetMainFrame()->GetSiteInstance();
|
|
// 2) Navigate to title2.html. The navigation is browser initiated.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_2));
|
|
scoped_refptr<SiteInstanceImpl> site_instance_2 =
|
|
web_contents()->GetMainFrame()->GetSiteInstance();
|
|
|
|
// Check that title1.html and title2.html are in different BrowsingInstances
|
|
// but have the same renderer process.
|
|
EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(site_instance_2.get()));
|
|
EXPECT_EQ(site_instance_1->GetProcess(), site_instance_2->GetProcess());
|
|
|
|
// 3) Do a back navigation to title1.html.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
EXPECT_EQ(web_contents()->GetLastCommittedURL(), url_1);
|
|
scoped_refptr<SiteInstanceImpl> site_instance_1_history_nav =
|
|
web_contents()->GetMainFrame()->GetSiteInstance();
|
|
|
|
// We will reuse the SiteInstance and renderer process of |site_instance_1|.
|
|
EXPECT_EQ(site_instance_1_history_nav, site_instance_1);
|
|
EXPECT_EQ(site_instance_1_history_nav->GetProcess(),
|
|
site_instance_1->GetProcess());
|
|
}
|
|
|
|
// We should not try to reuse process on cross-site navigations.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
CrossSiteNavigationDoesNotReuseProcess) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL a1_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
GURL a2_url(embedded_test_server()->GetURL("a.com", "/title2.html"));
|
|
|
|
// Navigate to A1.
|
|
EXPECT_TRUE(NavigateToURL(shell(), a1_url));
|
|
scoped_refptr<SiteInstanceImpl> a1_site_instance =
|
|
web_contents()->GetMainFrame()->GetSiteInstance();
|
|
// Navigate to B. The navigation is browser initiated.
|
|
EXPECT_TRUE(NavigateToURL(shell(), b_url));
|
|
scoped_refptr<SiteInstanceImpl> b_site_instance =
|
|
web_contents()->GetMainFrame()->GetSiteInstance();
|
|
|
|
// Check that A1 and B are in different BrowsingInstances and renderer
|
|
// processes.
|
|
EXPECT_FALSE(a1_site_instance->IsRelatedSiteInstance(b_site_instance.get()));
|
|
EXPECT_NE(a1_site_instance->GetProcess(), b_site_instance->GetProcess());
|
|
|
|
// Navigate to A2. The navigation is renderer-initiated.
|
|
EXPECT_TRUE(NavigateToURLFromRenderer(shell(), a2_url));
|
|
scoped_refptr<SiteInstanceImpl> a2_site_instance =
|
|
web_contents()->GetMainFrame()->GetSiteInstance();
|
|
|
|
// Check that B and A2 are in different BrowsingInstances and renderer
|
|
// processes.
|
|
EXPECT_FALSE(b_site_instance->IsRelatedSiteInstance(a2_site_instance.get()));
|
|
EXPECT_NE(b_site_instance->GetProcess(), a2_site_instance->GetProcess());
|
|
}
|
|
|
|
// Tests that the history value saved in the renderer is updated correctly when
|
|
// a page gets restored from the back-forward cache through browser-initiated
|
|
// navigation.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
RendererHistory_BrowserInitiated) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url1(embedded_test_server()->GetURL(
|
|
"a.com", "/cross_site_iframe_factory.html?a(b)"));
|
|
GURL url2(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
|
|
// 1) Go to |url1|, then |url2|. Both pages should have script to save the
|
|
// history.length value when getting restored from the back-forward cache.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url1));
|
|
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
|
|
FrameTreeNode* subframe = root->child_at(0);
|
|
|
|
std::string restore_time_length_saver_script =
|
|
"var resumeLength = -1;"
|
|
"var pageshowLength = -1;"
|
|
"document.onresume = () => {"
|
|
" resumeLength = history.length;"
|
|
"};"
|
|
"window.onpageshow = () => {"
|
|
" pageshowLength = history.length;"
|
|
"};";
|
|
EXPECT_TRUE(ExecJs(root, restore_time_length_saver_script));
|
|
EXPECT_TRUE(ExecJs(subframe, restore_time_length_saver_script));
|
|
// We should have one history entry.
|
|
EXPECT_EQ(EvalJs(root, "history.length").ExtractInt(), 1);
|
|
EXPECT_EQ(EvalJs(subframe, "history.length").ExtractInt(), 1);
|
|
|
|
EXPECT_TRUE(NavigateToURL(shell(), url2));
|
|
EXPECT_TRUE(ExecJs(root, restore_time_length_saver_script));
|
|
// We should have two history entries.
|
|
EXPECT_EQ(EvalJs(root, "history.length").ExtractInt(), 2);
|
|
|
|
// 2) Go back to |url1|, browser-initiated.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_EQ(web_contents()->GetLastCommittedURL(), url1);
|
|
|
|
// We should still have two history entries, and recorded the correct length
|
|
// when the 'resume' and 'pageshow' events were dispatched.
|
|
EXPECT_EQ(EvalJs(root, "history.length").ExtractInt(), 2);
|
|
EXPECT_EQ(EvalJs(root, "resumeLength").ExtractInt(), 2);
|
|
EXPECT_EQ(EvalJs(root, "pageshowLength").ExtractInt(), 2);
|
|
EXPECT_EQ(EvalJs(subframe, "history.length").ExtractInt(), 2);
|
|
EXPECT_EQ(EvalJs(subframe, "resumeLength").ExtractInt(), 2);
|
|
EXPECT_EQ(EvalJs(subframe, "pageshowLength").ExtractInt(), 2);
|
|
|
|
// 3) Go forward to |url2|, browser-initiated.
|
|
web_contents()->GetController().GoForward();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_EQ(web_contents()->GetLastCommittedURL(), url2);
|
|
|
|
// We should still have two history entries, and recorded the correct length
|
|
// when the 'resume' and 'pageshow' events were dispatched.
|
|
EXPECT_EQ(EvalJs(root, "history.length").ExtractInt(), 2);
|
|
EXPECT_EQ(EvalJs(root, "resumeLength").ExtractInt(), 2);
|
|
EXPECT_EQ(EvalJs(root, "pageshowLength").ExtractInt(), 2);
|
|
}
|
|
|
|
// Tests that the history value saved in the renderer is updated correctly when
|
|
// a page gets restored from the back-forward cache through renderer-initiated
|
|
// navigation.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
RendererHistory_RendererInitiated) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
GURL url1(embedded_test_server()->GetURL(
|
|
"a.com", "/cross_site_iframe_factory.html?a(b)"));
|
|
GURL url2(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
|
|
// 1) Go to |url1|, then |url2|. Both pages should have script to save the
|
|
// history.length value when getting restored from the back-forward cache.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url1));
|
|
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
|
|
FrameTreeNode* subframe = root->child_at(0);
|
|
|
|
std::string restore_time_length_saver_script =
|
|
"var resumeLength = -1;"
|
|
"var pageshowLength = -1;"
|
|
"document.onresume = () => {"
|
|
" resumeLength = history.length;"
|
|
"};"
|
|
"window.onpageshow = () => {"
|
|
" pageshowLength = history.length;"
|
|
"};";
|
|
EXPECT_TRUE(ExecJs(root, restore_time_length_saver_script));
|
|
EXPECT_TRUE(ExecJs(subframe, restore_time_length_saver_script));
|
|
// We should have one history entry.
|
|
EXPECT_EQ(EvalJs(root, "history.length").ExtractInt(), 1);
|
|
EXPECT_EQ(EvalJs(subframe, "history.length").ExtractInt(), 1);
|
|
|
|
EXPECT_TRUE(NavigateToURL(shell(), url2));
|
|
EXPECT_TRUE(ExecJs(root, restore_time_length_saver_script));
|
|
// We should have two history entries.
|
|
EXPECT_EQ(EvalJs(root, "history.length").ExtractInt(), 2);
|
|
|
|
// 2) Go back to |url1|, renderer-initiated.
|
|
EXPECT_TRUE(ExecJs(root, "history.back()"));
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_EQ(web_contents()->GetLastCommittedURL(), url1);
|
|
|
|
// We should still have two history entries, and recorded the correct length
|
|
// when the 'resume' and 'pageshow' events were dispatched.
|
|
EXPECT_EQ(EvalJs(root, "history.length").ExtractInt(), 2);
|
|
EXPECT_EQ(EvalJs(root, "resumeLength").ExtractInt(), 2);
|
|
EXPECT_EQ(EvalJs(root, "pageshowLength").ExtractInt(), 2);
|
|
EXPECT_EQ(EvalJs(subframe, "history.length").ExtractInt(), 2);
|
|
EXPECT_EQ(EvalJs(subframe, "resumeLength").ExtractInt(), 2);
|
|
EXPECT_EQ(EvalJs(subframe, "pageshowLength").ExtractInt(), 2);
|
|
|
|
// 3) Go forward to |url2|, renderer-initiated.
|
|
EXPECT_TRUE(ExecJs(root, "history.forward()"));
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
EXPECT_EQ(web_contents()->GetLastCommittedURL(), url2);
|
|
|
|
// We should still have two history entries, and recorded the correct length
|
|
// when the 'resume' and 'pageshow' events were dispatched.
|
|
EXPECT_EQ(EvalJs(root, "history.length").ExtractInt(), 2);
|
|
EXPECT_EQ(EvalJs(root, "resumeLength").ExtractInt(), 2);
|
|
EXPECT_EQ(EvalJs(root, "pageshowLength").ExtractInt(), 2);
|
|
}
|
|
|
|
// This observer keeps tracks whether a given RenderViewHost is deleted or not
|
|
// to avoid accessing it and causing use-after-free condition.
|
|
class RenderViewHostDeletedObserver : public WebContentsObserver {
|
|
public:
|
|
explicit RenderViewHostDeletedObserver(RenderViewHost* rvh)
|
|
: WebContentsObserver(WebContents::FromRenderViewHost(rvh)),
|
|
render_view_host_(rvh),
|
|
deleted_(false) {}
|
|
|
|
void RenderViewDeleted(RenderViewHost* render_view_host) override {
|
|
if (render_view_host_ == render_view_host)
|
|
deleted_ = true;
|
|
}
|
|
|
|
bool deleted() const { return deleted_; }
|
|
|
|
private:
|
|
RenderViewHost* render_view_host_;
|
|
bool deleted_;
|
|
};
|
|
|
|
// Tests that RenderViewHost is deleted on eviction along with
|
|
// RenderProcessHost.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
RenderViewHostDeletedOnEviction) {
|
|
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"));
|
|
|
|
NavigationControllerImpl& controller = web_contents()->GetController();
|
|
BackForwardCacheImpl& cache = controller.GetBackForwardCache();
|
|
|
|
// 1) Navigate to A.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
RenderViewHostDeletedObserver delete_observer_rvh_a(
|
|
rfh_a->GetRenderViewHost());
|
|
|
|
RenderProcessHost* process = rfh_a->GetProcess();
|
|
RenderProcessHostWatcher destruction_observer(
|
|
process, RenderProcessHostWatcher::WATCH_FOR_HOST_DESTRUCTION);
|
|
cache.Flush();
|
|
|
|
// 2) Navigate to B. A should be stored in cache, count of entries should
|
|
// be 1.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
EXPECT_EQ(1u, cache.GetEntries().size());
|
|
|
|
// 3) Initiate eviction of rfh_a from BackForwardCache. Entries should be 0.
|
|
// RenderViewHost, RenderProcessHost and RenderFrameHost should all be
|
|
// deleted.
|
|
EXPECT_TRUE(rfh_a->IsInactiveAndDisallowActivation());
|
|
destruction_observer.Wait();
|
|
ASSERT_TRUE(delete_observer_rvh_a.deleted());
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
EXPECT_EQ(0u, cache.GetEntries().size());
|
|
}
|
|
|
|
// Tests that cross-process sub-frame's RenderViewHost is deleted on root
|
|
// RenderFrameHost eviction from BackForwardCache along with its
|
|
// RenderProcessHost.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
CrossProcessSubFrameRenderViewHostDeletedOnEviction) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a(embedded_test_server()->GetURL(
|
|
"a.com", "/cross_site_iframe_factory.html?a(b)"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// 1) Navigate to A.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* a1 = current_frame_host();
|
|
RenderFrameHostImpl* b1 = a1->child_at(0)->current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_b1(b1);
|
|
|
|
RenderViewHostDeletedObserver delete_observer_rvh_b1(b1->GetRenderViewHost());
|
|
|
|
RenderProcessHost* process = b1->GetProcess();
|
|
RenderProcessHostWatcher destruction_observer(
|
|
process, RenderProcessHostWatcher::WATCH_FOR_HOST_DESTRUCTION);
|
|
|
|
// 2) Navigate to URL B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
EXPECT_TRUE(a1->IsInBackForwardCache());
|
|
|
|
// 3) Initiate eviction of rfh a1 from BackForwardCache. RenderViewHost,
|
|
// RenderProcessHost and RenderFrameHost of sub-frame b1 should all be deleted
|
|
// on eviction.
|
|
EXPECT_TRUE(a1->IsInactiveAndDisallowActivation());
|
|
destruction_observer.Wait();
|
|
ASSERT_TRUE(delete_observer_rvh_b1.deleted());
|
|
delete_observer_rfh_b1.WaitUntilDeleted();
|
|
}
|
|
|
|
// Tests that same-process sub-frame's RenderViewHost is deleted on root
|
|
// RenderFrameHost eviction from BackForwardCache along with its
|
|
// RenderProcessHost.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
SameProcessSubFrameRenderViewHostDeletedOnEviction) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a(embedded_test_server()->GetURL(
|
|
"a.com", "/cross_site_iframe_factory.html?a(a)"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// 1) Navigate to A.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* a1 = current_frame_host();
|
|
RenderFrameHostImpl* a2 = a1->child_at(0)->current_frame_host();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a2(a2);
|
|
|
|
RenderViewHostDeletedObserver delete_observer_rvh_a2(a2->GetRenderViewHost());
|
|
|
|
RenderProcessHost* process = a2->GetProcess();
|
|
RenderProcessHostWatcher destruction_observer(
|
|
process, RenderProcessHostWatcher::WATCH_FOR_HOST_DESTRUCTION);
|
|
|
|
// 2) Navigate to URL B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
EXPECT_TRUE(a1->IsInBackForwardCache());
|
|
|
|
// 3) Initiate eviction of rfh a1 from BackForwardCache. RenderViewHost,
|
|
// RenderProcessHost and RenderFrameHost of sub-frame a2 should all be
|
|
// deleted.
|
|
EXPECT_TRUE(a1->IsInactiveAndDisallowActivation());
|
|
destruction_observer.Wait();
|
|
ASSERT_TRUE(delete_observer_rvh_a2.deleted());
|
|
delete_observer_rfh_a2.WaitUntilDeleted();
|
|
}
|
|
|
|
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.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->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.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->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());
|
|
};
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
NavigationCancelledAfterJsEvictionWasDisabled) {
|
|
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);
|
|
|
|
PageLifecycleStateManagerTestDelegate delegate(
|
|
rfh_a->render_view_host()->GetPageLifecycleStateManager());
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
RenderFrameHostImpl* rfh_b = current_frame_host();
|
|
|
|
delegate.OnDisableJsEvictionSent(base::BindLambdaForTesting([&]() {
|
|
// Posted because Stop() will destroy the NavigationRequest but
|
|
// DisableJsEviction will be called from inside the navigation which may
|
|
// not be a safe place to destruct a NavigationRequest.
|
|
base::ThreadTaskRunnerHandle::Get()->PostTask(
|
|
FROM_HERE, base::BindOnce(&WebContentsImpl::Stop,
|
|
base::Unretained(web_contents())));
|
|
}));
|
|
|
|
// 3) Do not go back to A (navigation cancelled).
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
EXPECT_EQ(rfh_b, current_frame_host());
|
|
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
|
|
// 4) Go back to A.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason::
|
|
kNavigationCancelledWhileRestoring},
|
|
{}, {}, {}, FROM_HERE);
|
|
}
|
|
|
|
// 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));
|
|
|
|
// 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.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
// This about:blank document does not have a SiteInstance and then loading a
|
|
// page on it doesn't swap the browsing instance.
|
|
ExpectNotRestored(
|
|
{
|
|
BackForwardCacheMetrics::NotRestoredReason::
|
|
kBrowsingInstanceNotSwapped,
|
|
},
|
|
{}, {ShouldSwapBrowsingInstance::kNo_DoesNotHaveSite}, {}, FROM_HERE);
|
|
}
|
|
|
|
// Check that the page is not cached when navigating to about:blank.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
NavigatingToAboutBlankPreventsCaching) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
// 1) Navigate to a.com,
|
|
GURL url_a(embedded_test_server()->GetURL("a.com", "/empty.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
|
|
// 2) Navigate to about:blank.
|
|
GURL blank_url(url::kAboutBlankURL);
|
|
EXPECT_TRUE(NavigateToURL(shell(), blank_url));
|
|
|
|
// 3) Navigate back to a.com.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
// about:blank doesn't have a scheme http or https, and then this navigation
|
|
// doesn't swapt the browsing instance.
|
|
ExpectNotRestored(
|
|
{
|
|
BackForwardCacheMetrics::NotRestoredReason::
|
|
kBrowsingInstanceNotSwapped,
|
|
},
|
|
{},
|
|
{ShouldSwapBrowsingInstance::kNo_DestinationURLSchemeIsNotHTTPOrHTTPS},
|
|
{}, 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));
|
|
RenderFrameHostImpl* 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));
|
|
RenderFrameHostImpl* rfh_b = current_frame_host();
|
|
EXPECT_EQ(2, controller.GetEntryCount());
|
|
|
|
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.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_EQ(2, controller.GetEntryCount());
|
|
EXPECT_EQ(url_a, controller.GetLastCommittedEntry()->GetURL());
|
|
|
|
// TODO(crbug.com/1198030): Investigate whether these navigation results are
|
|
// expected.
|
|
|
|
ExpectNotRestored(
|
|
{
|
|
BackForwardCacheMetrics::NotRestoredReason::
|
|
kBrowsingInstanceNotSwapped,
|
|
},
|
|
{}, {ShouldSwapBrowsingInstance::kNo_SameUrlNavigation}, {}, FROM_HERE);
|
|
}
|
|
|
|
// Check that the response 204 No Content doesn't affect back-forward cache.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, NoContent) {
|
|
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) Navigate to c.com with 204 No Content, then the URL will still be b.com.
|
|
GURL url_c(embedded_test_server()->GetURL("c.com", "/echo?status=204"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_c, url_b));
|
|
EXPECT_EQ(2, controller.GetEntryCount());
|
|
EXPECT_EQ(url_b, controller.GetLastCommittedEntry()->GetURL());
|
|
|
|
// 4) Navigate back to a.com.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_EQ(2, controller.GetEntryCount());
|
|
EXPECT_EQ(url_a, controller.GetLastCommittedEntry()->GetURL());
|
|
|
|
ExpectRestored(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.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(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/1176061): 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.
|
|
web_contents()->GetController().GoForward();
|
|
EXPECT_TRUE(WaitForLoadStop(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.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(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);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
SubframeNavigationDoesNotRecordMetrics) {
|
|
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(B).
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
|
|
// 2) Navigate from B to C.
|
|
EXPECT_TRUE(NavigateFrameToURL(rfh_a->child_at(0), url_c));
|
|
EXPECT_EQ(url_c,
|
|
rfh_a->child_at(0)->current_frame_host()->GetLastCommittedURL());
|
|
EXPECT_FALSE(rfh_a->IsInBackForwardCache());
|
|
|
|
// 4) Go back from C to B.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_TRUE(
|
|
rfh_a->child_at(0)->current_frame_host()->GetLastCommittedURL().DomainIs(
|
|
"b.com"));
|
|
EXPECT_FALSE(rfh_a->IsInBackForwardCache());
|
|
|
|
// The reason why the frame is not cached in a subframe navigation is not
|
|
// recorded.
|
|
ExpectOutcomeDidNotChange(FROM_HERE);
|
|
}
|
|
|
|
class BackForwardCacheBrowserTestWithFileSystemAPISupported
|
|
: public BackForwardCacheBrowserTest {
|
|
protected:
|
|
void SetUpCommandLine(base::CommandLine* command_line) override {
|
|
EnableFeatureAndSetParams(features::kBackForwardCache,
|
|
"file_system_api_supported", "true");
|
|
BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
|
|
}
|
|
};
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithFileSystemAPISupported,
|
|
DISABLED_CacheWithFileSystemAPI) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a(embedded_test_server()->GetURL("/fileapi/request_test.html"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// 1) Navigate to a page with WebFileSystem usage.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver deleted(rfh_a);
|
|
|
|
// 2) Navigate away.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
EXPECT_FALSE(deleted.deleted());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
|
|
// 3) Go back to the page with WebFileSystem.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_EQ(rfh_a, current_frame_host());
|
|
ExpectRestored(FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
EnsureIsolationInfoForSubresourcesNotEmpty) {
|
|
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"));
|
|
|
|
NavigationControllerImpl& controller = web_contents()->GetController();
|
|
BackForwardCacheImpl& cache = controller.GetBackForwardCache();
|
|
|
|
// 1) Navigate to A.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
|
|
cache.Flush();
|
|
|
|
// 2) Navigate to B. A should be stored in cache, count of entries should
|
|
// be 1.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
RenderFrameHostImpl* rfh_b = current_frame_host();
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
EXPECT_EQ(1u, cache.GetEntries().size());
|
|
|
|
// 3) GoBack to A. RenderFrameHost of A should be restored and B should be
|
|
// stored in cache, count of entries should be 1. IsolationInfoForSubresources
|
|
// of rfh_a should not be empty.
|
|
controller.GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_EQ(rfh_a, current_frame_host());
|
|
EXPECT_TRUE(rfh_b->IsInBackForwardCache());
|
|
EXPECT_EQ(1u, cache.GetEntries().size());
|
|
EXPECT_FALSE(rfh_a->GetIsolationInfoForSubresources().IsEmpty());
|
|
|
|
// 4) GoForward to B. RenderFrameHost of B should be restored and A should be
|
|
// stored in cache, count of entries should be 1. IsolationInfoForSubresources
|
|
// of rfh_b should not be empty.
|
|
controller.GoForward();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_EQ(rfh_b, current_frame_host());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
EXPECT_EQ(1u, cache.GetEntries().size());
|
|
EXPECT_FALSE(rfh_b->GetIsolationInfoForSubresources().IsEmpty());
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
DoNotCacheIfMediaSessionExists) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
// 1) Navigate to a page using MediaSession.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("a.com", "/title1.html")));
|
|
RenderFrameHost* rfh_a = shell()->web_contents()->GetMainFrame();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
EXPECT_TRUE(ExecJs(rfh_a, R"(
|
|
navigator.mediaSession.metadata = new MediaMetadata({
|
|
artwork: [
|
|
{src: "test_image.jpg", sizes: "1x1", type: "image/jpeg"},
|
|
{src: "test_image.jpg", sizes: "10x10", type: "image/jpeg"}
|
|
]
|
|
});
|
|
)"));
|
|
|
|
// 2) Navigate away.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
|
|
|
|
// The page should not have been cached in the back forward cache.
|
|
delete_observer_rfh_a.WaitUntilDeleted();
|
|
|
|
// 3) Go back.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
auto reason = BackForwardCacheDisable::DisabledReason(
|
|
BackForwardCacheDisable::DisabledReasonId::
|
|
kMediaSessionImplOnServiceCreated);
|
|
ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason::
|
|
kDisableForRenderFrameHostCalled},
|
|
{}, {}, {reason}, FROM_HERE);
|
|
}
|
|
|
|
class BackForwardCacheBrowserTestWithSupportedFeatures
|
|
: public BackForwardCacheBrowserTest {
|
|
protected:
|
|
void SetUpCommandLine(base::CommandLine* command_line) override {
|
|
EnableFeatureAndSetParams(features::kBackForwardCache, "supported_features",
|
|
"BroadcastChannel,KeyboardLock");
|
|
BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
|
|
}
|
|
};
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithSupportedFeatures,
|
|
CacheWithSpecifiedFeatures) {
|
|
ASSERT_TRUE(CreateHttpsServer()->Start());
|
|
|
|
GURL url_a(https_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_b(https_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// 1) Navigate to the page A with BroadcastChannel.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
RenderFrameDeletedObserver deleted(rfh_a);
|
|
EXPECT_TRUE(ExecJs(rfh_a, "window.foo = new BroadcastChannel('foo');"));
|
|
|
|
// 2) Navigate away.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
EXPECT_FALSE(deleted.deleted());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
|
|
// 3) Go back to the page A
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_EQ(rfh_a, current_frame_host());
|
|
ExpectRestored(FROM_HERE);
|
|
|
|
// 4) Use KeyboardLock
|
|
EXPECT_EQ("DONE", EvalJs(rfh_a, R"(
|
|
new Promise(resolve => {
|
|
navigator.keyboard.lock();
|
|
resolve('DONE');
|
|
});
|
|
)"));
|
|
|
|
// 5) Navigate away again.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
EXPECT_FALSE(deleted.deleted());
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
|
|
// 6) Go back to the page A again.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_EQ(rfh_a, current_frame_host());
|
|
ExpectRestored(FROM_HERE);
|
|
}
|
|
|
|
class BackForwardCacheBrowserTestWithNoSupportedFeatures
|
|
: public BackForwardCacheBrowserTest {
|
|
protected:
|
|
void SetUpCommandLine(base::CommandLine* command_line) override {
|
|
// Specify empty supported features explicitly.
|
|
EnableFeatureAndSetParams(features::kBackForwardCache, "supported_features",
|
|
"");
|
|
BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
|
|
}
|
|
};
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithNoSupportedFeatures,
|
|
DontCache) {
|
|
ASSERT_TRUE(CreateHttpsServer()->Start());
|
|
|
|
GURL url_a(https_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_b(https_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// 1) Navigate to the page A with BoradcastChannel.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
RenderFrameHostImpl* rfh_a1 = current_frame_host();
|
|
RenderFrameDeletedObserver deleted_a1(rfh_a1);
|
|
EXPECT_TRUE(ExecJs(rfh_a1, "window.foo = new BroadcastChannel('foo');"));
|
|
|
|
// 2) Navigate away.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
deleted_a1.WaitUntilDeleted();
|
|
|
|
// 3) Go back to the page A
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kBlocklistedFeatures},
|
|
{blink::scheduler::WebSchedulerTrackedFeature::kBroadcastChannel}, {}, {},
|
|
FROM_HERE);
|
|
|
|
RenderFrameHostImpl* rfh_a2 = current_frame_host();
|
|
RenderFrameDeletedObserver deleted_a2(rfh_a2);
|
|
|
|
// 4) Use KeyboardLock
|
|
EXPECT_EQ("DONE", EvalJs(rfh_a2, R"(
|
|
new Promise(resolve => {
|
|
navigator.keyboard.lock();
|
|
resolve('DONE');
|
|
});
|
|
)"));
|
|
|
|
// 5) Navigate away again.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
deleted_a2.WaitUntilDeleted();
|
|
|
|
// 6) Go back to the page A again.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kBlocklistedFeatures},
|
|
{blink::scheduler::WebSchedulerTrackedFeature::kKeyboardLock}, {}, {},
|
|
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())
|
|
->GetFrameTree()
|
|
->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());
|
|
}
|
|
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());
|
|
|
|
// 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());
|
|
}
|
|
|
|
// 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());
|
|
}
|
|
}
|
|
|
|
// Regression test for crbug.com/1183313, but for is_overriding_user_agent.
|
|
// Checks that we won't restore an entry from the BackForwardCache if the
|
|
// is_overriding_user_agent value used in the entry differs from the one used
|
|
// in the restoring navigation.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
DoNotRestoreWhenIsOverridingUserAgentDiffers) {
|
|
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"));
|
|
NavigationControllerImpl& controller = web_contents()->GetController();
|
|
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
|
|
->GetFrameTree()
|
|
->root();
|
|
|
|
const std::string user_agent_override = "foo";
|
|
|
|
// 1) Navigate to A without user agent override.
|
|
{
|
|
FrameNavigateParamsCapturer params_capturer(root);
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
params_capturer.Wait();
|
|
EXPECT_FALSE(params_capturer.is_overriding_user_agent());
|
|
EXPECT_NE(user_agent_override,
|
|
EvalJs(shell()->web_contents(), "navigator.userAgent"));
|
|
}
|
|
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
|
|
// Enable user agent override for future navigations.
|
|
UserAgentInjector injector(shell()->web_contents(), user_agent_override);
|
|
|
|
// 2) Navigate to B with user agent override.
|
|
{
|
|
FrameNavigateParamsCapturer params_capturer(root);
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
params_capturer.Wait();
|
|
EXPECT_TRUE(params_capturer.is_overriding_user_agent());
|
|
EXPECT_EQ(user_agent_override,
|
|
EvalJs(shell()->web_contents(), "navigator.userAgent"));
|
|
}
|
|
|
|
// A should be stored in the back-forward cache.
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
|
|
RenderFrameHostImpl* rfh_b = current_frame_host();
|
|
|
|
// 3) Go back to A. RenderFrameHost of A should not be restored from the
|
|
// back-forward cache, and "is_overriding_user_agent" is set to true
|
|
// correctly.
|
|
{
|
|
RenderFrameDeletedObserver delete_observer(rfh_a);
|
|
FrameNavigateParamsCapturer params_capturer(root);
|
|
controller.GoBack();
|
|
params_capturer.Wait();
|
|
delete_observer.WaitUntilDeleted();
|
|
EXPECT_TRUE(params_capturer.is_overriding_user_agent());
|
|
EXPECT_EQ(user_agent_override,
|
|
EvalJs(shell()->web_contents(), "navigator.userAgent"));
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kUserAgentOverrideDiffers},
|
|
{}, {}, {}, FROM_HERE);
|
|
}
|
|
|
|
// B should be stored in the back-forward cache.
|
|
EXPECT_TRUE(rfh_b->IsInBackForwardCache());
|
|
|
|
// 4) Go forward to B. RenderFrameHost of B should be restored from the
|
|
// back-forward cache, and "is_overriding_user_agent" is set to true
|
|
// correctly.
|
|
{
|
|
FrameNavigateParamsCapturer params_capturer(root);
|
|
controller.GoForward();
|
|
params_capturer.Wait();
|
|
EXPECT_TRUE(params_capturer.is_overriding_user_agent());
|
|
EXPECT_EQ(user_agent_override,
|
|
EvalJs(shell()->web_contents(), "navigator.userAgent"));
|
|
EXPECT_EQ(rfh_b, current_frame_host());
|
|
ExpectRestored(FROM_HERE);
|
|
}
|
|
|
|
// Stop overriding user agent from now on.
|
|
injector.set_is_overriding_user_agent(false);
|
|
|
|
// 5) Go to C, which should not do a user agent override.
|
|
{
|
|
FrameNavigateParamsCapturer params_capturer(root);
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_c));
|
|
params_capturer.Wait();
|
|
EXPECT_FALSE(params_capturer.is_overriding_user_agent());
|
|
EXPECT_NE(user_agent_override,
|
|
EvalJs(shell()->web_contents(), "navigator.userAgent"));
|
|
}
|
|
|
|
// B should be stored in the back-forward cache again.
|
|
EXPECT_TRUE(rfh_b->IsInBackForwardCache());
|
|
|
|
// 6) Go back to B. RenderFrameHost of B should not be restored from the
|
|
// back-forward cache, and "is_overriding_user_agent" is set to false
|
|
// correctly.
|
|
{
|
|
FrameNavigateParamsCapturer params_capturer(root);
|
|
RenderFrameDeletedObserver delete_observer(rfh_b);
|
|
controller.GoBack();
|
|
params_capturer.Wait();
|
|
delete_observer.WaitUntilDeleted();
|
|
EXPECT_FALSE(params_capturer.is_overriding_user_agent());
|
|
EXPECT_NE(user_agent_override,
|
|
EvalJs(shell()->web_contents(), "navigator.userAgent"));
|
|
ExpectNotRestored(
|
|
{BackForwardCacheMetrics::NotRestoredReason::kUserAgentOverrideDiffers},
|
|
{}, {}, {}, FROM_HERE);
|
|
}
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
RestoreWhenUserAgentOverrideDiffers) {
|
|
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"));
|
|
NavigationControllerImpl& controller = web_contents()->GetController();
|
|
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
|
|
->GetFrameTree()
|
|
->root();
|
|
|
|
// Enable user agent override for future navigations.
|
|
const std::string user_agent_override_1 = "foo";
|
|
UserAgentInjector injector(shell()->web_contents(), user_agent_override_1);
|
|
|
|
// 1) Start a new navigation to A with user agent override.
|
|
{
|
|
FrameNavigateParamsCapturer params_capturer(root);
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
params_capturer.Wait();
|
|
EXPECT_TRUE(params_capturer.is_overriding_user_agent());
|
|
EXPECT_EQ(user_agent_override_1,
|
|
EvalJs(shell()->web_contents(), "navigator.userAgent"));
|
|
}
|
|
|
|
RenderFrameHostImpl* rfh_a = current_frame_host();
|
|
|
|
// 2) Navigate to another page.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
// A should be stored in the back-forward cache.
|
|
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
|
|
|
|
// Change the user agent override string.
|
|
const std::string user_agent_override_2 = "bar";
|
|
injector.set_user_agent_override(user_agent_override_2);
|
|
|
|
// 3) Go back to A, which should restore the page saved in the back-forward
|
|
// cache and use the old user agent.
|
|
// TODO(https://crbug.com/1194880): This should use the new UA override.
|
|
{
|
|
FrameNavigateParamsCapturer params_capturer(root);
|
|
controller.GoBack();
|
|
params_capturer.Wait();
|
|
EXPECT_TRUE(params_capturer.is_overriding_user_agent());
|
|
EXPECT_EQ(user_agent_override_1,
|
|
EvalJs(shell()->web_contents(), "navigator.userAgent"));
|
|
EXPECT_EQ(rfh_a, current_frame_host());
|
|
ExpectRestored(FROM_HERE);
|
|
}
|
|
|
|
// 4) Navigate to another page, which should use the new user agent. Note that
|
|
// we didn't do this in step 2 instead because the UA override change during
|
|
// navigation would trigger a RendererPreferences to the active page (page A).
|
|
{
|
|
FrameNavigateParamsCapturer params_capturer(root);
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
params_capturer.Wait();
|
|
EXPECT_TRUE(params_capturer.is_overriding_user_agent());
|
|
EXPECT_EQ(user_agent_override_2,
|
|
EvalJs(shell()->web_contents(), "navigator.userAgent"));
|
|
}
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
WebContentsDestroyedWhileRestoringThePageFromBFCache) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
Shell* shell = CreateBrowser();
|
|
|
|
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));
|
|
|
|
// 2) Navigate to another page.
|
|
EXPECT_TRUE(NavigateToURL(shell, url_b));
|
|
|
|
// 3) Start navigating back.
|
|
TestNavigationManager nav_manager(shell->web_contents(), url_a);
|
|
shell->web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(nav_manager.WaitForRequestStart());
|
|
|
|
testing::NiceMock<MockWebContentsObserver> observer(shell->web_contents());
|
|
EXPECT_CALL(observer, DidFinishNavigation(_))
|
|
.WillOnce(testing::Invoke([](NavigationHandle* handle) {
|
|
EXPECT_FALSE(handle->HasCommitted());
|
|
EXPECT_TRUE(handle->IsServedFromBackForwardCache());
|
|
// This call checks that |rfh_restored_from_back_forward_cache| is not
|
|
// deleted and the virtual |GetRoutingID| does not crash.
|
|
EXPECT_TRUE(NavigationRequest::From(handle)
|
|
->rfh_restored_from_back_forward_cache()
|
|
->GetRoutingID());
|
|
}));
|
|
|
|
shell->Close();
|
|
}
|
|
|
|
class BackForwardCacheBrowserTestWithMediaSession
|
|
: public BackForwardCacheBrowserTest {
|
|
protected:
|
|
void SetUpCommandLine(base::CommandLine* command_line) override {
|
|
EnableFeatureAndSetParams(features::kBackForwardCache, "supported_features",
|
|
"MediaSessionImplOnServiceCreated");
|
|
BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
|
|
}
|
|
};
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithMediaSession,
|
|
DoNotCacheIfMediaSessionPlaybackStateChanged) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
// 1) Navigate to a page using MediaSession.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("a.com", "/title1.html")));
|
|
RenderFrameHost* rfh_a = shell()->web_contents()->GetMainFrame();
|
|
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
|
|
EXPECT_TRUE(ExecJs(rfh_a, R"(
|
|
navigator.mediaSession.metadata = new MediaMetadata({
|
|
artwork: [
|
|
{src: "test_image.jpg", sizes: "1x1", type: "image/jpeg"},
|
|
{src: "test_image.jpg", sizes: "10x10", type: "image/jpeg"}
|
|
]
|
|
});
|
|
)"));
|
|
|
|
// 2) Navigate away.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
|
|
|
|
// 3) Go back.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
// The page is restored since the playback state is not changed.
|
|
ExpectRestored(FROM_HERE);
|
|
|
|
// 4) Modify the playback state of the media session.
|
|
EXPECT_TRUE(ExecJs(rfh_a, R"(
|
|
navigator.mediaSession.playbackState = 'playing';
|
|
)"));
|
|
|
|
// 5) Navigate away.
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
|
|
|
|
// 6) Go back.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
// The page is not restored due to the playback.
|
|
auto reason = BackForwardCacheDisable::DisabledReason(
|
|
BackForwardCacheDisable::DisabledReasonId::kMediaSession);
|
|
ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason::
|
|
kDisableForRenderFrameHostCalled},
|
|
{}, {}, {reason}, FROM_HERE);
|
|
}
|
|
|
|
// Test if the delegate doesn't support BFCache that the reason is
|
|
// recorded correctly.
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
|
|
DelegateDoesNotSupportBackForwardCache) {
|
|
// Set the delegate to null to force the default behavior.
|
|
web_contents()->SetDelegate(nullptr);
|
|
|
|
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 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);
|
|
|
|
web_contents()->GetController().GoToOffset(-1);
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason::
|
|
kBackForwardCacheDisabledForDelegate},
|
|
{}, {}, {}, FROM_HERE);
|
|
}
|
|
|
|
class BackForwardCacheOptInBrowserTest : public BackForwardCacheBrowserTest {
|
|
protected:
|
|
void SetUpCommandLine(base::CommandLine* command_line) override {
|
|
EnableFeatureAndSetParams(features::kBackForwardCache,
|
|
"opt_in_header_required", "true");
|
|
BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
|
|
}
|
|
};
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheOptInBrowserTest, NoCacheWithoutHeader) {
|
|
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));
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
// 3) Go back.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason::
|
|
kOptInUnloadHeaderNotPresent},
|
|
{}, {}, {}, FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheOptInBrowserTest,
|
|
CacheIfHeaderIsPresent) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url_a(embedded_test_server()->GetURL("a.com",
|
|
"/set-header?"
|
|
"BFCache-Opt-In: unload"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// 1) Navigate to A.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
// 3) Go back.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
ExpectRestored(FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(BackForwardCacheOptInBrowserTest,
|
|
NoCacheIfHeaderOnlyPresentOnDestinationPage) {
|
|
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",
|
|
"/set-header?"
|
|
"BFCache-Opt-In: unload"));
|
|
|
|
// 1) Navigate to A.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
// 3) Go back. - A doesn't have header so it shouldn't be cached.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason::
|
|
kOptInUnloadHeaderNotPresent},
|
|
{}, {}, {}, FROM_HERE);
|
|
|
|
// 4) Go forward. - B has the header, so it should be cached.
|
|
web_contents()->GetController().GoForward();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
ExpectRestored(FROM_HERE);
|
|
}
|
|
|
|
class BackForwardCacheUnloadStrategyBrowserTest
|
|
: public BackForwardCacheBrowserTest,
|
|
public testing::WithParamInterface<std::string> {
|
|
protected:
|
|
void SetUpCommandLine(base::CommandLine* command_line) override {
|
|
EnableFeatureAndSetParams(features::kBackForwardCache, "unload_support",
|
|
GetParam());
|
|
BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
|
|
}
|
|
|
|
void 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, "window.onunload = () => 42;"));
|
|
}
|
|
};
|
|
|
|
INSTANTIATE_TEST_SUITE_P(All,
|
|
BackForwardCacheUnloadStrategyBrowserTest,
|
|
testing::Values("always",
|
|
"opt_in_header_required",
|
|
"no"));
|
|
|
|
IN_PROC_BROWSER_TEST_P(BackForwardCacheUnloadStrategyBrowserTest,
|
|
UnloadHandlerPresentWithOptInHeader) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
GURL url_a(embedded_test_server()->GetURL("a.com",
|
|
"/set-header?"
|
|
"BFCache-Opt-In: unload"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// 1) Navigate to A.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
EXPECT_TRUE(ExecJs(current_frame_host(), "window.onunload = () => 42;"));
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
// 3) Go back.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
if (GetParam() == "always" || GetParam() == "opt_in_header_required") {
|
|
ExpectRestored(FROM_HERE);
|
|
} else {
|
|
ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason::
|
|
kUnloadHandlerExistsInMainFrame},
|
|
{}, {}, {}, FROM_HERE);
|
|
}
|
|
|
|
// 4) Go forward.
|
|
web_contents()->GetController().GoForward();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
ExpectRestored(FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_P(BackForwardCacheUnloadStrategyBrowserTest,
|
|
UnloadHandlerPresentWithoutOptInHeader) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
GURL url_a(embedded_test_server()->GetURL("a.com", "/unload.html"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// 1) Navigate to A.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
// 3) Go back.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
if (GetParam() == "always") {
|
|
ExpectRestored(FROM_HERE);
|
|
} else if (GetParam() == "opt_in_header_required") {
|
|
ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason::
|
|
kOptInUnloadHeaderNotPresent},
|
|
{}, {}, {}, FROM_HERE);
|
|
} else {
|
|
ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason::
|
|
kUnloadHandlerExistsInMainFrame},
|
|
{}, {}, {}, FROM_HERE);
|
|
}
|
|
|
|
// 4) Go forward.
|
|
web_contents()->GetController().GoForward();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
ExpectRestored(FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_P(BackForwardCacheUnloadStrategyBrowserTest,
|
|
UnloadHandlerPresentInSubFrameWithOptInHeader) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
GURL url_a(embedded_test_server()->GetURL("a.com",
|
|
"/set-header?"
|
|
"BFCache-Opt-In: unload"));
|
|
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
|
|
|
|
// 1) Navigate to A.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
InstallUnloadHandlerOnSubFrame();
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
// 3) Go back.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
if (GetParam() == "always" || GetParam() == "opt_in_header_required") {
|
|
ExpectRestored(FROM_HERE);
|
|
} else {
|
|
ASSERT_EQ("no", GetParam());
|
|
ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason::
|
|
kUnloadHandlerExistsInSubFrame},
|
|
{}, {}, {}, FROM_HERE);
|
|
}
|
|
|
|
// 4) Go forward.
|
|
web_contents()->GetController().GoForward();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
ExpectRestored(FROM_HERE);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_P(BackForwardCacheUnloadStrategyBrowserTest,
|
|
UnloadHandlerPresentInSubFrameWithoutOptInHeader) {
|
|
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));
|
|
InstallUnloadHandlerOnSubFrame();
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
|
|
// 3) Go back.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
if (GetParam() == "always") {
|
|
ExpectRestored(FROM_HERE);
|
|
} else {
|
|
ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason::
|
|
kUnloadHandlerExistsInSubFrame},
|
|
{}, {}, {}, FROM_HERE);
|
|
}
|
|
|
|
// 4) Go forward.
|
|
web_contents()->GetController().GoForward();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
ExpectRestored(FROM_HERE);
|
|
}
|
|
|
|
} // namespace content
|