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