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