1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <memory>
6
7 #include "base/run_loop.h"
8 #include "base/strings/utf_string_conversions.h"
9 #include "base/test/metrics/histogram_tester.h"
10 #include "base/threading/platform_thread.h"
11 #include "build/build_config.h"
12 #include "components/no_state_prefetch/browser/prerender_histograms.h"
13 #include "components/no_state_prefetch/browser/prerender_manager.h"
14 #include "content/public/test/browser_test_utils.h"
15 #include "content/public/test/url_loader_monitor.h"
16 #include "net/test/embedded_test_server/embedded_test_server.h"
17 #include "net/test/embedded_test_server/http_request.h"
18 #include "net/test/embedded_test_server/http_response.h"
19 #include "services/network/public/cpp/resource_request.h"
20 #include "weblayer/browser/no_state_prefetch/prerender_link_manager_factory.h"
21 #include "weblayer/browser/no_state_prefetch/prerender_manager_factory.h"
22 #include "weblayer/browser/profile_impl.h"
23 #include "weblayer/browser/tab_impl.h"
24 #include "weblayer/public/prerender_controller.h"
25 #include "weblayer/shell/browser/shell.h"
26 #include "weblayer/test/weblayer_browser_test.h"
27 #include "weblayer/test/weblayer_browser_test_utils.h"
28
29 #if defined(OS_ANDROID)
30 #include "components/ukm/test_ukm_recorder.h"
31 #include "services/metrics/public/cpp/ukm_builders.h"
32 #include "weblayer/browser/android/metrics/metrics_test_helper.h"
33 #endif
34
35 namespace weblayer {
36
37 class NoStatePrefetchBrowserTest : public WebLayerBrowserTest {
38 public:
39 #if defined(OS_ANDROID)
SetUp()40 void SetUp() override {
41 InstallTestGmsBridge(ConsentType::kConsent);
42
43 WebLayerBrowserTest::SetUp();
44 }
45
TearDown()46 void TearDown() override {
47 RemoveTestGmsBridge();
48 WebLayerBrowserTest::TearDown();
49 }
50 #endif
51
SetUpOnMainThread()52 void SetUpOnMainThread() override {
53 prerendered_page_fetched_ = std::make_unique<base::RunLoop>();
54 script_resource_fetched_ = std::make_unique<base::RunLoop>();
55
56 https_server_ = std::make_unique<net::EmbeddedTestServer>(
57 net::EmbeddedTestServer::TYPE_HTTPS);
58 https_server_->RegisterRequestHandler(base::BindRepeating(
59 &NoStatePrefetchBrowserTest::HandleRequest, base::Unretained(this)));
60 https_server_->AddDefaultHandlers(
61 base::FilePath(FILE_PATH_LITERAL("weblayer/test/data")));
62 ASSERT_TRUE(https_server_->Start());
63
64 #if defined(OS_ANDROID)
65 ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
66 #endif
67 }
68
69 // Helper methods.
HandleRequest(const net::test_server::HttpRequest & request)70 std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
71 const net::test_server::HttpRequest& request) {
72 if (request.GetURL().path().find("prerendered_page") != std::string::npos) {
73 prerendered_page_fetched_->Quit();
74 prerendered_page_was_fetched_ = true;
75 }
76 if (request.GetURL().path().find("prefetch.js") != std::string::npos) {
77 script_fetched_ = true;
78 auto iter = request.headers.find("Purpose");
79 purpose_header_value_ = iter->second;
80 script_resource_fetched_->Quit();
81 }
82 if (request.GetURL().path().find("prefetch_meta.js") != std::string::npos) {
83 script_executed_ = true;
84 }
85
86 // The default handlers will take care of this request.
87 return nullptr;
88 }
89
NavigateToPageAndWaitForTitleChange(const GURL & navigate_to,base::string16 expected_title)90 void NavigateToPageAndWaitForTitleChange(const GURL& navigate_to,
91 base::string16 expected_title) {
92 content::TitleWatcher title_watcher(
93 static_cast<TabImpl*>(shell()->tab())->web_contents(), expected_title);
94 NavigateAndWaitForCompletion(navigate_to, shell());
95 ASSERT_TRUE(expected_title == title_watcher.WaitAndGetTitle());
96 }
97
98 protected:
GetBrowserContext()99 content::BrowserContext* GetBrowserContext() {
100 Tab* tab = shell()->tab();
101 TabImpl* tab_impl = static_cast<TabImpl*>(tab);
102 return tab_impl->web_contents()->GetBrowserContext();
103 }
104
105 std::unique_ptr<base::RunLoop> prerendered_page_fetched_;
106 std::unique_ptr<base::RunLoop> script_resource_fetched_;
107 bool prerendered_page_was_fetched_ = false;
108 bool script_fetched_ = false;
109 bool script_executed_ = false;
110 std::string purpose_header_value_;
111 std::unique_ptr<net::EmbeddedTestServer> https_server_;
112 #if defined(OS_ANDROID)
113 std::unique_ptr<ukm::TestAutoSetUkmRecorder> ukm_recorder_;
114 #endif
115 };
116
IN_PROC_BROWSER_TEST_F(NoStatePrefetchBrowserTest,CreatePrerenderManager)117 IN_PROC_BROWSER_TEST_F(NoStatePrefetchBrowserTest, CreatePrerenderManager) {
118 auto* prerender_manager =
119 PrerenderManagerFactory::GetForBrowserContext(GetBrowserContext());
120 EXPECT_TRUE(prerender_manager);
121 }
122
IN_PROC_BROWSER_TEST_F(NoStatePrefetchBrowserTest,CreatePrerenderLinkManager)123 IN_PROC_BROWSER_TEST_F(NoStatePrefetchBrowserTest, CreatePrerenderLinkManager) {
124 auto* prerender_link_manager =
125 PrerenderLinkManagerFactory::GetForBrowserContext(GetBrowserContext());
126 EXPECT_TRUE(prerender_link_manager);
127 }
128
129 // Test that adding a link-rel prerender tag causes a fetch.
IN_PROC_BROWSER_TEST_F(NoStatePrefetchBrowserTest,LinkRelPrerenderPageFetched)130 IN_PROC_BROWSER_TEST_F(NoStatePrefetchBrowserTest,
131 LinkRelPrerenderPageFetched) {
132 NavigateAndWaitForCompletion(GURL(https_server_->GetURL("/parent_page.html")),
133 shell());
134 prerendered_page_fetched_->Run();
135 }
136
137 // Test that only render blocking resources are loaded during NoStatePrefetch.
IN_PROC_BROWSER_TEST_F(NoStatePrefetchBrowserTest,NSPLoadsRenderBlockingResource)138 IN_PROC_BROWSER_TEST_F(NoStatePrefetchBrowserTest,
139 NSPLoadsRenderBlockingResource) {
140 NavigateAndWaitForCompletion(GURL(https_server_->GetURL("/parent_page.html")),
141 shell());
142 script_resource_fetched_->Run();
143 EXPECT_EQ("prefetch", purpose_header_value_);
144 EXPECT_FALSE(script_executed_);
145 }
146
147 // Test that navigating to a no-state-prefetched page executes JS and reuses
148 // prerendered resources.
IN_PROC_BROWSER_TEST_F(NoStatePrefetchBrowserTest,NavigateToPrerenderedPage)149 IN_PROC_BROWSER_TEST_F(NoStatePrefetchBrowserTest, NavigateToPrerenderedPage) {
150 NavigateAndWaitForCompletion(GURL(https_server_->GetURL("/parent_page.html")),
151 shell());
152 script_resource_fetched_->Run();
153
154 // Navigate to the prerendered page and wait for its title to change.
155 script_fetched_ = false;
156 NavigateToPageAndWaitForTitleChange(
157 GURL(https_server_->GetURL("/prerendered_page.html")),
158 base::ASCIIToUTF16("Prefetch Page"));
159
160 EXPECT_FALSE(script_fetched_);
161 EXPECT_TRUE(script_executed_);
162 }
163
164 #if defined(OS_ANDROID)
165 // Test that no-state-prefetch results in UKM getting recorded.
IN_PROC_BROWSER_TEST_F(NoStatePrefetchBrowserTest,UKMRecorded)166 IN_PROC_BROWSER_TEST_F(NoStatePrefetchBrowserTest, UKMRecorded) {
167 GetProfile()->SetBooleanSetting(SettingType::UKM_ENABLED, true);
168 NavigateAndWaitForCompletion(GURL(https_server_->GetURL("/parent_page.html")),
169 shell());
170 script_resource_fetched_->Run();
171
172 NavigateToPageAndWaitForTitleChange(
173 GURL(https_server_->GetURL("/prerendered_page.html")),
174 base::ASCIIToUTF16("Prefetch Page"));
175
176 auto entries = ukm_recorder_->GetEntriesByName(
177 ukm::builders::NoStatePrefetch::kEntryName);
178 ASSERT_EQ(entries.size(), 1u);
179 const auto* entry = entries[0];
180 // FinalStatus must be set to FINAL_STATUS_NOSTATE_PREFETCH_FINISHED.
181 ukm_recorder_->ExpectEntryMetric(
182 entry,
183 ukm::builders::NoStatePrefetch::kPrefetchedRecently_FinalStatusName, 56);
184 // Origin must be set to ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN.
185 ukm_recorder_->ExpectEntryMetric(
186 entry, ukm::builders::NoStatePrefetch::kPrefetchedRecently_OriginName, 7);
187 }
188 #endif
189
190 // link-rel="prerender" happens even when NoStatePrefetch has been disabled.
IN_PROC_BROWSER_TEST_F(NoStatePrefetchBrowserTest,LinkRelPrerenderWithNSPDisabled)191 IN_PROC_BROWSER_TEST_F(NoStatePrefetchBrowserTest,
192 LinkRelPrerenderWithNSPDisabled) {
193 GetProfile()->SetBooleanSetting(SettingType::NETWORK_PREDICTION_ENABLED,
194 false);
195 NavigateAndWaitForCompletion(GURL(https_server_->GetURL("/parent_page.html")),
196 shell());
197 prerendered_page_fetched_->Run();
198 }
199
200 // link-rel="next" URLs should not be prefetched.
IN_PROC_BROWSER_TEST_F(NoStatePrefetchBrowserTest,LinkRelNextWithNSPDisabled)201 IN_PROC_BROWSER_TEST_F(NoStatePrefetchBrowserTest, LinkRelNextWithNSPDisabled) {
202 NavigateAndWaitForCompletion(
203 GURL(https_server_->GetURL("/link_rel_next_parent.html")), shell());
204 base::RunLoop().RunUntilIdle();
205 EXPECT_FALSE(prerendered_page_was_fetched_);
206 }
207
208 // Non-web initiated prerender succeeds and subsequent navigations reuse
209 // previously downloaded resources.
210 // TODO(https://crbug.com/1144282): Fix failures on Asan.
211 #if defined(ADDRESS_SANITIZER)
212 #define MAYBE_ExternalPrerender DISABLED_ExternalPrerender
213 #else
214 #define MAYBE_ExternalPrerender ExternalPrerender
215 #endif
IN_PROC_BROWSER_TEST_F(NoStatePrefetchBrowserTest,MAYBE_ExternalPrerender)216 IN_PROC_BROWSER_TEST_F(NoStatePrefetchBrowserTest, MAYBE_ExternalPrerender) {
217 GetProfile()->GetPrerenderController()->Prerender(
218 GURL(https_server_->GetURL("/prerendered_page.html")));
219
220 script_resource_fetched_->Run();
221
222 // Navigate to the prerendered page and wait for its title to change.
223 script_fetched_ = false;
224 NavigateToPageAndWaitForTitleChange(
225 GURL(https_server_->GetURL("/prerendered_page.html")),
226 base::ASCIIToUTF16("Prefetch Page"));
227 EXPECT_FALSE(script_fetched_);
228 }
229
230 // Non-web initiated prerender fails when the user has opted out.
IN_PROC_BROWSER_TEST_F(NoStatePrefetchBrowserTest,ExternalPrerenderWhenOptedOut)231 IN_PROC_BROWSER_TEST_F(NoStatePrefetchBrowserTest,
232 ExternalPrerenderWhenOptedOut) {
233 GetProfile()->SetBooleanSetting(SettingType::NETWORK_PREDICTION_ENABLED,
234 false);
235 GetProfile()->GetPrerenderController()->Prerender(
236 GURL(https_server_->GetURL("/prerendered_page.html")));
237 base::RunLoop().RunUntilIdle();
238 EXPECT_FALSE(prerendered_page_was_fetched_);
239 }
240
241 } // namespace weblayer
242