1 // Copyright 2019 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 "base/macros.h"
6 #include "base/test/test_timeouts.h"
7 #include "build/build_config.h"
8 #include "components/network_session_configurator/common/network_switches.h"
9 #include "content/browser/feature_observer.h"
10 #include "content/public/browser/content_browser_client.h"
11 #include "content/public/browser/feature_observer_client.h"
12 #include "content/public/browser/render_process_host.h"
13 #include "content/public/common/content_client.h"
14 #include "content/public/test/browser_test_utils.h"
15 #include "content/public/test/content_browser_test.h"
16 #include "content/public/test/content_browser_test_utils.h"
17 #include "content/shell/browser/shell.h"
18 #include "net/dns/mock_host_resolver.h"
19 #include "net/test/embedded_test_server/embedded_test_server.h"
20 #include "testing/gmock/include/gmock/gmock.h"
21 #include "testing/gtest/include/gtest/gtest.h"
22 
23 #if defined(OS_ANDROID)
24 #include "base/android/build_info.h"
25 #endif
26 
27 namespace content {
28 
29 namespace {
30 
RunLoopWithTimeout()31 void RunLoopWithTimeout() {
32   base::RunLoop run_loop;
33   base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
34       FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
35   run_loop.Run();
36 }
37 
38 class TestBrowserClient : public ContentBrowserClient {
39  public:
TestBrowserClient(FeatureObserverClient * feature_observer_client)40   explicit TestBrowserClient(FeatureObserverClient* feature_observer_client)
41       : feature_observer_client_(feature_observer_client) {}
42   ~TestBrowserClient() override = default;
43 
GetFeatureObserverClient()44   FeatureObserverClient* GetFeatureObserverClient() override {
45     return feature_observer_client_;
46   }
47 
48   TestBrowserClient(const TestBrowserClient&) = delete;
49   TestBrowserClient& operator=(const TestBrowserClient&) = delete;
50 
51  private:
52   FeatureObserverClient* feature_observer_client_;
53 };
54 
55 class MockObserverClient : public FeatureObserverClient {
56  public:
57   MockObserverClient() = default;
58   ~MockObserverClient() override = default;
59 
60   // PerformanceManagerFeatureObserver implementation:
61   MOCK_METHOD2(OnStartUsing,
62                void(GlobalFrameRoutingId id,
63                     blink::mojom::ObservedFeatureType type));
64   MOCK_METHOD2(OnStopUsing,
65                void(GlobalFrameRoutingId id,
66                     blink::mojom::ObservedFeatureType type));
67 };
68 
69 class IndexedDBFeatureObserverBrowserTest : public ContentBrowserTest {
70  public:
71   IndexedDBFeatureObserverBrowserTest() = default;
72   ~IndexedDBFeatureObserverBrowserTest() override = default;
73 
SetUpCommandLine(base::CommandLine * command_line)74   void SetUpCommandLine(base::CommandLine* command_line) override {
75     ContentBrowserTest::SetUpCommandLine(command_line);
76     command_line->AppendSwitch(switches::kIgnoreCertificateErrors);
77   }
78 
SetUpOnMainThread()79   void SetUpOnMainThread() override {
80     ContentBrowserTest::SetUpOnMainThread();
81 
82     original_client_ = SetBrowserClientForTesting(&test_browser_client_);
83 
84     host_resolver()->AddRule("*", "127.0.0.1");
85     server_.ServeFilesFromSourceDirectory(GetTestDataFilePath());
86     ASSERT_TRUE(server_.Start());
87   }
88 
TearDownOnMainThread()89   void TearDownOnMainThread() override {
90     ContentBrowserTest::TearDownOnMainThread();
91     if (original_client_)
92       SetBrowserClientForTesting(original_client_);
93   }
94 
95   // Check if the test can run on the current system. If the test can run,
96   // navigates to the test page and returns true. Otherwise, returns false.
CheckShouldRunTestAndNavigate() const97   bool CheckShouldRunTestAndNavigate() const {
98 #if defined(OS_ANDROID)
99     // Don't run the test if we couldn't override BrowserClient. It happens only
100     // on Android Kitkat or older systems.
101     if (!original_client_)
102       return false;
103 
104     // TODO(https://crbug.com/1011765, https://crbug.com/1019659):
105     // Navigation fails on Android Kit Kat.
106     if (base::android::BuildInfo::GetInstance()->sdk_int() <=
107         base::android::SDK_VERSION_KITKAT) {
108       return false;
109     }
110 #endif  // defined(OS_ANDROID)
111     EXPECT_TRUE(NavigateToURL(shell(), GetTestURL("a.com")));
112     return true;
113   }
114 
GetTestURL(const std::string & hostname) const115   GURL GetTestURL(const std::string& hostname) const {
116     return server_.GetURL(hostname,
117                           "/indexeddb/open_connection/open_connection.html");
118   }
119 
120   testing::StrictMock<MockObserverClient> mock_observer_client_;
121 
122  private:
123   net::EmbeddedTestServer server_{net::EmbeddedTestServer::TYPE_HTTPS};
124   ContentBrowserClient* original_client_ = nullptr;
125   TestBrowserClient test_browser_client_{&mock_observer_client_};
126 
127   IndexedDBFeatureObserverBrowserTest(
128       const IndexedDBFeatureObserverBrowserTest&) = delete;
129   IndexedDBFeatureObserverBrowserTest& operator=(
130       const IndexedDBFeatureObserverBrowserTest&) = delete;
131 };
132 
OpenConnectionA(RenderFrameHost * rfh)133 bool OpenConnectionA(RenderFrameHost* rfh) {
134   return EvalJs(rfh, R"(
135       (async () => {
136         return await OpenConnection('A');
137       }) ();
138   )")
139       .ExtractBool();
140 }
141 
OpenConnectionB(RenderFrameHost * rfh)142 bool OpenConnectionB(RenderFrameHost* rfh) {
143   return EvalJs(rfh, R"(
144       (async () => {
145         return await OpenConnection('B');
146       }) ();
147   )")
148       .ExtractBool();
149 }
150 
151 }  // namespace
152 
153 // Verify that content::FeatureObserver is notified when a frame opens/closes an
154 // IndexedDB connection.
IN_PROC_BROWSER_TEST_F(IndexedDBFeatureObserverBrowserTest,ObserverSingleConnection)155 IN_PROC_BROWSER_TEST_F(IndexedDBFeatureObserverBrowserTest,
156                        ObserverSingleConnection) {
157   if (!CheckShouldRunTestAndNavigate())
158     return;
159 
160   RenderFrameHost* rfh = shell()->web_contents()->GetMainFrame();
161   GlobalFrameRoutingId routing_id(rfh->GetProcess()->GetID(),
162                                   rfh->GetRoutingID());
163 
164   {
165     // Open a connection. Expect observer notification.
166     base::RunLoop run_loop;
167     EXPECT_CALL(
168         mock_observer_client_,
169         OnStartUsing(routing_id,
170                      blink::mojom::ObservedFeatureType::kIndexedDBConnection))
171         .WillOnce([&](GlobalFrameRoutingId, blink::mojom::ObservedFeatureType) {
172           run_loop.Quit();
173         });
174     EXPECT_TRUE(OpenConnectionA(rfh));
175     // Quit when OnFrameStartsHoldingIndexedDBConnections(routing_id)
176     // is invoked.
177     run_loop.Run();
178   }
179 
180   {
181     // Close the connection. Expect observer notification.
182     base::RunLoop run_loop;
183     EXPECT_CALL(
184         mock_observer_client_,
185         OnStopUsing(routing_id,
186                     blink::mojom::ObservedFeatureType::kIndexedDBConnection))
187         .WillOnce([&](GlobalFrameRoutingId, blink::mojom::ObservedFeatureType) {
188           run_loop.Quit();
189         });
190     EXPECT_TRUE(ExecJs(rfh, "CloseConnection('A');"));
191     // Quit when OnFrameStopsHoldingIndexedDBConnections(routing_id)
192     // is invoked.
193     run_loop.Run();
194   }
195 }
196 
197 // Verify that content::FeatureObserver is notified when a frame opens multiple
198 // IndexedDB connections (notifications only when the number of held connections
199 // switches between zero and non-zero).
200 // Disabled on ChromeOS release build for flakiness. See crbug.com/1030733.
201 #if defined(OS_CHROMEOS) && defined(NDEBUG)
202 #define MAYBE_ObserverTwoLocks DISABLED_ObserverTwoLocks
203 #else
204 #define MAYBE_ObserverTwoLocks ObserverTwoLocks
205 #endif
IN_PROC_BROWSER_TEST_F(IndexedDBFeatureObserverBrowserTest,MAYBE_ObserverTwoLocks)206 IN_PROC_BROWSER_TEST_F(IndexedDBFeatureObserverBrowserTest,
207                        MAYBE_ObserverTwoLocks) {
208   if (!CheckShouldRunTestAndNavigate())
209     return;
210 
211   RenderFrameHost* rfh = shell()->web_contents()->GetMainFrame();
212   GlobalFrameRoutingId routing_id(rfh->GetProcess()->GetID(),
213                                   rfh->GetRoutingID());
214 
215   {
216     // Open a connection. Expect observer notification.
217     base::RunLoop run_loop;
218     EXPECT_CALL(
219         mock_observer_client_,
220         OnStartUsing(routing_id,
221                      blink::mojom::ObservedFeatureType::kIndexedDBConnection))
222         .WillOnce([&](GlobalFrameRoutingId, blink::mojom::ObservedFeatureType) {
223           run_loop.Quit();
224         });
225     EXPECT_TRUE(OpenConnectionA(rfh));
226     // Quit when OnFrameStartsHoldingIndexedDBConnections(routing_id)
227     // is invoked.
228     run_loop.Run();
229   }
230 
231   // Open a second connection. Don't expect a notification.
232   EXPECT_TRUE(OpenConnectionB(rfh));
233   // Wait a short timeout to make sure that the observer is not notified.
234   RunLoopWithTimeout();
235 
236   // Close the connection. Don't expect a notification.
237   EXPECT_TRUE(ExecJs(rfh, "CloseConnection('B');"));
238   // Wait a short timeout to make sure that the observer is not notified.
239   RunLoopWithTimeout();
240 
241   {
242     // Close the connection. Expect observer notification.
243     base::RunLoop run_loop;
244     EXPECT_CALL(
245         mock_observer_client_,
246         OnStopUsing(routing_id,
247                     blink::mojom::ObservedFeatureType::kIndexedDBConnection))
248         .WillOnce([&](GlobalFrameRoutingId, blink::mojom::ObservedFeatureType) {
249           run_loop.Quit();
250         });
251     EXPECT_TRUE(ExecJs(rfh, "CloseConnection('A');"));
252     // Quit when OnFrameStopsHoldingIndexedDBConnections(routing_id)
253     // is invoked.
254     run_loop.Run();
255   }
256 }
257 
258 // Verify that content::FeatureObserver is notified when a frame with active
259 // IndexedDB connections is navigated away.
IN_PROC_BROWSER_TEST_F(IndexedDBFeatureObserverBrowserTest,ObserverNavigate)260 IN_PROC_BROWSER_TEST_F(IndexedDBFeatureObserverBrowserTest, ObserverNavigate) {
261   if (!CheckShouldRunTestAndNavigate())
262     return;
263 
264   RenderFrameHost* rfh = shell()->web_contents()->GetMainFrame();
265   GlobalFrameRoutingId routing_id(rfh->GetProcess()->GetID(),
266                                   rfh->GetRoutingID());
267 
268   {
269     // Open a connection. Expect observer notification.
270     base::RunLoop run_loop;
271     EXPECT_CALL(
272         mock_observer_client_,
273         OnStartUsing(routing_id,
274                      blink::mojom::ObservedFeatureType::kIndexedDBConnection))
275         .WillOnce([&](GlobalFrameRoutingId, blink::mojom::ObservedFeatureType) {
276           run_loop.Quit();
277         });
278     EXPECT_TRUE(OpenConnectionA(rfh));
279     // Quit when OnFrameStartsHoldingIndexedDBConnections(routing_id)
280     // is invoked.
281     run_loop.Run();
282   }
283 
284   {
285     // Navigate away. Expect observer notification.
286     base::RunLoop run_loop;
287     EXPECT_CALL(
288         mock_observer_client_,
289         OnStopUsing(routing_id,
290                     blink::mojom::ObservedFeatureType::kIndexedDBConnection))
291         .WillOnce([&](GlobalFrameRoutingId, blink::mojom::ObservedFeatureType) {
292           run_loop.Quit();
293         });
294     EXPECT_TRUE(NavigateToURL(shell(), GetTestURL("b.com")));
295     // Quit when OnFrameStopsHoldingIndexedDBConnections(routing_id)
296     // is invoked.
297     run_loop.Run();
298   }
299 }
300 
301 // Verify that content::FeatureObserver is *not* notified when a dedicated
302 // worker opens/closes an IndexedDB connection.
IN_PROC_BROWSER_TEST_F(IndexedDBFeatureObserverBrowserTest,ObserverDedicatedWorker)303 IN_PROC_BROWSER_TEST_F(IndexedDBFeatureObserverBrowserTest,
304                        ObserverDedicatedWorker) {
305   if (!CheckShouldRunTestAndNavigate())
306     return;
307 
308   RenderFrameHost* rfh = shell()->web_contents()->GetMainFrame();
309 
310   // Use EvalJs() instead of ExecJs() to ensure that this doesn't return before
311   // the lock is acquired and released by the worker.
312   EXPECT_TRUE(EvalJs(rfh, R"(
313       (async () => {
314         await OpenConnectionFromDedicatedWorker();
315         return true;
316       }) ();
317   )")
318                   .ExtractBool());
319 
320   // Wait a short timeout to make sure that the observer is not notified.
321   RunLoopWithTimeout();
322 }
323 
324 // SharedWorkers are not enabled on Android. https://crbug.com/154571
325 #if !defined(OS_ANDROID)
326 // Verify that content::FeatureObserver is *not* notified when a shared worker
327 // opens/closes an IndexedDB connection.
IN_PROC_BROWSER_TEST_F(IndexedDBFeatureObserverBrowserTest,ObserverSharedWorker)328 IN_PROC_BROWSER_TEST_F(IndexedDBFeatureObserverBrowserTest,
329                        ObserverSharedWorker) {
330   if (!CheckShouldRunTestAndNavigate())
331     return;
332 
333   RenderFrameHost* rfh = shell()->web_contents()->GetMainFrame();
334 
335   // Use EvalJs() instead of ExecJs() to ensure that this doesn't return before
336   // the lock is acquired and released by the worker.
337   EXPECT_TRUE(EvalJs(rfh, R"(
338       (async () => {
339         await OpenConnectionFromSharedWorker();
340         return true;
341       }) ();
342   )")
343                   .ExtractBool());
344 
345   // Wait a short timeout to make sure that the observer is not notified.
346   RunLoopWithTimeout();
347 }
348 #endif  // !defined(OS_ANDROID)
349 
350 // Verify that content::FeatureObserver is *not* notified when a service worker
351 // opens/closes an IndexedDB connection.
IN_PROC_BROWSER_TEST_F(IndexedDBFeatureObserverBrowserTest,ObserverServiceWorker)352 IN_PROC_BROWSER_TEST_F(IndexedDBFeatureObserverBrowserTest,
353                        ObserverServiceWorker) {
354   if (!CheckShouldRunTestAndNavigate())
355     return;
356 
357   RenderFrameHost* rfh = shell()->web_contents()->GetMainFrame();
358 
359   // Use EvalJs() instead of ExecJs() to ensure that this doesn't return before
360   // the lock is acquired and released by the worker.
361   EXPECT_TRUE(EvalJs(rfh, R"(
362       (async () => {
363         await OpenConnectionFromServiceWorker();
364         return true;
365       }) ();
366   )")
367                   .ExtractBool());
368 
369   // Wait a short timeout to make sure that the observer is not notified.
370   RunLoopWithTimeout();
371 }
372 
373 }  // namespace content
374