| // Copyright 2019 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 <string> |
| |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/logging.h" |
| #include "base/macros.h" |
| #include "base/run_loop.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/browser_process_impl.h" |
| #include "chrome/browser/chrome_content_browser_client.h" |
| #include "chrome/browser/metrics/subprocess_metrics_provider.h" |
| #include "chrome/browser/previews/previews_service.h" |
| #include "chrome/browser/previews/previews_service_factory.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "components/data_reduction_proxy/core/common/data_reduction_proxy_features.h" |
| #include "components/optimization_guide/hints_component_info.h" |
| #include "components/optimization_guide/optimization_guide_features.h" |
| #include "components/optimization_guide/optimization_guide_service.h" |
| #include "components/optimization_guide/proto/hints.pb.h" |
| #include "components/optimization_guide/test_hints_component_creator.h" |
| #include "components/previews/content/previews_hints.h" |
| #include "components/previews/content/previews_optimization_guide.h" |
| #include "components/previews/content/previews_ui_service.h" |
| #include "components/previews/core/previews_constants.h" |
| #include "components/previews/core/previews_features.h" |
| #include "components/previews/core/previews_switches.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_base.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "net/test/embedded_test_server/http_request.h" |
| #include "net/test/embedded_test_server/http_response.h" |
| |
| namespace { |
| |
| // Retries fetching |histogram_name| until it contains at least |count| samples. |
| void RetryForHistogramUntilCountReached(base::HistogramTester* histogram_tester, |
| const std::string& histogram_name, |
| size_t count) { |
| while (true) { |
| base::ThreadPoolInstance::Get()->FlushForTesting(); |
| base::RunLoop().RunUntilIdle(); |
| |
| content::FetchHistogramsFromChildProcesses(); |
| SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| |
| const std::vector<base::Bucket> buckets = |
| histogram_tester->GetAllSamples(histogram_name); |
| size_t total_count = 0; |
| for (const auto& bucket : buckets) { |
| total_count += bucket.count; |
| } |
| if (total_count >= count) { |
| break; |
| } |
| } |
| } |
| |
| } // namespace |
| |
| class DeferAllScriptPriorityBrowserTest |
| : public ::testing::WithParamInterface<bool>, |
| public InProcessBrowserTest { |
| public: |
| DeferAllScriptPriorityBrowserTest() = default; |
| ~DeferAllScriptPriorityBrowserTest() override = default; |
| |
| void SetUp() override { |
| if (IsDeferAllScriptFeatureEnabled()) { |
| scoped_feature_list_.InitWithFeatures( |
| {previews::features::kPreviews, |
| previews::features::kDeferAllScriptPreviews, |
| optimization_guide::features::kOptimizationHints, |
| data_reduction_proxy::features:: |
| kDataReductionProxyEnabledWithNetworkService}, |
| {}); |
| } else { |
| scoped_feature_list_.InitWithFeatures( |
| {previews::features::kPreviews, |
| optimization_guide::features::kOptimizationHints, |
| data_reduction_proxy::features:: |
| kDataReductionProxyEnabledWithNetworkService}, |
| {}); |
| } |
| |
| InProcessBrowserTest::SetUp(); |
| } |
| |
| bool IsDeferAllScriptFeatureEnabled() const { return GetParam(); } |
| |
| // Returns the fetch time for the JavaScript file (in milliseconds). This |
| // value is obtained using the resource timing API. |
| double GetFetchTimeForJavaScriptFileInMilliseconds() { |
| double script_log; |
| std::string script = "getFetchTimeForJavaScriptFileInMilliseconds()"; |
| EXPECT_TRUE(ExecuteScriptAndExtractDouble( |
| browser()->tab_strip_model()->GetActiveWebContents(), script, |
| &script_log)); |
| return script_log; |
| } |
| |
| void SetUpOnMainThread() override { |
| https_server_.reset( |
| new net::EmbeddedTestServer(net::EmbeddedTestServer::TYPE_HTTPS)); |
| https_server_->RegisterRequestHandler( |
| base::BindRepeating(&DeferAllScriptPriorityBrowserTest::RequestHandler, |
| base::Unretained(this))); |
| |
| ASSERT_TRUE(https_server_->Start()); |
| |
| https_url_ = https_server_->GetURL("/defer_all_script_priority_test.html"); |
| ASSERT_TRUE(https_url_.SchemeIs(url::kHttpsScheme)); |
| |
| InProcessBrowserTest::SetUpOnMainThread(); |
| } |
| |
| void SetUpCommandLine(base::CommandLine* cmd) override { |
| cmd->AppendSwitch("enable-spdy-proxy-auth"); |
| cmd->AppendSwitchASCII("--force-effective-connection-type", "Slow-2G"); |
| |
| cmd->AppendSwitch("optimization-guide-disable-installer"); |
| cmd->AppendSwitch("purge_hint_cache_store"); |
| |
| // Due to race conditions, it's possible that blacklist data is not loaded |
| // at the time of first navigation. That may prevent Preview from |
| // triggering, and causing the test to flake. |
| cmd->AppendSwitch(previews::switches::kIgnorePreviewsBlacklist); |
| } |
| |
| // Returns a unique script for each request, to test service worker update. |
| std::unique_ptr<net::test_server::HttpResponse> RequestHandler( |
| const net::test_server::HttpRequest& request) { |
| if (request.GetURL().path().find("/defer_all_script_priority_test.html") != |
| std::string::npos) { |
| return GetHtmlResponse(request); |
| } |
| |
| if (request.GetURL().path().find("/hung") != std::string::npos) { |
| return GetDelayedResponse(request); |
| } |
| return std::make_unique<net::test_server::BasicHttpResponse>(); |
| } |
| |
| // Returns an HTML response that fetches CSS files followed by synchronous |
| // external JavaScript file. The HTML file contains an inline JavaScript |
| // function getFetchTimeForJavaScriptFileInMilliseconds() that returns |
| // the fetch time for JavaScript file in milliseconds. |
| std::unique_ptr<net::test_server::HttpResponse> GetHtmlResponse( |
| const net::test_server::HttpRequest& request) { |
| std::unique_ptr<net::test_server::BasicHttpResponse> response = |
| std::make_unique<net::test_server::BasicHttpResponse>(); |
| response->set_content( |
| "<html><body>" |
| "<link rel=\"stylesheet\" type=\"text/css\" href=\"hung1.css\">" |
| "<link rel=\"stylesheet\" type=\"text/css\" href=\"hung2.css\">" |
| "<link rel=\"stylesheet\" type=\"text/css\" href=\"hung3.css\">" |
| "<link rel=\"stylesheet\" type=\"text/css\" href=\"hung4.css\">" |
| "<script src=\"defer_all_script_syncscript.js\"></script>" |
| "<script>function getFetchTimeForJavaScriptFileInMilliseconds() {" |
| "var p=window.performance.getEntriesByType(\"resource\");" |
| "for (var i=0; i < p.length; i++) {" |
| "if(p[i].name.includes(\"defer_all_script_syncscript.js\")) {" |
| "sendValueToTest(p[i].responseStart-p[i].fetchStart);" |
| "}" |
| "}" |
| "}" |
| "function sendValueToTest(value) {" |
| "window.domAutomationController.send(value);" |
| "}" |
| "</script></body></html>"); |
| return std::move(response); |
| } |
| |
| int css_files_hung_time_milliseconds() { return 5000; } |
| |
| std::unique_ptr<net::test_server::HttpResponse> GetDelayedResponse( |
| const net::test_server::HttpRequest& request) { |
| std::unique_ptr<net::test_server::DelayedHttpResponse> response = |
| std::make_unique<net::test_server::DelayedHttpResponse>( |
| base::TimeDelta::FromMilliseconds( |
| css_files_hung_time_milliseconds())); |
| response->set_code(net::HttpStatusCode::HTTP_OK); |
| return std::move(response); |
| } |
| |
| // Creates hint data from the |component_info| and waits for it to be fully |
| // processed before returning. |
| void ProcessHintsComponent( |
| const optimization_guide::HintsComponentInfo& component_info) { |
| // Register a QuitClosure for when the next hint update is started below. |
| base::RunLoop run_loop; |
| PreviewsServiceFactory::GetForProfile( |
| Profile::FromBrowserContext(browser() |
| ->tab_strip_model() |
| ->GetActiveWebContents() |
| ->GetBrowserContext())) |
| ->previews_ui_service() |
| ->previews_decider_impl() |
| ->previews_opt_guide() |
| ->ListenForNextUpdateForTesting(run_loop.QuitClosure()); |
| |
| g_browser_process->optimization_guide_service()->MaybeUpdateHintsComponent( |
| component_info); |
| run_loop.Run(); |
| } |
| |
| // Performs a navigation to |url| and waits for the the url's host's hints to |
| // load before returning. This ensures that the hints will be available in the |
| // hint cache for a subsequent navigation to a test url with the same host. |
| void LoadHintsForUrl(const GURL& url) { |
| base::HistogramTester histogram_tester; |
| |
| // Navigate to the url to prime the OptimizationGuide hints for the |
| // url's host and ensure that they have been loaded from the store (via |
| // histogram) prior to the navigation that tests functionality. |
| ui_test_utils::NavigateToURL(browser(), url); |
| |
| RetryForHistogramUntilCountReached( |
| &histogram_tester, |
| previews::kPreviewsOptimizationGuideOnLoadedHintResultHistogramString, |
| 1); |
| } |
| |
| void SetDeferAllScriptHintWithPageWithPattern( |
| const GURL& hint_setup_url, |
| const std::string& page_pattern) { |
| ProcessHintsComponent( |
| test_hints_component_creator_.CreateHintsComponentInfoWithPageHints( |
| optimization_guide::proto::DEFER_ALL_SCRIPT, |
| {hint_setup_url.host()}, page_pattern, {})); |
| LoadHintsForUrl(hint_setup_url); |
| } |
| |
| virtual const GURL& https_url() const { return https_url_; } |
| |
| protected: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| |
| private: |
| void TearDownOnMainThread() override { |
| EXPECT_TRUE(https_server_->ShutdownAndWaitUntilComplete()); |
| |
| InProcessBrowserTest::TearDownOnMainThread(); |
| } |
| |
| optimization_guide::testing::TestHintsComponentCreator |
| test_hints_component_creator_; |
| |
| std::unique_ptr<net::EmbeddedTestServer> https_server_; |
| GURL https_url_; |
| |
| DISALLOW_COPY_AND_ASSIGN(DeferAllScriptPriorityBrowserTest); |
| }; |
| |
| // Parameter is true if the test should be run with defer feature enabled. |
| INSTANTIATE_TEST_SUITE_P(, |
| DeferAllScriptPriorityBrowserTest, |
| ::testing::Values(false, true)); |
| |
| // Avoid flakes and issues on non-applicable platforms. |
| #if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_CHROMEOS) |
| #define DISABLE_ON_WIN_MAC_CHROMESOS(x) DISABLED_##x |
| #else |
| #define DISABLE_ON_WIN_MAC_CHROMESOS(x) x |
| #endif |
| |
| // Fetches an HTML weboage that fetches CSS files followed by an external |
| // JavaScript file. Verifies that the fetching of the JavaScript files is |
| // delayed only when it's not render blocking. |
| // |
| // When feature kDeferAllScriptPreviews is enabled, loading priority of |
| // JavaScript file should be lowered. This should cause resource scheduler |
| // to mark the JavaScript request as delayable, and delay its fetching to after |
| // the fetching of CSS files finish. |
| // |
| // However, if kDeferAllScriptPreviews is not enabled, then JavaScript is render |
| // blocking. This should cause resource scheduler to mark the JavaScript request |
| // as non-delayale, thus fetching it in parallel with the CSS files. |
| IN_PROC_BROWSER_TEST_P( |
| DeferAllScriptPriorityBrowserTest, |
| DISABLE_ON_WIN_MAC_CHROMESOS(DeferAllScriptHttpsWhitelisted)) { |
| GURL url = https_url(); |
| |
| if (IsDeferAllScriptFeatureEnabled()) { |
| // Whitelist DeferAllScript for any path for the url's host. |
| SetDeferAllScriptHintWithPageWithPattern(url, "*"); |
| } |
| |
| ui_test_utils::NavigateToURL(browser(), url); |
| |
| double delay_milliseconds = GetFetchTimeForJavaScriptFileInMilliseconds(); |
| |
| if (IsDeferAllScriptFeatureEnabled()) { |
| // Fetching of JavaScript must start after fetching of the CSS files are |
| // finished. |
| EXPECT_LT(css_files_hung_time_milliseconds(), delay_milliseconds); |
| } else { |
| // Fetching of JavaScript should start in parallel with fetching of the |
| // other files. So, it should finish fast enough. Note that even without any |
| // queuing delays in resource scheduler, it's possible that this fetching |
| // takes more css_files_hung_time_milliseconds(). This can potentially make |
| // this test a bit flaky. |
| EXPECT_GT(css_files_hung_time_milliseconds(), delay_milliseconds); |
| } |
| } |