| // 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/chromecast_buildflags.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_can_store_document_result.h" |
| #include "content/browser/renderer_host/back_forward_cache_disable.h" |
| #include "content/browser/renderer_host/back_forward_cache_impl.h" |
| #include "content/browser/renderer_host/frame_tree_node.h" |
| #include "content/browser/renderer_host/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/document_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/common/result_codes.h" |
| #include "content/public/test/back_forward_cache_util.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/commit_message_delayer.h" |
| #include "content/public/test/content_browser_test.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_content_browser_client.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, "unload_support", |
| unload_support_); |
| 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; |
| std::string unload_support_ = "always"; |
| |
| 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. |
| // The number 5 was picked since Android ASAN trybot failed to keep more than |
| // 6 pages in memory. |
| const size_t kBackForwardCacheSize = 5; |
| }; |
| |
| // Test documents are evicted from the BackForwardCache at some point. |
| IN_PROC_BROWSER_TEST_F(HighCacheSizeBackForwardCacheBrowserTest, |
| CacheEvictionWithIncreasedCacheSize) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); // BackForwardCache size is 0. |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); // BackForwardCache size is 1. |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b); |
| |
| for (size_t i = 2; i < kBackForwardCacheSize; ++i) { |
| EXPECT_TRUE(NavigateToURL(shell(), i % 2 ? url_b : url_a)); |
| // After |i+1| navigations, |i| documents went into the BackForwardCache. |
| // When |i| is greater than the BackForwardCache size limit, they are |
| // evicted: |
| EXPECT_EQ(i >= kBackForwardCacheSize + 1, delete_observer_rfh_a.deleted()); |
| EXPECT_EQ(i >= kBackForwardCacheSize + 2, delete_observer_rfh_b.deleted()); |
| } |
| } |
| |
| 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 RenderFrameHostImplWrapper& rfh, |
| bool cached, |
| bool backgrounded) { |
| EXPECT_FALSE(rfh.IsDestroyed()); |
| EXPECT_EQ(cached, rfh->IsInBackForwardCache()); |
| EXPECT_EQ(backgrounded, rfh->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<RenderFrameHostImplWrapper> rfhs; |
| |
| 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)); |
| rfhs.emplace_back(current_frame_host()); |
| EXPECT_FALSE(rfhs.back()->GetProcess()->IsProcessBackgrounded()); |
| |
| 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(rfhs[j], /*cached=*/i != j, |
| /*backgrounded=*/false); |
| } else { |
| rfhs[j].WaitUntilRenderFrameDeleted(); |
| } |
| } |
| } |
| |
| // 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<RenderFrameHostImplWrapper> rfhs; |
| |
| 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)); |
| rfhs.emplace_back(current_frame_host()); |
| EXPECT_FALSE(rfhs.back()->GetProcess()->IsProcessBackgrounded()); |
| |
| 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(rfhs[j].IsDestroyed()); |
| // Pages except the active one should be cached and in the background. |
| ExpectCached(rfhs[j], /*cached=*/i != j, |
| /*backgrounded=*/i != j); |
| } else { |
| rfhs[j].WaitUntilRenderFrameDeleted(); |
| } |
| } |
| } |
| |
| // 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<RenderFrameHostImplWrapper> rfhs; |
| |
| // 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)); |
| rfhs.emplace_back(current_frame_host()); |
| EXPECT_FALSE(rfhs.back()->GetProcess()->IsProcessBackgrounded()); |
| } |
| // Check that a0-2 are cached and backgrounded. |
| for (size_t i = 0; i < kBackForwardCacheSize - 1; ++i) { |
| SCOPED_TRACE(i); |
| ExpectCached(rfhs[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()); |
| |
| rfhs[1]->GetProcess()->SetPriorityOverride( |
| /*foreground=*/true); |
| rfhs[2]->GetProcess()->SetPriorityOverride( |
| /*foreground=*/true); |
| rfhs[3]->GetProcess()->SetPriorityOverride( |
| /*foreground=*/true); |
| |
| // The page should be evicted. |
| rfhs[1].WaitUntilRenderFrameDeleted(); |
| |
| // Check that a0 is cached and backgrounded. |
| ExpectCached(rfhs[0], /*cached=*/true, /*backgrounded=*/true); |
| // Check that a2-3 are cached and foregrounded. |
| ExpectCached(rfhs[2], /*cached=*/true, /*backgrounded=*/false); |
| ExpectCached(rfhs[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_FALSE(rfh_a->GetPage().IsPrimary()); |
| EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kActive, |
| rfh_b->lifecycle_state()); |
| EXPECT_TRUE(rfh_b->GetPage().IsPrimary()); |
| EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kSpeculative, |
| rfh_c->lifecycle_state()); |
| EXPECT_FALSE(rfh_c->GetPage().IsPrimary()); |
| |
| // 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); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| 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"))); |
| EXPECT_EQ(42, EvalJs(current_frame_host()->child_at(0)->current_frame_host(), |
| "window.receivedMessagePromise")); |
| |
| 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); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| 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 => { window.keepaliveMedia = 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); |
| } |
| |
| // 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.WaitForResponse()); |
| |
| // 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. |
| 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_TRUE( |
| NavigationRequest::From(navigation_manager_back.GetNavigationHandle()) |
| ->IsCommitDeferringConditionDeferredForTesting()); |
| 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 |
| #if defined(OS_ANDROID) |
| #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)); |
| RenderFrameHostImplWrapper rfh(current_frame_host()); |
| |
| // 2) Navigate away. |
| hostname[0]++; |
| GURL reset_url(embedded_test_server()->GetURL(hostname, "/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), reset_url)); |
| |
| if (test_case.expectation == STORED) { |
| EXPECT_FALSE(rfh.IsRenderFrameDeleted()); |
| EXPECT_TRUE(rfh->IsInBackForwardCache()); |
| continue; |
| } |
| |
| if (rfh.get() == current_frame_host()) { |
| // If the RenderFrameHost is reused, it won't be deleted, so don't wait |
| // for deletion. Just check that it's not saved in the back-forward cache. |
| EXPECT_FALSE(rfh.IsRenderFrameDeleted()); |
| EXPECT_FALSE(rfh->IsInBackForwardCache()); |
| continue; |
| } |
| |
| // When the RenderFrameHost is not reused and it's not stored in the |
| // back-forward cache, it will eventually be deleted. |
| rfh.WaitUntilRenderFrameDeleted(); |
| } |
| } |
| |
| 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); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| 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(BackForwardCacheBrowserTest, |
| 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(BackForwardCacheBrowserTest, |
| 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)); |
| RenderFrameHostImplWrapper rfh( |
| tab_to_be_bfcached->web_contents()->GetMainFrame()); |
| |
| // 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(rfh.IsDestroyed()); |
| 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)); |
| rfh.WaitUntilRenderFrameDeleted(); |
| |
| // 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(BackForwardCacheBrowserTest, 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, |
| EvictOnServiceWorkerUnregistration) { |
| 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_unregister_service_worker = CreateBrowser(); |
| |
| // 1) Navigate to A in |tab_to_be_bfcached|. This tab will be controlled by a |
| // service worker. |
| EXPECT_TRUE(NavigateToURL( |
| tab_to_be_bfcached, |
| https_server.GetURL("a.com", |
| "/back_forward_cache/" |
| "service_worker_registration.html?to_be_bfcached"))); |
| |
| // 2) Register a service worker for |tab_to_be_bfcached|, but with a narrow |
| // scope with URL param. This is to prevent |tab_to_unregister_service_worker| |
| // from being controlled by the service worker. |
| EXPECT_EQ("DONE", |
| EvalJs(tab_to_be_bfcached, |
| "register('service_worker_registration.js', " |
| "'service_worker_registration.html?to_be_bfcached')")); |
| EXPECT_EQ("DONE", EvalJs(tab_to_be_bfcached, "claim()")); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver deleted(rfh_a); |
| |
| // 3) Navigate to A in |tab_to_unregister_service_worker|. This tab is not |
| // controlled by the service worker. |
| EXPECT_TRUE(NavigateToURL( |
| tab_to_unregister_service_worker, |
| https_server.GetURL( |
| "a.com", "/back_forward_cache/service_worker_registration.html"))); |
| |
| // 5) Navigate from A to B in |tab_to_be_bfcached|. Now |tab_to_be_bfcached| |
| // should be in bfcache. |
| EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached, |
| https_server.GetURL("b.com", "/title1.html"))); |
| EXPECT_FALSE(deleted.deleted()); |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| |
| // 6) The service worker gets unregistered. Now |tab_to_be_bfcached| should be |
| // notified of the unregistration and evicted from bfcache. |
| EXPECT_EQ( |
| "DONE", |
| EvalJs(tab_to_unregister_service_worker, |
| "unregister('service_worker_registration.html?to_be_bfcached')")); |
| |
| // 7) Navigate 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())); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| EXPECT_TRUE(deleted.deleted()); |
| ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason:: |
| kServiceWorkerUnregistration}, |
| {}, {}, {}, 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); |
| // Ensure that the bot flags won't override the same-site back/forward cache |
| // disabling. |
| DisableFeature(features::kBackForwardCacheSameSiteForBots); |
| 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); |
| auto 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); |
| auto 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) {} |
| |
| 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); |
| GlobalRenderFrameHostId rfh_a_id = rfh_a->GetGlobalId(); |
| 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"))); |
| } |
| |
| // Test the "blocked_cgi_params" feature params in back-forward cache. |
| class BackForwardCacheBrowserTestWithBlockedCgiParams |
| : 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_cgi_params = "ibp=1|tbm=1"; |
| EnableFeatureAndSetParams(features::kBackForwardCache, "blocked_cgi_params", |
| blocked_cgi_params); |
| |
| BackForwardCacheBrowserTest::SetUpCommandLine(command_line); |
| } |
| }; |
| |
| // Check the disallowed page isn't bfcached when it's navigated from allowed |
| // page. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithBlockedCgiParams, |
| 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_allowed( |
| embedded_test_server()->GetURL("a.llowed", "/title1.html?tbm=0")); |
| GURL url_not_allowed( |
| embedded_test_server()->GetURL("nota.llowed", "/title1.html?tbm=1")); |
| |
| // 1) Navigate to url_allowed. |
| EXPECT_TRUE(NavigateToURL(shell(), url_allowed)); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| RenderFrameHostImpl* rfh_allowed = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_allowed(rfh_allowed); |
| |
| // 2) Navigate to url_not_allowed. |
| EXPECT_TRUE(NavigateToURL(shell(), url_not_allowed)); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| RenderFrameHostImpl* rfh_not_allowed = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_not_allowed(rfh_not_allowed); |
| |
| // 3) Check that url_allowed is stored in back-forward cache. |
| EXPECT_FALSE(delete_observer_rfh_allowed.deleted()); |
| EXPECT_TRUE(rfh_allowed->IsInBackForwardCache()); |
| |
| // 4) Now go back to url_allowed. |
| web_contents()->GetController().GoBack(); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| EXPECT_EQ(rfh_allowed, current_frame_host()); |
| ExpectRestored(FROM_HERE); |
| |
| // 5) Check that url_not_allowed is not stored in back-forward cache |
| delete_observer_rfh_not_allowed.WaitUntilDeleted(); |
| EXPECT_TRUE(delete_observer_rfh_not_allowed.deleted()); |
| |
| // 6) Go forward to url_not_allowed, it should not be restored from the |
| // back-forward cache. |
| web_contents()->GetController().GoForward(); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| |
| // Nothing is recorded since it 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(BackForwardCacheBrowserTestWithBlockedCgiParams, |
| 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_allowed( |
| embedded_test_server()->GetURL("a.llowed", "/title1.html?tbm=0")); |
| GURL url_not_allowed( |
| embedded_test_server()->GetURL("nota.llowed", "/title1.html?tbm=1")); |
| |
| // 1) Navigate to url_not_allowed. |
| EXPECT_TRUE(NavigateToURL(shell(), url_not_allowed)); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| RenderFrameHostImpl* rfh_not_allowed = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_not_allowed(rfh_not_allowed); |
| |
| // 2) Navigate to url_allowed. |
| EXPECT_TRUE(NavigateToURL(shell(), url_allowed)); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| RenderFrameHostImpl* rfh_allowed = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_allowed(rfh_allowed); |
| |
| // 3) Check that url_not_allowed is not stored in back-forward cache. |
| delete_observer_rfh_not_allowed.WaitUntilDeleted(); |
| EXPECT_TRUE(delete_observer_rfh_not_allowed.deleted()); |
| |
| // 4) Now go back to url_not_allowed. |
| web_contents()->GetController().GoBack(); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| |
| // Nothing is recorded since it is disallowed. |
| ExpectOutcomeDidNotChange(FROM_HERE); |
| ExpectNotRestoredDidNotChange(FROM_HERE); |
| |
| // 5) Check that url_allowed is stored in back-forward cache |
| EXPECT_FALSE(delete_observer_rfh_allowed.deleted()); |
| EXPECT_TRUE(rfh_allowed->IsInBackForwardCache()); |
| |
| // 6) Go forward to url_allowed, it should be restored from the |
| // back-forward cache. |
| web_contents()->GetController().GoForward(); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| ExpectRestored(FROM_HERE); |
| } |
| |
| // 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(); |
| auto 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. |
| // TODO(crbug.com/1211428): Flaky on other platforms. |
| // 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, DISABLED_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) |
| |
| // TODO(https://crbug.com/1213145): The test is consistently failing on some Mac |
| // bots. |
| #if defined(OS_MAC) |
| #define MAYBE_TrivialRTCPeerConnectionCached \ |
| DISABLED_TrivialRTCPeerConnectionCached |
| #else |
| #define MAYBE_TrivialRTCPeerConnectionCached TrivialRTCPeerConnectionCached |
| #endif |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| MAYBE_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) |
| |
| // TODO(https://crbug.com/1213145): The test is consistently failing on some Mac |
| // bots. |
| #if defined(OS_MAC) |
| #define MAYBE_NonTrivialRTCPeerConnectionNotCached \ |
| DISABLED_NonTrivialRTCPeerConnectionNotCached |
| #else |
| #define MAYBE_NonTrivialRTCPeerConnectionNotCached \ |
| NonTrivialRTCPeerConnectionNotCached |
| #endif |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| MAYBE_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()->SetPrimaryMainFrameImportance( |
| 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 DocumentServiceBase for test. |
| class EchoImpl final : public DocumentServiceBase<mojom::Echo> { |
| public: |
| EchoImpl(RenderFrameHost* render_frame_host, |
| mojo::PendingReceiver<mojom::Echo> receiver, |
| bool* deleted) |
| : DocumentServiceBase(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, DocumentServiceBase) { |
| 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().Has( |
| 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().Has( |
| 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 IsActive status for RenderFrameHost A |
| // and B before and after entering back-forward cache. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, CheckIsActive) { |
| 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->IsActive()); |
| EXPECT_TRUE(rfh_b->IsActive()); |
| |
| // 2) Navigate to C. |
| EXPECT_TRUE(NavigateToURL(shell(), url_c)); |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| EXPECT_TRUE(rfh_b->IsInBackForwardCache()); |
| |
| EXPECT_FALSE(rfh_a->IsActive()); |
| EXPECT_FALSE(rfh_b->IsActive()); |
| } |
| |
| // 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()); |
| EXPECT_TRUE(rfh_a->GetPage().IsPrimary()); |
| |
| // 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_FALSE(rfh_a->GetPage().IsPrimary()); |
| EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kActive, |
| rfh_b->lifecycle_state()); |
| EXPECT_EQ(RenderFrameHost::LifecycleState::kActive, |
| rfh_b->GetLifecycleState()); |
| EXPECT_TRUE(rfh_b->GetPage().IsPrimary()); |
| |
| // 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_a->GetPage().IsPrimary()); |
| EXPECT_TRUE(rfh_b->IsInBackForwardCache()); |
| EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kInBackForwardCache, |
| rfh_b->lifecycle_state()); |
| EXPECT_FALSE(rfh_b->GetPage().IsPrimary()); |
| } |
| |
| 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->GetGlobalId()); |
| |
| // 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 an eligible page is cached when navigating to about:blank. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| NavigatingToAboutBlankDoesNotPreventCaching) { |
| 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())); |
| |
| ExpectRestored(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(); |
| nav_manager.WaitForFirstYieldAfterDidStartNavigation(); |
| |
| 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 { |
| 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")); |
| |
| 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())); |
| |
| ExpectRestored(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 { |
| EXPECT_EQ(GetParam(), "opt_in_header_required"); |
| ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason:: |
| kOptInUnloadHeaderNotPresent}, |
| {}, {}, {}, 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())); |
| |
| ExpectRestored(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); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, NoThrottlesOnCacheRestore) { |
| 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); |
| |
| bool did_register_throttles = false; |
| |
| // This will track for each navigation whether we attempted to register |
| // NavigationThrottles. |
| content::ShellContentBrowserClient::Get() |
| ->set_create_throttles_for_navigation_callback(base::BindLambdaForTesting( |
| [&did_register_throttles](content::NavigationHandle* handle) |
| -> std::vector<std::unique_ptr<content::NavigationThrottle>> { |
| did_register_throttles = true; |
| return std::vector<std::unique_ptr<content::NavigationThrottle>>(); |
| })); |
| |
| // 2) Navigate to B. |
| ASSERT_TRUE(NavigateToURL(shell(), url_b)); |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b); |
| ASSERT_FALSE(delete_observer_rfh_a.deleted()); |
| ASSERT_TRUE(rfh_a->IsInBackForwardCache()); |
| EXPECT_TRUE(did_register_throttles); |
| did_register_throttles = false; |
| |
| // 3) Go back to A which is in the BackForward cache and will be restored via |
| // an IsPageActivation navigation. Ensure that we did not register |
| // NavigationThrottles for this navigation since we already ran their checks |
| // when we navigated to A in step 1. |
| web_contents()->GetController().GoBack(); |
| ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| EXPECT_FALSE(did_register_throttles); |
| |
| ExpectRestored(FROM_HERE); |
| } |
| |
| class BackForwardCacheBrowserTestAllowCacheControlNoStore |
| : public BackForwardCacheBrowserTest { |
| protected: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| EnableFeatureAndSetParams(features::kBackForwardCache, "", ""); |
| EnableFeatureAndSetParams(kCacheControlNoStoreEnterBackForwardCache, "", |
| ""); |
| BackForwardCacheBrowserTest::SetUpCommandLine(command_line); |
| } |
| }; |
| |
| // TODO(https://crbug.com/1231849): flaky on Cast Linux. |
| #if defined(OS_LINUX) |
| #define MAYBE_PagesWithCacheControlNoStoreEnterBfcacheAndEvicted \ |
| DISABLED_PagesWithCacheControlNoStoreEnterBfcacheAndEvicted |
| #else |
| #define MAYBE_PagesWithCacheControlNoStoreEnterBfcacheAndEvicted \ |
| PagesWithCacheControlNoStoreEnterBfcacheAndEvicted |
| #endif |
| |
| // Test that a page with cache-control:no-store enters bfcache with the flag on, |
| // but does not get restored and gets evicted. |
| IN_PROC_BROWSER_TEST_F( |
| BackForwardCacheBrowserTestAllowCacheControlNoStore, |
| MAYBE_PagesWithCacheControlNoStoreEnterBfcacheAndEvicted) { |
| net::test_server::ControllableHttpResponse response(embedded_test_server(), |
| "/main_document"); |
| net::test_server::ControllableHttpResponse response2(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); |
| RenderFrameHostImplWrapper rfh_a(current_frame_host()); |
| response.WaitForRequest(); |
| response.Send(kResponseWithNoCache); |
| response.Done(); |
| observer.Wait(); |
| |
| // 2) Navigate away. |rfh_a| should enter the bfcache. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| |
| // 3) Go back. |rfh_a| should be evicted upon restoration. |
| TestNavigationObserver observer2(web_contents()); |
| web_contents()->GetController().GoBack(); |
| response2.WaitForRequest(); |
| response2.Send(kResponseWithNoCache); |
| response2.Done(); |
| observer2.Wait(); |
| |
| ExpectNotRestored( |
| {BackForwardCacheMetrics::NotRestoredReason::kCacheControlNoStore}, {}, |
| {}, {}, FROM_HERE); |
| } |
| |
| #if BUILDFLAG(IS_CHROMECAST) |
| #define MAYBE_PagesWithCacheControlNoStoreCookieModifiedThroughJavaScript \ |
| DISABLED_PagesWithCacheControlNoStoreCookieModifiedThroughJavaScript |
| #else |
| #define MAYBE_PagesWithCacheControlNoStoreCookieModifiedThroughJavaScript \ |
| PagesWithCacheControlNoStoreCookieModifiedThroughJavaScript |
| #endif |
| |
| // Test that a page with cache-control:no-store enters bfcache with the flag on, |
| // and if a cookie is modified while it is in bfcache via JavaScript, gets |
| // evicted with cookie modified marked. |
| IN_PROC_BROWSER_TEST_F( |
| BackForwardCacheBrowserTestAllowCacheControlNoStore, |
| MAYBE_PagesWithCacheControlNoStoreCookieModifiedThroughJavaScript) { |
| net::test_server::ControllableHttpResponse response(embedded_test_server(), |
| "/main_document"); |
| net::test_server::ControllableHttpResponse response2(embedded_test_server(), |
| "/main_document"); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/main_document")); |
| GURL url_a_2(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| Shell* tab_to_be_bfcached = shell(); |
| Shell* tab_to_modify_cookie = CreateBrowser(); |
| |
| // 1) Load the document and specify no-store for the main resource. |
| TestNavigationObserver observer(tab_to_be_bfcached->web_contents()); |
| tab_to_be_bfcached->LoadURL(url_a); |
| RenderFrameHostImplWrapper rfh_a(current_frame_host()); |
| response.WaitForRequest(); |
| response.Send(kResponseWithNoCache); |
| response.Done(); |
| observer.Wait(); |
| |
| // 2) Set a normal cookie from JavaScript. |
| EXPECT_TRUE(ExecJs(tab_to_be_bfcached, "document.cookie='foo=bar'")); |
| EXPECT_EQ("foo=bar", EvalJs(tab_to_be_bfcached, "document.cookie")); |
| |
| // 3) Navigate away. |rfh_a| should enter bfcache. |
| EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached, url_b)); |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| |
| // 4) Navigate to a.com in |tab_to_modify_cookie| and modify cookie from |
| // JavaScript. |
| EXPECT_TRUE(NavigateToURL(tab_to_modify_cookie, url_a_2)); |
| EXPECT_EQ("foo=bar", EvalJs(tab_to_modify_cookie, "document.cookie")); |
| EXPECT_TRUE(ExecJs(tab_to_modify_cookie, "document.cookie='foo=baz'")); |
| EXPECT_EQ("foo=baz", EvalJs(tab_to_modify_cookie, "document.cookie")); |
| |
| // 5) Go back. |rfh_a| should be evicted upon restoration. |
| TestNavigationObserver observer2(tab_to_be_bfcached->web_contents()); |
| tab_to_be_bfcached->web_contents()->GetController().GoBack(); |
| response2.WaitForRequest(); |
| response2.Send(kResponseWithNoCache); |
| response2.Done(); |
| observer2.Wait(); |
| |
| EXPECT_EQ("foo=baz", EvalJs(tab_to_be_bfcached, "document.cookie")); |
| ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason:: |
| kCacheControlNoStoreCookieModified}, |
| {}, {}, {}, FROM_HERE); |
| } |
| |
| // Disabled due to flakiness on Cast Audio Linux https://crbug.com/1229182 |
| #if BUILDFLAG(IS_CHROMECAST) |
| #define MAYBE_PagesWithCacheControlNoStoreCookieModifiedBackTwice \ |
| DISABLED_PagesWithCacheControlNoStoreCookieModifiedBackTwice |
| #else |
| #define MAYBE_PagesWithCacheControlNoStoreCookieModifiedBackTwice \ |
| PagesWithCacheControlNoStoreCookieModifiedBackTwice |
| #endif |
| |
| // Test that a page with cache-control:no-store enters bfcache with the flag on, |
| // and if a cookie is modified, it gets evicted with cookie changed, but if |
| // navigated away again and navigated back, it gets evicted without cookie |
| // change marked. |
| IN_PROC_BROWSER_TEST_F( |
| BackForwardCacheBrowserTestAllowCacheControlNoStore, |
| MAYBE_PagesWithCacheControlNoStoreCookieModifiedBackTwice) { |
| net::test_server::ControllableHttpResponse response(embedded_test_server(), |
| "/main_document"); |
| net::test_server::ControllableHttpResponse response2(embedded_test_server(), |
| "/main_document"); |
| net::test_server::ControllableHttpResponse response3(embedded_test_server(), |
| "/main_document"); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/main_document")); |
| GURL url_a_2(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| Shell* tab_to_be_bfcached = shell(); |
| Shell* tab_to_modify_cookie = CreateBrowser(); |
| |
| // 1) Load the document and specify no-store for the main resource. |
| TestNavigationObserver observer(tab_to_be_bfcached->web_contents()); |
| tab_to_be_bfcached->LoadURL(url_a); |
| RenderFrameHostImplWrapper rfh_a(current_frame_host()); |
| response.WaitForRequest(); |
| response.Send(kResponseWithNoCache); |
| response.Done(); |
| observer.Wait(); |
| |
| // 2) Set a normal cookie from JavaScript. |
| EXPECT_TRUE(ExecJs(tab_to_be_bfcached, "document.cookie='foo=bar'")); |
| EXPECT_EQ("foo=bar", EvalJs(tab_to_be_bfcached, "document.cookie")); |
| |
| // 3) Navigate away. |rfh_a| should enter bfcache. |
| EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached, url_b)); |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| |
| // 4) Navigate to a.com in |tab_to_modify_cookie| and modify cookie from |
| // JavaScript. |
| EXPECT_TRUE(NavigateToURL(tab_to_modify_cookie, url_a_2)); |
| EXPECT_EQ("foo=bar", EvalJs(tab_to_modify_cookie, "document.cookie")); |
| EXPECT_TRUE(ExecJs(tab_to_modify_cookie, "document.cookie='foo=baz'")); |
| EXPECT_EQ("foo=baz", EvalJs(tab_to_modify_cookie, "document.cookie")); |
| |
| // 5) Go back. |rfh_a| should be evicted upon restoration. |
| TestNavigationObserver observer2(tab_to_be_bfcached->web_contents()); |
| tab_to_be_bfcached->web_contents()->GetController().GoBack(); |
| response2.WaitForRequest(); |
| response2.Send(kResponseWithNoCache); |
| response2.Done(); |
| observer2.Wait(); |
| |
| EXPECT_EQ("foo=baz", EvalJs(tab_to_be_bfcached, "document.cookie")); |
| ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason:: |
| kCacheControlNoStoreCookieModified}, |
| {}, {}, {}, FROM_HERE); |
| RenderFrameHostImplWrapper rfh_a_2(current_frame_host()); |
| |
| // 6) Navigate away to b.com. |rfh_a_2| should enter bfcache again. |
| EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached, url_b)); |
| EXPECT_TRUE(rfh_a_2->IsInBackForwardCache()); |
| |
| // 7) Navigate back to a.com. This time the cookie change has to be reset and |
| // gets evicted with a different reason. |
| TestNavigationObserver observer3(tab_to_be_bfcached->web_contents()); |
| tab_to_be_bfcached->web_contents()->GetController().GoBack(); |
| response3.WaitForRequest(); |
| response3.Send(kResponseWithNoCache); |
| response3.Done(); |
| observer3.Wait(); |
| ExpectNotRestored( |
| {BackForwardCacheMetrics::NotRestoredReason::kCacheControlNoStore}, {}, |
| {}, {}, FROM_HERE); |
| } |
| |
| // Disabled due to flakiness on Cast Audio Linux https://crbug.com/1229182 |
| #if BUILDFLAG(IS_CHROMECAST) |
| #define MAYBE_PagesWithCacheControlNoStoreCookieModifiedThroughJavaScriptOnDifferentDomain \ |
| DISABLED_PagesWithCacheControlNoStoreCookieModifiedThroughJavaScriptOnDifferentDomain |
| #else |
| #define MAYBE_PagesWithCacheControlNoStoreCookieModifiedThroughJavaScriptOnDifferentDomain \ |
| PagesWithCacheControlNoStoreCookieModifiedThroughJavaScriptOnDifferentDomain |
| #endif |
| |
| // Test that a page with cache-control:no-store enters bfcache with the flag on, |
| // and even if a cookie is modified on a different domain than the entry, the |
| // entry is not marked as cookie modified. |
| IN_PROC_BROWSER_TEST_F( |
| BackForwardCacheBrowserTestAllowCacheControlNoStore, |
| MAYBE_PagesWithCacheControlNoStoreCookieModifiedThroughJavaScriptOnDifferentDomain) { |
| net::test_server::ControllableHttpResponse response(embedded_test_server(), |
| "/main_document"); |
| net::test_server::ControllableHttpResponse response2(embedded_test_server(), |
| "/main_document"); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/main_document")); |
| GURL url_a_2(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| Shell* tab_to_be_bfcached = shell(); |
| Shell* tab_to_modify_cookie = CreateBrowser(); |
| |
| // 1) Load the document and specify no-store for the main resource. |
| TestNavigationObserver observer(tab_to_be_bfcached->web_contents()); |
| tab_to_be_bfcached->LoadURL(url_a); |
| RenderFrameHostImplWrapper rfh_a(current_frame_host()); |
| response.WaitForRequest(); |
| response.Send(kResponseWithNoCache); |
| response.Done(); |
| observer.Wait(); |
| |
| // 2) Set a normal cookie from JavaScript. |
| EXPECT_TRUE(ExecJs(tab_to_be_bfcached, "document.cookie='foo=bar'")); |
| EXPECT_EQ("foo=bar", EvalJs(tab_to_be_bfcached, "document.cookie")); |
| |
| // 3) Navigate away. |rfh_a| should enter bfcache. |
| EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached, url_b)); |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| |
| // 4) Navigate to b.com in |tab_to_modify_cookie| and modify cookie from |
| // JavaScript. |
| EXPECT_TRUE(NavigateToURL(tab_to_modify_cookie, url_b)); |
| EXPECT_TRUE(ExecJs(tab_to_modify_cookie, "document.cookie='foo=baz'")); |
| EXPECT_EQ("foo=baz", EvalJs(tab_to_modify_cookie, "document.cookie")); |
| |
| // 5) Go back. |rfh_a| should be evicted upon restoration. |
| TestNavigationObserver observer2(tab_to_be_bfcached->web_contents()); |
| tab_to_be_bfcached->web_contents()->GetController().GoBack(); |
| response2.WaitForRequest(); |
| response2.Send(kResponseWithNoCache); |
| response2.Done(); |
| observer2.Wait(); |
| EXPECT_EQ("foo=bar", EvalJs(tab_to_be_bfcached, "document.cookie")); |
| |
| ExpectNotRestored( |
| {BackForwardCacheMetrics::NotRestoredReason::kCacheControlNoStore}, {}, |
| {}, {}, FROM_HERE); |
| } |
| |
| namespace { |
| const char kResponseWithNoCacheWithCookie[] = |
| "HTTP/1.1 200 OK\r\n" |
| "Content-Type: text/html; charset=utf-8\r\n" |
| "Set-Cookie: foo=bar\r\n" |
| "Cache-Control: no-store\r\n" |
| "\r\n" |
| "The server speaks HTTP!"; |
| |
| const char kResponseWithNoCacheWithHTTPOnlyCookie[] = |
| "HTTP/1.1 200 OK\r\n" |
| "Content-Type: text/html; charset=utf-8\r\n" |
| "Set-Cookie: foo=bar; Secure; HttpOnly;\r\n" |
| "Cache-Control: no-store\r\n" |
| "\r\n" |
| "The server speaks HTTP!"; |
| |
| const char kResponseWithNoCacheWithHTTPOnlyCookie2[] = |
| "HTTP/1.1 200 OK\r\n" |
| "Content-Type: text/html; charset=utf-8\r\n" |
| "Set-Cookie: foo=baz; Secure; HttpOnly;\r\n" |
| "Cache-Control: no-store\r\n" |
| "\r\n" |
| "The server speaks HTTP!"; |
| } // namespace |
| |
| // Disabled due to flakiness on Cast Audio Linux https://crbug.com/1229182 |
| #if BUILDFLAG(IS_CHROMECAST) |
| #define MAYBE_PagesWithCacheControlNoStoreSetFromResponseHeader \ |
| DISABLED_PagesWithCacheControlNoStoreSetFromResponseHeader |
| #else |
| #define MAYBE_PagesWithCacheControlNoStoreSetFromResponseHeader \ |
| PagesWithCacheControlNoStoreSetFromResponseHeader |
| #endif |
| |
| // Test that a page with cache-control:no-store enters bfcache with the flag on, |
| // and if a cookie is modified while it is in bfcache via response header, gets |
| // evicted with cookie modified marked. |
| IN_PROC_BROWSER_TEST_F( |
| BackForwardCacheBrowserTestAllowCacheControlNoStore, |
| MAYBE_PagesWithCacheControlNoStoreSetFromResponseHeader) { |
| net::test_server::ControllableHttpResponse response(embedded_test_server(), |
| "/main_document"); |
| net::test_server::ControllableHttpResponse response2(embedded_test_server(), |
| "/main_document"); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/main_document")); |
| GURL url_a_2(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| Shell* tab_to_be_bfcached = shell(); |
| Shell* tab_to_modify_cookie = CreateBrowser(); |
| |
| // 1) Load the document and specify no-store for the main resource. |
| TestNavigationObserver observer(tab_to_be_bfcached->web_contents()); |
| tab_to_be_bfcached->LoadURL(url_a); |
| RenderFrameHostImplWrapper rfh_a(current_frame_host()); |
| response.WaitForRequest(); |
| response.Send(kResponseWithNoCacheWithCookie); |
| response.Done(); |
| observer.Wait(); |
| EXPECT_EQ("foo=bar", EvalJs(tab_to_be_bfcached, "document.cookie")); |
| |
| // 2) Navigate away. |rfh_a| should enter bfcache. |
| EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached, url_b)); |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| |
| // 3) Navigate to a.com in |tab_to_modify_cookie| and modify cookie from |
| // JavaScript. |
| EXPECT_TRUE(NavigateToURL(tab_to_modify_cookie, url_a_2)); |
| EXPECT_EQ("foo=bar", EvalJs(tab_to_modify_cookie, "document.cookie")); |
| EXPECT_TRUE(ExecJs(tab_to_modify_cookie, "document.cookie='foo=baz'")); |
| EXPECT_EQ("foo=baz", EvalJs(tab_to_modify_cookie, "document.cookie")); |
| |
| // 4) Go back. |rfh_a| should be evicted upon restoration. |
| TestNavigationObserver observer2(tab_to_be_bfcached->web_contents()); |
| tab_to_be_bfcached->web_contents()->GetController().GoBack(); |
| response2.WaitForRequest(); |
| // Send the response without the cookie header to avoid overwriting the |
| // cookie. |
| response2.Send(kResponseWithNoCache); |
| response2.Done(); |
| observer2.Wait(); |
| EXPECT_EQ("foo=baz", EvalJs(tab_to_be_bfcached, "document.cookie")); |
| ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason:: |
| kCacheControlNoStoreCookieModified}, |
| {}, {}, {}, FROM_HERE); |
| } |
| |
| // Disabled due to flakiness on Cast Audio Linux https://crbug.com/1229182 |
| #if BUILDFLAG(IS_CHROMECAST) |
| #define MAYBE_PagesWithCacheControlNoStoreSetFromResponseHeaderHTTPOnlyCookie \ |
| DISABLED_PagesWithCacheControlNoStoreSetFromResponseHeaderHTTPOnlyCookie |
| #else |
| #define MAYBE_PagesWithCacheControlNoStoreSetFromResponseHeaderHTTPOnlyCookie \ |
| PagesWithCacheControlNoStoreSetFromResponseHeaderHTTPOnlyCookie |
| #endif |
| |
| // Test that a page with cache-control:no-store enters bfcache with the flag on, |
| // and if HTTPOnly cookie is modified while it is in bfcache, gets evicted with |
| // HTTPOnly cookie modified marked. |
| IN_PROC_BROWSER_TEST_F( |
| BackForwardCacheBrowserTestAllowCacheControlNoStore, |
| MAYBE_PagesWithCacheControlNoStoreSetFromResponseHeaderHTTPOnlyCookie) { |
| // HTTPOnly cookie can be only set over HTTPS. |
| CreateHttpsServer(); |
| net::test_server::ControllableHttpResponse response(https_server(), |
| "/main_document"); |
| net::test_server::ControllableHttpResponse response2(https_server(), |
| "/main_document2"); |
| net::test_server::ControllableHttpResponse response3(https_server(), |
| "/main_document"); |
| ASSERT_TRUE(https_server()->Start()); |
| |
| GURL url_a(https_server()->GetURL("a.com", "/main_document")); |
| GURL url_a_2(https_server()->GetURL("a.com", "/main_document2")); |
| GURL url_b(https_server()->GetURL("b.com", "/title1.html")); |
| |
| Shell* tab_to_be_bfcached = shell(); |
| Shell* tab_to_modify_cookie = CreateBrowser(); |
| |
| // 1) Load the document and specify no-store for the main resource. |
| TestNavigationObserver observer(tab_to_be_bfcached->web_contents()); |
| tab_to_be_bfcached->LoadURL(url_a); |
| RenderFrameHostImplWrapper rfh_a(current_frame_host()); |
| response.WaitForRequest(); |
| response.Send(kResponseWithNoCacheWithHTTPOnlyCookie); |
| response.Done(); |
| observer.Wait(); |
| // HTTPOnly cookie should not be accessible from JavaScript. |
| EXPECT_EQ("", EvalJs(tab_to_be_bfcached, "document.cookie")); |
| |
| // 2) Navigate away. |rfh_a| should enter bfcache. |
| EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached, url_b)); |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| |
| // 3) Navigate to a.com in |tab_to_modify_cookie| and modify HTTPOnly cookie |
| // from the response. |
| TestNavigationObserver observer2(tab_to_modify_cookie->web_contents()); |
| tab_to_modify_cookie->LoadURL(url_a_2); |
| response2.WaitForRequest(); |
| response2.Send(kResponseWithNoCacheWithHTTPOnlyCookie2); |
| response2.Done(); |
| observer2.Wait(); |
| |
| // 4) Go back. |rfh_a| should be evicted upon restoration. |
| TestNavigationObserver observer3(tab_to_be_bfcached->web_contents()); |
| tab_to_be_bfcached->web_contents()->GetController().GoBack(); |
| response3.WaitForRequest(); |
| response3.Send(kResponseWithNoCacheWithHTTPOnlyCookie); |
| response3.Done(); |
| observer3.Wait(); |
| ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason:: |
| kCacheControlNoStoreHTTPOnlyCookieModified}, |
| {}, {}, {}, FROM_HERE); |
| } |
| |
| // Disabled due to flakiness on Cast Audio Linux https://crbug.com/1229182 |
| #if BUILDFLAG(IS_CHROMECAST) |
| #define MAYBE_PagesWithCacheControlNoStoreHTTPOnlyCookieModifiedBackTwice \ |
| DISABLED_PagesWithCacheControlNoStoreHTTPOnlyCookieModifiedBackTwice |
| #else |
| #define MAYBE_PagesWithCacheControlNoStoreHTTPOnlyCookieModifiedBackTwice \ |
| PagesWithCacheControlNoStoreHTTPOnlyCookieModifiedBackTwice |
| #endif |
| |
| // Test that a page with cache-control:no-store enters bfcache with the flag on, |
| // and if a HTTPOnly cookie is modified, it gets evicted with cookie changed, |
| // but if navigated away again and navigated back, it gets evicted without |
| // HTTPOnly cookie change marked. |
| IN_PROC_BROWSER_TEST_F( |
| BackForwardCacheBrowserTestAllowCacheControlNoStore, |
| MAYBE_PagesWithCacheControlNoStoreHTTPOnlyCookieModifiedBackTwice) { |
| CreateHttpsServer(); |
| net::test_server::ControllableHttpResponse response(https_server(), |
| "/main_document"); |
| net::test_server::ControllableHttpResponse response2(https_server(), |
| "/main_document2"); |
| net::test_server::ControllableHttpResponse response3(https_server(), |
| "/main_document"); |
| net::test_server::ControllableHttpResponse response4(https_server(), |
| "/main_document"); |
| ASSERT_TRUE(https_server()->Start()); |
| |
| GURL url_a(https_server()->GetURL("a.com", "/main_document")); |
| GURL url_a_2(https_server()->GetURL("a.com", "/main_document2")); |
| GURL url_b(https_server()->GetURL("b.com", "/title1.html")); |
| |
| Shell* tab_to_be_bfcached = shell(); |
| Shell* tab_to_modify_cookie = CreateBrowser(); |
| |
| // 1) Load the document and specify no-store for the main resource. |
| TestNavigationObserver observer(tab_to_be_bfcached->web_contents()); |
| tab_to_be_bfcached->LoadURL(url_a); |
| RenderFrameHostImplWrapper rfh_a(current_frame_host()); |
| response.WaitForRequest(); |
| response.Send(kResponseWithNoCacheWithHTTPOnlyCookie); |
| response.Done(); |
| observer.Wait(); |
| |
| // 2) Navigate away. |rfh_a| should enter bfcache. |
| EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached, url_b)); |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| |
| // 3) Navigate to a.com in |tab_to_modify_cookie| and modify cookie from |
| // response header. |
| TestNavigationObserver observer2(tab_to_modify_cookie->web_contents()); |
| tab_to_modify_cookie->LoadURL(url_a_2); |
| response2.WaitForRequest(); |
| response2.Send(kResponseWithNoCacheWithHTTPOnlyCookie2); |
| response2.Done(); |
| observer2.Wait(); |
| |
| // 4) Go back. |rfh_a| should be evicted upon restoration. |
| TestNavigationObserver observer3(tab_to_be_bfcached->web_contents()); |
| tab_to_be_bfcached->web_contents()->GetController().GoBack(); |
| response3.WaitForRequest(); |
| response3.Send(kResponseWithNoCache); |
| response3.Done(); |
| observer3.Wait(); |
| |
| ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason:: |
| kCacheControlNoStoreHTTPOnlyCookieModified}, |
| {}, {}, {}, FROM_HERE); |
| RenderFrameHostImplWrapper rfh_a_2(current_frame_host()); |
| |
| // 5) Navigate away to b.com. |rfh_a_2| should enter bfcache again. |
| EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached, url_b)); |
| EXPECT_TRUE(rfh_a_2->IsInBackForwardCache()); |
| |
| // 6) Navigate back to a.com. This time the cookie change has to be reset and |
| // gets evicted with a different reason. |
| TestNavigationObserver observer4(tab_to_be_bfcached->web_contents()); |
| tab_to_be_bfcached->web_contents()->GetController().GoBack(); |
| response4.WaitForRequest(); |
| response4.Send(kResponseWithNoCache); |
| response4.Done(); |
| observer4.Wait(); |
| ExpectNotRestored( |
| {BackForwardCacheMetrics::NotRestoredReason::kCacheControlNoStore}, {}, |
| {}, {}, FROM_HERE); |
| } |
| |
| class BackForwardCacheBrowserTestRestoreCacheControlNoStoreUnlessCookieChange |
| : public BackForwardCacheBrowserTest { |
| protected: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| EnableFeatureAndSetParams(features::kBackForwardCache, "", ""); |
| EnableFeatureAndSetParams(kCacheControlNoStoreEnterBackForwardCache, "", |
| ""); |
| EnableFeatureAndSetParams( |
| kCacheControlNoStoreRestoreFromBackForwardCacheUnlessCookieChange, "", |
| ""); |
| BackForwardCacheBrowserTest::SetUpCommandLine(command_line); |
| } |
| }; |
| |
| // TODO(https://crbug.com/1231849): flaky on Cast Linux. |
| #if defined(OS_LINUX) |
| #define MAYBE_PagesWithCacheControlNoStoreRestoreFromBackForwardCache \ |
| DISABLED_PagesWithCacheControlNoStoreRestoreFromBackForwardCache |
| #else |
| #define MAYBE_PagesWithCacheControlNoStoreRestoreFromBackForwardCache \ |
| PagesWithCacheControlNoStoreRestoreFromBackForwardCache |
| #endif |
| |
| |
| // Test that a page with cache-control:no-store enters bfcache with the flag on, |
| // and gets restored if cookies do not change. |
| IN_PROC_BROWSER_TEST_F( |
| BackForwardCacheBrowserTestRestoreCacheControlNoStoreUnlessCookieChange, |
| MAYBE_PagesWithCacheControlNoStoreRestoreFromBackForwardCache) { |
| net::test_server::ControllableHttpResponse response(embedded_test_server(), |
| "/main_document"); |
| net::test_server::ControllableHttpResponse response2(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); |
| RenderFrameHostImplWrapper rfh_a(current_frame_host()); |
| response.WaitForRequest(); |
| response.Send(kResponseWithNoCache); |
| response.Done(); |
| observer.Wait(); |
| |
| // 2) Navigate away. |rfh_a| should enter the bfcache. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| |
| // 3) Go back. |rfh_a| should be restored. |
| web_contents()->GetController().GoBack(); |
| EXPECT_TRUE(WaitForLoadStop(web_contents())); |
| ExpectRestored(FROM_HERE); |
| } |
| |
| // Flaky on Cast: crbug.com/1229182 |
| #if BUILDFLAG(IS_CHROMECAST) |
| #define MAYBE_PagesWithCacheControlNoStoreEvictedIfCookieChange \ |
| DISABLED_PagesWithCacheControlNoStoreEvictedIfCookieChange |
| #else |
| #define MAYBE_PagesWithCacheControlNoStoreEvictedIfCookieChange \ |
| PagesWithCacheControlNoStoreEvictedIfCookieChange |
| #endif |
| // Test that a page with cache-control:no-store enters bfcache with the flag on, |
| // but gets evicted if cookies change. |
| IN_PROC_BROWSER_TEST_F( |
| BackForwardCacheBrowserTestRestoreCacheControlNoStoreUnlessCookieChange, |
| MAYBE_PagesWithCacheControlNoStoreEvictedIfCookieChange) { |
| net::test_server::ControllableHttpResponse response(embedded_test_server(), |
| "/main_document"); |
| net::test_server::ControllableHttpResponse response2(embedded_test_server(), |
| "/main_document"); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/main_document")); |
| GURL url_a_2(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| Shell* tab_to_be_bfcached = shell(); |
| Shell* tab_to_modify_cookie = CreateBrowser(); |
| |
| // 1) Load the document and specify no-store for the main resource. |
| TestNavigationObserver observer(tab_to_be_bfcached->web_contents()); |
| tab_to_be_bfcached->LoadURL(url_a); |
| RenderFrameHostImplWrapper rfh_a(current_frame_host()); |
| response.WaitForRequest(); |
| response.Send(kResponseWithNoCache); |
| response.Done(); |
| observer.Wait(); |
| |
| // 2) Set a normal cookie from JavaScript. |
| EXPECT_TRUE(ExecJs(tab_to_be_bfcached, "document.cookie='foo=bar'")); |
| EXPECT_EQ("foo=bar", EvalJs(tab_to_be_bfcached, "document.cookie")); |
| |
| // 3) Navigate away. |rfh_a| should enter bfcache. |
| EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached, url_b)); |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| |
| // 4) Navigate to a.com in |tab_to_modify_cookie| and modify cookie from |
| // JavaScript. |
| EXPECT_TRUE(NavigateToURL(tab_to_modify_cookie, url_a_2)); |
| EXPECT_EQ("foo=bar", EvalJs(tab_to_modify_cookie, "document.cookie")); |
| EXPECT_TRUE(ExecJs(tab_to_modify_cookie, "document.cookie='foo=baz'")); |
| EXPECT_EQ("foo=baz", EvalJs(tab_to_modify_cookie, "document.cookie")); |
| |
| // 5) Go back. |rfh_a| should be evicted upon restoration. |
| TestNavigationObserver observer2(tab_to_be_bfcached->web_contents()); |
| tab_to_be_bfcached->web_contents()->GetController().GoBack(); |
| response2.WaitForRequest(); |
| response2.Send(kResponseWithNoCache); |
| response2.Done(); |
| observer2.Wait(); |
| |
| EXPECT_EQ("foo=baz", EvalJs(tab_to_be_bfcached, "document.cookie")); |
| ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason:: |
| kCacheControlNoStoreCookieModified}, |
| {}, {}, {}, FROM_HERE); |
| } |
| |
| namespace { |
| enum class SubframeType { SameSite, CrossSite }; |
| } |
| |
| class BackForwardCacheEvictionDueToSubframeNavigationBrowserTest |
| : public BackForwardCacheBrowserTest, |
| public ::testing::WithParamInterface<SubframeType> { |
| public: |
| // Provides meaningful param names instead of /0 and /1. |
| static std::string DescribeParams( |
| const ::testing::TestParamInfo<ParamType>& info) { |
| switch (info.param) { |
| case SubframeType::SameSite: |
| return "SameSite"; |
| case SubframeType::CrossSite: |
| return "CrossSite"; |
| } |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_P( |
| BackForwardCacheEvictionDueToSubframeNavigationBrowserTest, |
| SubframePendingCommitShouldPreventCache) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| bool use_cross_origin_subframe = GetParam() == SubframeType::CrossSite; |
| GURL subframe_url = embedded_test_server()->GetURL( |
| use_cross_origin_subframe ? "b.com" : "a.com", "/title1.html"); |
| |
| IsolateOriginsForTesting(embedded_test_server(), web_contents(), |
| {"a.com", "b.com"}); |
| |
| // 1) Navigate to a.com. |
| EXPECT_TRUE(NavigateToURL(shell(), a_url)); |
| RenderFrameHostImpl* main_frame = current_frame_host(); |
| |
| // 2) Add subframe and wait for empty document to commit. |
| CreateSubframe(web_contents(), "child", GURL(""), true); |
| |
| CommitMessageDelayer commit_message_delayer( |
| web_contents(), subframe_url, |
| base::BindLambdaForTesting([&](RenderFrameHost*) { |
| // 5) Test that page cannot be stored in bfcache when subframe is |
| // pending commit. |
| BackForwardCacheCanStoreDocumentResult can_store_result = |
| web_contents() |
| ->GetController() |
| .GetBackForwardCache() |
| .CanStorePageNow(static_cast<RenderFrameHostImpl*>(main_frame)); |
| EXPECT_TRUE(can_store_result.HasNotStoredReason( |
| BackForwardCacheMetrics::NotRestoredReason::kSubframeIsNavigating)); |
| })); |
| |
| // 3) Start navigation in subframe to |subframe_url|. |
| EXPECT_TRUE(ExecJs( |
| main_frame, |
| JsReplace("document.querySelector('#child').src = $1;", subframe_url))); |
| // 4) Wait until subframe navigation is pending commit. |
| commit_message_delayer.Wait(); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| All, |
| BackForwardCacheEvictionDueToSubframeNavigationBrowserTest, |
| ::testing::Values(SubframeType::SameSite, SubframeType::CrossSite), |
| &BackForwardCacheEvictionDueToSubframeNavigationBrowserTest:: |
| DescribeParams); |
| |
| // Tests that a back navigation from a crashed page has the process state |
| // tracked correctly by WebContentsImpl. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| BackNavigationFromCrashedPage) { |
| 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); |
| EXPECT_FALSE(web_contents()->IsCrashed()); |
| |
| // 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); |
| EXPECT_FALSE(web_contents()->IsCrashed()); |
| |
| // 3) Crash B. |
| CrashTab(web_contents()); |
| EXPECT_TRUE(web_contents()->IsCrashed()); |
| EXPECT_TRUE(delete_observer_rfh_b.deleted()); |
| |
| // 4) Go back to A. |
| web_contents()->GetController().GoBack(); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| |
| EXPECT_FALSE(delete_observer_rfh_a.deleted()); |
| EXPECT_EQ(origin_a, rfh_a->GetLastCommittedOrigin()); |
| EXPECT_EQ(rfh_a, current_frame_host()); |
| EXPECT_FALSE(rfh_a->IsInBackForwardCache()); |
| EXPECT_EQ(rfh_a->GetVisibilityState(), PageVisibilityState::kVisible); |
| EXPECT_FALSE(web_contents()->IsCrashed()); |
| |
| ExpectRestored(FROM_HERE); |
| } |
| |
| } // namespace content |