1 // Copyright (c) 2012 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 // Contains holistic tests of the bindings infrastructure
6
7 #include "base/run_loop.h"
8 #include "chrome/browser/extensions/api/permissions/permissions_api.h"
9 #include "chrome/browser/extensions/extension_apitest.h"
10 #include "chrome/browser/ui/browser.h"
11 #include "chrome/browser/ui/browser_tabstrip.h"
12 #include "chrome/browser/ui/tabs/tab_strip_model.h"
13 #include "chrome/test/base/ui_test_utils.h"
14 #include "components/embedder_support/switches.h"
15 #include "components/sessions/content/session_tab_helper.h"
16 #include "content/public/browser/browser_thread.h"
17 #include "content/public/browser/render_frame_host.h"
18 #include "content/public/browser/render_view_host.h"
19 #include "content/public/common/content_features.h"
20 #include "content/public/test/browser_test.h"
21 #include "content/public/test/browser_test_utils.h"
22 #include "content/public/test/test_navigation_observer.h"
23 #include "extensions/browser/event_router.h"
24 #include "extensions/browser/extension_host.h"
25 #include "extensions/browser/process_manager.h"
26 #include "extensions/test/extension_test_message_listener.h"
27 #include "extensions/test/result_catcher.h"
28 #include "extensions/test/test_extension_dir.h"
29 #include "net/dns/mock_host_resolver.h"
30 #include "net/test/embedded_test_server/embedded_test_server.h"
31 #include "third_party/blink/public/common/input/web_mouse_event.h"
32
33 namespace extensions {
34 namespace {
35
MouseDownInWebContents(content::WebContents * web_contents)36 void MouseDownInWebContents(content::WebContents* web_contents) {
37 blink::WebMouseEvent mouse_event(
38 blink::WebInputEvent::Type::kMouseDown,
39 blink::WebInputEvent::kNoModifiers,
40 blink::WebInputEvent::GetStaticTimeStampForTests());
41 mouse_event.button = blink::WebMouseEvent::Button::kLeft;
42 mouse_event.SetPositionInWidget(10, 10);
43 mouse_event.click_count = 1;
44 web_contents->GetMainFrame()
45 ->GetRenderViewHost()
46 ->GetWidget()
47 ->ForwardMouseEvent(mouse_event);
48 }
49
MouseUpInWebContents(content::WebContents * web_contents)50 void MouseUpInWebContents(content::WebContents* web_contents) {
51 blink::WebMouseEvent mouse_event(
52 blink::WebInputEvent::Type::kMouseUp, blink::WebInputEvent::kNoModifiers,
53 blink::WebInputEvent::GetStaticTimeStampForTests());
54 mouse_event.button = blink::WebMouseEvent::Button::kLeft;
55 mouse_event.SetPositionInWidget(10, 10);
56 mouse_event.click_count = 1;
57 web_contents->GetMainFrame()
58 ->GetRenderViewHost()
59 ->GetWidget()
60 ->ForwardMouseEvent(mouse_event);
61 }
62
63 class ExtensionBindingsApiTest : public ExtensionApiTest {
64 public:
ExtensionBindingsApiTest()65 ExtensionBindingsApiTest() {}
~ExtensionBindingsApiTest()66 ~ExtensionBindingsApiTest() override {}
67
SetUpOnMainThread()68 void SetUpOnMainThread() override {
69 ExtensionApiTest::SetUpOnMainThread();
70 host_resolver()->AddRule("*", "127.0.0.1");
71 ASSERT_TRUE(StartEmbeddedTestServer());
72 }
73
74 private:
75 DISALLOW_COPY_AND_ASSIGN(ExtensionBindingsApiTest);
76 };
77
IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,UnavailableBindingsNeverRegistered)78 IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,
79 UnavailableBindingsNeverRegistered) {
80 // Test will request the 'storage' permission.
81 PermissionsRequestFunction::SetIgnoreUserGestureForTests(true);
82 ASSERT_TRUE(RunExtensionTest(
83 "bindings/unavailable_bindings_never_registered")) << message_;
84 }
85
IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,ExceptionInHandlerShouldNotCrash)86 IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,
87 ExceptionInHandlerShouldNotCrash) {
88 ASSERT_TRUE(RunExtensionSubtest(
89 "bindings/exception_in_handler_should_not_crash",
90 "page.html")) << message_;
91 }
92
93 // Tests that an error raised during an async function still fires
94 // the callback, but sets chrome.runtime.lastError.
IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,LastError)95 IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest, LastError) {
96 ExtensionTestMessageListener ready_listener("ready", /*will_reply=*/false);
97 ASSERT_TRUE(LoadExtension(
98 test_data_dir_.AppendASCII("bindings").AppendASCII("last_error")));
99 ASSERT_TRUE(ready_listener.WaitUntilSatisfied());
100
101 // Get the ExtensionHost that is hosting our background page.
102 extensions::ProcessManager* manager =
103 extensions::ProcessManager::Get(browser()->profile());
104 extensions::ExtensionHost* host = FindHostWithPath(manager, "/bg.html", 1);
105 ASSERT_TRUE(host);
106
107 bool result = false;
108 ASSERT_TRUE(content::ExecuteScriptAndExtractBool(host->host_contents(),
109 "testLastError()", &result));
110 EXPECT_TRUE(result);
111 }
112
113 // Regression test that we don't delete our own bindings with about:blank
114 // iframes.
IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,AboutBlankIframe)115 IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest, AboutBlankIframe) {
116 ResultCatcher catcher;
117 ExtensionTestMessageListener listener("load", true);
118
119 ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII("bindings")
120 .AppendASCII("about_blank_iframe")));
121
122 ASSERT_TRUE(listener.WaitUntilSatisfied());
123
124 const Extension* extension = LoadExtension(
125 test_data_dir_.AppendASCII("bindings")
126 .AppendASCII("internal_apis_not_on_chrome_object"));
127 ASSERT_TRUE(extension);
128 listener.Reply(extension->id());
129
130 ASSERT_TRUE(catcher.GetNextResult()) << message_;
131 }
132
IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,InternalAPIsNotOnChromeObject)133 IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,
134 InternalAPIsNotOnChromeObject) {
135 ASSERT_TRUE(RunExtensionSubtest(
136 "bindings/internal_apis_not_on_chrome_object",
137 "page.html")) << message_;
138 }
139
140 // Tests that we don't override events when bindings are re-injected.
141 // Regression test for http://crbug.com/269149.
142 // Regression test for http://crbug.com/436593.
143 // Flaky on Mac. http://crbug.com/733064.
144 #if defined(OS_MAC)
145 #define MAYBE_EventOverriding DISABLED_EventOverriding
146 #else
147 #define MAYBE_EventOverriding EventOverriding
148 #endif
IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,MAYBE_EventOverriding)149 IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest, MAYBE_EventOverriding) {
150 ASSERT_TRUE(RunExtensionTest("bindings/event_overriding")) << message_;
151 // The extension test removes a window and, during window removal, sends the
152 // success message. Make sure we flush all pending tasks.
153 base::RunLoop().RunUntilIdle();
154 }
155
156 // Tests the effectiveness of the 'nocompile' feature file property.
157 // Regression test for http://crbug.com/356133.
IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,Nocompile)158 IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest, Nocompile) {
159 ASSERT_TRUE(RunExtensionSubtest("bindings/nocompile", "page.html"))
160 << message_;
161 }
162
IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,ApiEnums)163 IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest, ApiEnums) {
164 ASSERT_TRUE(RunExtensionTest("bindings/api_enums")) << message_;
165 }
166
167 // Regression test for http://crbug.com/504011 - proper access checks on
168 // getModuleSystem().
IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,ModuleSystem)169 IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest, ModuleSystem) {
170 ASSERT_TRUE(RunExtensionTest("bindings/module_system")) << message_;
171 }
172
IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,NoExportOverriding)173 IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest, NoExportOverriding) {
174 // We need to create runtime bindings in the web page. An extension that's
175 // externally connectable will do that for us.
176 ASSERT_TRUE(LoadExtension(
177 test_data_dir_.AppendASCII("bindings")
178 .AppendASCII("externally_connectable_everywhere")));
179
180 ui_test_utils::NavigateToURL(
181 browser(),
182 embedded_test_server()->GetURL(
183 "/extensions/api_test/bindings/override_exports.html"));
184
185 // See chrome/test/data/extensions/api_test/bindings/override_exports.html.
186 std::string result;
187 EXPECT_TRUE(content::ExecuteScriptAndExtractString(
188 browser()->tab_strip_model()->GetActiveWebContents(),
189 "window.domAutomationController.send("
190 "document.getElementById('status').textContent.trim());",
191 &result));
192 EXPECT_EQ("success", result);
193 }
194
IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,NoGinDefineOverriding)195 IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest, NoGinDefineOverriding) {
196 // We need to create runtime bindings in the web page. An extension that's
197 // externally connectable will do that for us.
198 ASSERT_TRUE(LoadExtension(
199 test_data_dir_.AppendASCII("bindings")
200 .AppendASCII("externally_connectable_everywhere")));
201
202 ui_test_utils::NavigateToURL(
203 browser(),
204 embedded_test_server()->GetURL(
205 "/extensions/api_test/bindings/override_gin_define.html"));
206 ASSERT_FALSE(
207 browser()->tab_strip_model()->GetActiveWebContents()->IsCrashed());
208
209 // See chrome/test/data/extensions/api_test/bindings/override_gin_define.html.
210 std::string result;
211 EXPECT_TRUE(content::ExecuteScriptAndExtractString(
212 browser()->tab_strip_model()->GetActiveWebContents(),
213 "window.domAutomationController.send("
214 "document.getElementById('status').textContent.trim());",
215 &result));
216 EXPECT_EQ("success", result);
217 }
218
IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,HandlerFunctionTypeChecking)219 IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest, HandlerFunctionTypeChecking) {
220 ui_test_utils::NavigateToURL(
221 browser(),
222 embedded_test_server()->GetURL(
223 "/extensions/api_test/bindings/handler_function_type_checking.html"));
224 content::WebContents* web_contents =
225 browser()->tab_strip_model()->GetActiveWebContents();
226 EXPECT_FALSE(web_contents->IsCrashed());
227 // See handler_function_type_checking.html.
228 std::string result;
229 EXPECT_TRUE(content::ExecuteScriptAndExtractString(
230 web_contents,
231 "window.domAutomationController.send("
232 "document.getElementById('status').textContent.trim());",
233 &result));
234 EXPECT_EQ("success", result);
235 }
236
IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,MoreNativeFunctionInterceptionTests)237 IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,
238 MoreNativeFunctionInterceptionTests) {
239 // We need to create runtime bindings in the web page. An extension that's
240 // externally connectable will do that for us.
241 ASSERT_TRUE(
242 LoadExtension(test_data_dir_.AppendASCII("bindings")
243 .AppendASCII("externally_connectable_everywhere")));
244
245 ui_test_utils::NavigateToURL(
246 browser(),
247 embedded_test_server()->GetURL(
248 "/extensions/api_test/bindings/function_interceptions.html"));
249 content::WebContents* web_contents =
250 browser()->tab_strip_model()->GetActiveWebContents();
251 EXPECT_FALSE(web_contents->IsCrashed());
252 // See function_interceptions.html.
253 std::string result;
254 EXPECT_TRUE(content::ExecuteScriptAndExtractString(
255 web_contents, "window.domAutomationController.send(window.testStatus);",
256 &result));
257 EXPECT_EQ("success", result);
258 }
259
260 class FramesExtensionBindingsApiTest : public ExtensionBindingsApiTest {
261 public:
SetUpCommandLine(base::CommandLine * command_line)262 void SetUpCommandLine(base::CommandLine* command_line) override {
263 ExtensionBindingsApiTest::SetUpCommandLine(command_line);
264 command_line->AppendSwitch(embedder_support::kDisablePopupBlocking);
265 }
266 };
267
268 // This tests that web pages with iframes or child windows pointing at
269 // chrome-extenison:// urls, both web_accessible and nonexistent pages, don't
270 // get improper extensions bindings injected while they briefly still point at
271 // about:blank and are still scriptable by their parent.
272 //
273 // The general idea is to load up 2 extensions, one which listens for external
274 // messages ("receiver") and one which we'll try first faking messages from in
275 // the web page's iframe, as well as actually send a message from later
276 // ("sender").
IN_PROC_BROWSER_TEST_F(FramesExtensionBindingsApiTest,FramesBeforeNavigation)277 IN_PROC_BROWSER_TEST_F(FramesExtensionBindingsApiTest, FramesBeforeNavigation) {
278 // Load the sender and receiver extensions, and make sure they are ready.
279 ExtensionTestMessageListener sender_ready("sender_ready", true);
280 const Extension* sender = LoadExtension(
281 test_data_dir_.AppendASCII("bindings").AppendASCII("message_sender"));
282 ASSERT_NE(nullptr, sender);
283 ASSERT_TRUE(sender_ready.WaitUntilSatisfied());
284
285 ExtensionTestMessageListener receiver_ready("receiver_ready", false);
286 const Extension* receiver =
287 LoadExtension(test_data_dir_.AppendASCII("bindings")
288 .AppendASCII("external_message_listener"));
289 ASSERT_NE(nullptr, receiver);
290 ASSERT_TRUE(receiver_ready.WaitUntilSatisfied());
291
292 // Load the web page which tries to impersonate the sender extension via
293 // scripting iframes/child windows before they finish navigating to pages
294 // within the sender extension.
295 ui_test_utils::NavigateToURL(
296 browser(),
297 embedded_test_server()->GetURL(
298 "/extensions/api_test/bindings/frames_before_navigation.html"));
299
300 bool page_success = false;
301 ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
302 browser()->tab_strip_model()->GetWebContentsAt(0), "getResult()",
303 &page_success));
304 EXPECT_TRUE(page_success);
305
306 // Reply to |sender|, causing it to send a message over to |receiver|, and
307 // then ask |receiver| for the total message count. It should be 1 since
308 // |receiver| should not have received any impersonated messages.
309 sender_ready.Reply(receiver->id());
310 int message_count = 0;
311 ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
312 ProcessManager::Get(profile())
313 ->GetBackgroundHostForExtension(receiver->id())
314 ->host_contents(),
315 "getMessageCountAfterReceivingRealSenderMessage()", &message_count));
316 EXPECT_EQ(1, message_count);
317 }
318
IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,TestFreezingChrome)319 IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest, TestFreezingChrome) {
320 ui_test_utils::NavigateToURL(
321 browser(), embedded_test_server()->GetURL(
322 "/extensions/api_test/bindings/freeze.html"));
323 content::WebContents* web_contents =
324 browser()->tab_strip_model()->GetActiveWebContents();
325 ASSERT_FALSE(web_contents->IsCrashed());
326 }
327
328 // Tests interaction with event filter parsing.
IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,TestEventFilterParsing)329 IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest, TestEventFilterParsing) {
330 ExtensionTestMessageListener listener("ready", false);
331 ASSERT_TRUE(
332 LoadExtension(test_data_dir_.AppendASCII("bindings/event_filter")));
333 ASSERT_TRUE(listener.WaitUntilSatisfied());
334
335 ResultCatcher catcher;
336 ui_test_utils::NavigateToURL(
337 browser(), embedded_test_server()->GetURL("example.com", "/title1.html"));
338 ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
339 }
340
341 // crbug.com/733337
IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,ValidationInterception)342 IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest, ValidationInterception) {
343 // We need to create runtime bindings in the web page. An extension that's
344 // externally connectable will do that for us.
345 ASSERT_TRUE(
346 LoadExtension(test_data_dir_.AppendASCII("bindings")
347 .AppendASCII("externally_connectable_everywhere")));
348
349 content::WebContents* web_contents =
350 browser()->tab_strip_model()->GetActiveWebContents();
351 ui_test_utils::NavigateToURL(
352 browser(),
353 embedded_test_server()->GetURL(
354 "/extensions/api_test/bindings/validation_interception.html"));
355 EXPECT_TRUE(content::WaitForLoadStop(web_contents));
356 ASSERT_FALSE(web_contents->IsCrashed());
357 bool caught = false;
358 ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
359 web_contents, "domAutomationController.send(caught)", &caught));
360 EXPECT_TRUE(caught);
361 }
362
IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,UncaughtExceptionLogging)363 IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest, UncaughtExceptionLogging) {
364 ASSERT_TRUE(RunExtensionTest("bindings/uncaught_exception_logging"))
365 << message_;
366 }
367
368 // Verify that when a web frame embeds an extension subframe, and that subframe
369 // is the only active portion of the extension, the subframe gets proper JS
370 // bindings. See https://crbug.com/760341.
IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,ExtensionSubframeGetsBindings)371 IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,
372 ExtensionSubframeGetsBindings) {
373 // Load an extension that does not have a background page or popup, so it
374 // won't be activated just yet.
375 const extensions::Extension* extension =
376 LoadExtension(test_data_dir_.AppendASCII("bindings")
377 .AppendASCII("extension_subframe_gets_bindings"));
378 ASSERT_TRUE(extension);
379
380 // Navigate current tab to a web URL with a subframe.
381 content::WebContents* web_contents =
382 browser()->tab_strip_model()->GetActiveWebContents();
383 ui_test_utils::NavigateToURL(browser(),
384 embedded_test_server()->GetURL("/iframe.html"));
385
386 // Navigate the subframe to the extension URL, which should activate the
387 // extension.
388 GURL extension_url(extension->GetResourceURL("page.html"));
389 ResultCatcher catcher;
390 content::NavigateIframeToURL(web_contents, "test", extension_url);
391 ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
392 }
393
IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,ExtensionListenersRemoveContext)394 IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,
395 ExtensionListenersRemoveContext) {
396 const Extension* extension = LoadExtension(
397 test_data_dir_.AppendASCII("bindings/listeners_destroy_context"));
398 ASSERT_TRUE(extension);
399
400 ExtensionTestMessageListener listener("ready", true);
401
402 // Navigate to a web page with an iframe (the iframe is title1.html).
403 GURL main_frame_url = embedded_test_server()->GetURL("a.com", "/iframe.html");
404 ui_test_utils::NavigateToURL(browser(), main_frame_url);
405
406 content::WebContents* tab =
407 browser()->tab_strip_model()->GetActiveWebContents();
408
409 content::RenderFrameHost* main_frame = tab->GetMainFrame();
410 content::RenderFrameHost* subframe = ChildFrameAt(main_frame, 0);
411 content::RenderFrameDeletedObserver subframe_deleted(subframe);
412
413 // Wait for the extension's content script to be ready.
414 ASSERT_TRUE(listener.WaitUntilSatisfied());
415
416 // It's actually critical to the test that these frames are in the same
417 // process, because otherwise a crash in the iframe wouldn't be detectable
418 // (since we rely on JS execution in the main frame to tell if the renderer
419 // crashed - see comment below).
420 content::RenderProcessHost* main_frame_process = main_frame->GetProcess();
421 EXPECT_EQ(main_frame_process, subframe->GetProcess());
422
423 ExtensionTestMessageListener failure_listener("failed", false);
424
425 // Tell the extension to register listeners that will remove the iframe, and
426 // trigger them.
427 listener.Reply("go!");
428
429 // The frame will be deleted.
430 subframe_deleted.WaitUntilDeleted();
431
432 // Unfortunately, we don't have a good way of checking if something crashed
433 // after the frame was removed. WebContents::IsCrashed() seems like it should
434 // work, but is insufficient. Instead, use JS execution as the source of
435 // true.
436 EXPECT_FALSE(tab->IsCrashed());
437 EXPECT_EQ(main_frame_url, main_frame->GetLastCommittedURL());
438 EXPECT_EQ(main_frame_process, main_frame->GetProcess());
439 bool renderer_valid = false;
440 EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
441 main_frame, "domAutomationController.send(true);", &renderer_valid));
442 EXPECT_TRUE(renderer_valid);
443 EXPECT_FALSE(failure_listener.was_satisfied());
444 }
445
IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,UseAPIsAfterContextRemoval)446 IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest, UseAPIsAfterContextRemoval) {
447 EXPECT_TRUE(RunExtensionTest("bindings/invalidate_context")) << message_;
448 }
449
450 // Tests that we don't crash if the extension invalidates the context in a
451 // callback with a runtime.lastError present. Regression test for
452 // https://crbug.com/944014.
IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,InvalidateContextInCallbackWithLastError)453 IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,
454 InvalidateContextInCallbackWithLastError) {
455 TestExtensionDir dir;
456 dir.WriteManifest(
457 R"({
458 "name": "Invalidate Context in onDisconnect",
459 "version": "0.1",
460 "manifest_version": 2,
461 "background": {"scripts": ["background.js"]}
462 })");
463
464 constexpr char kFrameHtml[] =
465 R"(<html>
466 <body></body>
467 <script src="frame.js"></script>
468 </html>)";
469 constexpr char kFrameJs[] =
470 R"(chrome.tabs.executeScript({code: ''}, () => {
471 // We expect a last error to be present, since we don't have access
472 // to the tab.
473 chrome.test.assertTrue(!!chrome.runtime.lastError);
474 // Remove the frame from the DOM. This causes blink to remove the
475 // associated script contexts.
476 parent.document.body.removeChild(
477 parent.document.body.querySelector('iframe'));
478 });)";
479 constexpr char kBackgroundJs[] =
480 R"(let frame = document.createElement('iframe');
481 frame.src = 'frame.html';
482 let observer = new MutationObserver((mutationList) => {
483 for (let mutation of mutationList) {
484 if (mutation.removedNodes.length == 0)
485 continue;
486 chrome.test.assertEq(1, mutation.removedNodes.length);
487 chrome.test.assertEq('IFRAME', mutation.removedNodes[0].tagName);
488 chrome.test.notifyPass();
489 break;
490 }
491 });
492 observer.observe(document.body, {childList: true});
493 document.body.appendChild(frame);)";
494 dir.WriteFile(FILE_PATH_LITERAL("frame.html"), kFrameHtml);
495 dir.WriteFile(FILE_PATH_LITERAL("frame.js"), kFrameJs);
496 dir.WriteFile(FILE_PATH_LITERAL("background.js"), kBackgroundJs);
497
498 ResultCatcher catcher;
499 const Extension* extension = LoadExtension(dir.UnpackedPath());
500 ASSERT_TRUE(extension);
501 EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
502 }
503
504 // TODO(devlin): Can this be combined with
505 // ExtensionBindingsApiTest.UseAPIsAfterContextRemoval?
IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,UseAppAPIAfterFrameRemoval)506 IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest, UseAppAPIAfterFrameRemoval) {
507 ASSERT_TRUE(RunExtensionTest("crazy_extension"));
508 }
509
510 // Tests attaching two listeners from the same extension but different pages,
511 // then removing one, and ensuring the second is still notified.
512 // Regression test for https://crbug.com/868763.
IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,MultipleEventListenersFromDifferentContextsAndTheSameExtension)513 IN_PROC_BROWSER_TEST_F(
514 ExtensionBindingsApiTest,
515 MultipleEventListenersFromDifferentContextsAndTheSameExtension) {
516 // A script that listens for tab creation and populates the result in a
517 // global variable.
518 constexpr char kTestPageScript[] = R"(
519 window.tabEventId = -1;
520 function registerListener() {
521 chrome.tabs.onCreated.addListener((tab) => {
522 window.tabEventId = tab.id;
523 });
524 }
525 )";
526 TestExtensionDir test_dir;
527 test_dir.WriteManifest(R"(
528 {
529 "name": "Duplicate event listeners",
530 "manifest_version": 2,
531 "version": "0.1"
532 })");
533 test_dir.WriteFile(FILE_PATH_LITERAL("page.html"),
534 R"(<html><script src="page.js"></script></html>)");
535 test_dir.WriteFile(FILE_PATH_LITERAL("page.js"), kTestPageScript);
536
537 const Extension* extension = LoadExtension(test_dir.UnpackedPath());
538 ASSERT_TRUE(extension);
539
540 // Set up: open two tabs to the same extension page, and wait for each to
541 // load.
542 const GURL page_url = extension->GetResourceURL("page.html");
543 ui_test_utils::NavigateToURLWithDisposition(
544 browser(), page_url, WindowOpenDisposition::NEW_FOREGROUND_TAB,
545 ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
546 content::WebContents* first_tab =
547 browser()->tab_strip_model()->GetActiveWebContents();
548 ui_test_utils::NavigateToURLWithDisposition(
549 browser(), page_url, WindowOpenDisposition::NEW_FOREGROUND_TAB,
550 ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
551 content::WebContents* second_tab =
552 browser()->tab_strip_model()->GetActiveWebContents();
553
554 // Initially, there are no listeners registered.
555 EventRouter* event_router = EventRouter::Get(profile());
556 EXPECT_FALSE(event_router->ExtensionHasEventListener(extension->id(),
557 "tabs.onCreated"));
558
559 // Register both lsiteners, and verify they were added.
560 ASSERT_TRUE(content::ExecuteScript(first_tab, "registerListener()"));
561 ASSERT_TRUE(content::ExecuteScript(second_tab, "registerListener()"));
562 EXPECT_TRUE(event_router->ExtensionHasEventListener(extension->id(),
563 "tabs.onCreated"));
564
565 // Close one of the extension pages.
566 constexpr bool add_to_history = false;
567 content::WebContentsDestroyedWatcher watcher(second_tab);
568 chrome::CloseWebContents(browser(), second_tab, add_to_history);
569 watcher.Wait();
570 // Hacky round trip to the renderer to flush IPCs.
571 ASSERT_TRUE(content::ExecuteScript(first_tab, ""));
572
573 // Since the second page is still open, the extension should still be
574 // registered as a listener.
575 EXPECT_TRUE(event_router->ExtensionHasEventListener(extension->id(),
576 "tabs.onCreated"));
577
578 // Open a new tab.
579 ui_test_utils::NavigateToURLWithDisposition(
580 browser(), GURL("chrome://newtab"),
581 WindowOpenDisposition::NEW_FOREGROUND_TAB,
582 ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
583 content::WebContents* new_tab =
584 browser()->tab_strip_model()->GetActiveWebContents();
585
586 // The extension should have been notified about the new tab, and have
587 // recorded the result.
588 int result_tab_id = -1;
589 EXPECT_TRUE(content::ExecuteScriptAndExtractInt(
590 first_tab, "domAutomationController.send(window.tabEventId)",
591 &result_tab_id));
592 EXPECT_EQ(sessions::SessionTabHelper::IdForTab(new_tab).id(), result_tab_id);
593 }
594
595 // Verifies that user gestures are carried through extension messages.
IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,UserGestureFromExtensionMessageTest)596 IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,
597 UserGestureFromExtensionMessageTest) {
598 TestExtensionDir test_dir;
599 test_dir.WriteManifest(
600 R"({
601 "name": "User Gesture Content Script",
602 "manifest_version": 2,
603 "version": "0.1",
604 "background": { "scripts": ["background.js"] },
605 "content_scripts": [{
606 "matches": ["*://*.example.com:*/*"],
607 "js": ["content_script.js"],
608 "run_at": "document_end"
609 }]
610 })");
611 test_dir.WriteFile(FILE_PATH_LITERAL("content_script.js"),
612 R"(const button = document.getElementById('go-button');
613 button.addEventListener('click', () => {
614 chrome.runtime.sendMessage('clicked');
615 });)");
616 test_dir.WriteFile(FILE_PATH_LITERAL("background.js"),
617 R"(chrome.runtime.onMessage.addListener((message) => {
618 chrome.test.sendMessage(
619 'Clicked: ' +
620 chrome.test.isProcessingUserGesture());
621 });)");
622
623 const Extension* extension = LoadExtension(test_dir.UnpackedPath());
624 ASSERT_TRUE(extension);
625
626 const GURL url = embedded_test_server()->GetURL(
627 "example.com", "/extensions/page_with_button.html");
628 ui_test_utils::NavigateToURL(browser(), url);
629
630 content::WebContents* tab =
631 browser()->tab_strip_model()->GetActiveWebContents();
632
633 {
634 // Passing a message without an active user gesture shouldn't result in a
635 // gesture being active on the receiving end.
636 ExtensionTestMessageListener listener(false);
637 content::EvalJsResult result =
638 content::EvalJs(tab, "document.getElementById('go-button').click()",
639 content::EXECUTE_SCRIPT_NO_USER_GESTURE);
640 EXPECT_TRUE(result.value.is_none());
641
642 EXPECT_TRUE(listener.WaitUntilSatisfied());
643 EXPECT_EQ("Clicked: false", listener.message());
644 }
645
646 {
647 // If there is an active user gesture when the message is sent, we should
648 // synthesize a user gesture on the receiving end.
649 ExtensionTestMessageListener listener(false);
650 content::EvalJsResult result =
651 content::EvalJs(tab, "document.getElementById('go-button').click()");
652 EXPECT_TRUE(result.value.is_none());
653
654 EXPECT_TRUE(listener.WaitUntilSatisfied());
655 EXPECT_EQ("Clicked: true", listener.message());
656 }
657 }
658
659 // Verifies that user gestures from API calls are active when the callback is
660 // triggered.
IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,UserGestureInExtensionAPICallback)661 IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,
662 UserGestureInExtensionAPICallback) {
663 TestExtensionDir test_dir;
664 test_dir.WriteManifest(
665 R"({
666 "name": "User Gesture Extension API Callback",
667 "manifest_version": 2,
668 "version": "0.1"
669 })");
670 test_dir.WriteFile(FILE_PATH_LITERAL("page.html"), "<html></html>");
671
672 const Extension* extension = LoadExtension(test_dir.UnpackedPath());
673 ASSERT_TRUE(extension);
674
675 const GURL extension_page = extension->GetResourceURL("page.html");
676 ui_test_utils::NavigateToURL(browser(), extension_page);
677
678 content::WebContents* tab =
679 browser()->tab_strip_model()->GetActiveWebContents();
680
681 constexpr char kScript[] =
682 R"(chrome.tabs.query({}, (tabs) => {
683 let message;
684 if (chrome.runtime.lastError)
685 message = 'Unexpected error: ' + chrome.runtime.lastError;
686 else
687 message = 'Has gesture: ' + chrome.test.isProcessingUserGesture();
688 domAutomationController.send(message);
689 });)";
690
691 {
692 // Triggering an API without an active gesture shouldn't result in a
693 // gesture in the callback.
694 std::string message;
695 EXPECT_TRUE(content::ExecuteScriptWithoutUserGestureAndExtractString(
696 tab, kScript, &message));
697 EXPECT_EQ("Has gesture: false", message);
698 }
699 {
700 // If there was an active gesture at the time of the API call, there should
701 // be an active gesture in the callback.
702 std::string message;
703 EXPECT_TRUE(content::ExecuteScriptAndExtractString(tab, kScript, &message));
704 EXPECT_EQ("Has gesture: true", message);
705 }
706 }
707
708 // Tests that a web page can consume a user gesture after an extension sends and
709 // receives a reply during the same user gesture.
710 // Regression test for https://crbug.com/921141.
IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,WebUserGestureAfterMessagingCallback)711 IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,
712 WebUserGestureAfterMessagingCallback) {
713 TestExtensionDir test_dir;
714 test_dir.WriteManifest(
715 R"({
716 "name": "User Gesture Messaging Test",
717 "version": "0.1",
718 "manifest_version": 2,
719 "content_scripts": [{
720 "matches": ["*://*/*"],
721 "js": ["content_script.js"],
722 "run_at": "document_start"
723 }],
724 "background": {
725 "scripts": ["background.js"]
726 }
727 })");
728 test_dir.WriteFile(FILE_PATH_LITERAL("content_script.js"),
729 R"(window.addEventListener('mousedown', () => {
730 chrome.runtime.sendMessage('hello', () => {
731 let message = chrome.test.isProcessingUserGesture() ?
732 'got reply' : 'no user gesture';
733 chrome.test.sendMessage(message);
734 });
735 });)");
736 test_dir.WriteFile(
737 FILE_PATH_LITERAL("background.js"),
738 R"(chrome.runtime.onMessage.addListener((message, sender, respond) => {
739 respond('reply');
740 });
741 chrome.test.sendMessage('ready');)");
742
743 const Extension* extension = nullptr;
744 {
745 ExtensionTestMessageListener listener("ready", false);
746 extension = LoadExtension(test_dir.UnpackedPath());
747 ASSERT_TRUE(extension);
748 EXPECT_TRUE(listener.WaitUntilSatisfied());
749 }
750
751 ui_test_utils::NavigateToURL(
752 browser(), embedded_test_server()->GetURL(
753 "/extensions/api_test/bindings/user_gesture_test.html"));
754 content::WebContents* web_contents =
755 browser()->tab_strip_model()->GetActiveWebContents();
756 ASSERT_TRUE(web_contents);
757
758 {
759 ExtensionTestMessageListener listener("got reply", false);
760 listener.set_failure_message("no user gesture");
761 MouseDownInWebContents(web_contents);
762 EXPECT_TRUE(listener.WaitUntilSatisfied());
763 }
764
765 MouseUpInWebContents(web_contents);
766
767 EXPECT_EQ("success",
768 content::EvalJs(web_contents, "window.getEnteredFullscreen",
769 content::EXECUTE_SCRIPT_NO_USER_GESTURE));
770 }
771
772 // Tests that a web page can consume a user gesture after an extension calls a
773 // method and receives the response in the callback.
774 // Regression test for https://crbug.com/921141.
IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,WebUserGestureAfterApiCallback)775 IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,
776 WebUserGestureAfterApiCallback) {
777 TestExtensionDir test_dir;
778 test_dir.WriteManifest(
779 R"({
780 "name": "User Gesture Messaging Test",
781 "version": "0.1",
782 "manifest_version": 2,
783 "content_scripts": [{
784 "matches": ["*://*/*"],
785 "js": ["content_script.js"],
786 "run_at": "document_start"
787 }],
788 "permissions": ["storage"]
789 })");
790 test_dir.WriteFile(FILE_PATH_LITERAL("content_script.js"),
791 R"(window.addEventListener('mousedown', () => {
792 chrome.storage.local.get('foo', () => {
793 let message = chrome.test.isProcessingUserGesture() ?
794 'got reply' : 'no user gesture';
795 chrome.test.sendMessage(message);
796 });
797 });)");
798
799 const Extension* extension = LoadExtension(test_dir.UnpackedPath());
800 ASSERT_TRUE(extension);
801
802 ui_test_utils::NavigateToURL(
803 browser(), embedded_test_server()->GetURL(
804 "/extensions/api_test/bindings/user_gesture_test.html"));
805 content::WebContents* web_contents =
806 browser()->tab_strip_model()->GetActiveWebContents();
807 ASSERT_TRUE(web_contents);
808
809 {
810 ExtensionTestMessageListener listener("got reply", false);
811 listener.set_failure_message("no user gesture");
812 MouseDownInWebContents(web_contents);
813 EXPECT_TRUE(listener.WaitUntilSatisfied());
814 }
815
816 MouseUpInWebContents(web_contents);
817
818 EXPECT_EQ("success",
819 content::EvalJs(web_contents, "window.getEnteredFullscreen",
820 content::EXECUTE_SCRIPT_NO_USER_GESTURE));
821 }
822
823 // Tests that bindings are properly instantiated for a window navigated to an
824 // extension URL after being opened with an undefined URL.
825 // Regression test for https://crbug.com/925118.
IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,TestBindingsAvailableWithNavigatedBlankWindow)826 IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,
827 TestBindingsAvailableWithNavigatedBlankWindow) {
828 constexpr char kManifest[] =
829 R"({
830 "name": "chrome.runtime bug checker",
831 "description": "test case for crbug.com/925118",
832 "version": "0",
833 "manifest_version": 2
834 })";
835 constexpr char kOpenerHTML[] =
836 R"(<!DOCTYPE html>
837 <html>
838 <head>
839 <script src='opener.js'></script>
840 </head>
841 <body>
842 </body>
843 </html>)";
844 // opener.js opens a blank window and then navigates it to an extension URL
845 // (where extension APIs should be available).
846 constexpr char kOpenerJS[] =
847 R"(const url = chrome.runtime.getURL('/page.html');
848 const win = window.open(undefined, '');
849 win.location = url;
850 chrome.test.notifyPass())";
851 constexpr char kPageHTML[] =
852 R"(<!DOCTYPE html>
853 <html>
854 This space intentionally left blank.
855 </html>)";
856 TestExtensionDir extension_dir;
857 extension_dir.WriteManifest(kManifest);
858 extension_dir.WriteFile(FILE_PATH_LITERAL("opener.html"), kOpenerHTML);
859 extension_dir.WriteFile(FILE_PATH_LITERAL("opener.js"), kOpenerJS);
860 extension_dir.WriteFile(FILE_PATH_LITERAL("page.html"), kPageHTML);
861
862 const Extension* extension = LoadExtension(extension_dir.UnpackedPath());
863 const GURL target_url = extension->GetResourceURL("page.html");
864
865 ResultCatcher catcher;
866 content::TestNavigationObserver observer(target_url);
867 observer.StartWatchingNewWebContents();
868 ui_test_utils::NavigateToURL(browser(),
869 extension->GetResourceURL("opener.html"));
870 EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
871 observer.Wait();
872 EXPECT_TRUE(observer.last_navigation_succeeded());
873
874 content::WebContents* web_contents =
875 browser()->tab_strip_model()->GetActiveWebContents();
876 EXPECT_EQ(target_url, web_contents->GetLastCommittedURL());
877
878 // Check whether bindings are available. They should be.
879 constexpr char kScript[] =
880 R"(let message;
881 if (!chrome.runtime)
882 message = 'Runtime not defined';
883 else if (!chrome.tabs)
884 message = 'Tabs not defined';
885 else
886 message = 'success';
887 domAutomationController.send(message);)";
888 std::string result;
889 // Note: Can't use EvalJs() because of CSP in extension pages.
890 EXPECT_TRUE(
891 content::ExecuteScriptAndExtractString(web_contents, kScript, &result));
892 EXPECT_EQ("success", result);
893 }
894
895 // Tests the aliasing of chrome.extension methods to their chrome.runtime
896 // equivalents.
IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,ChromeExtensionIsAliasedToChromeRuntime)897 IN_PROC_BROWSER_TEST_F(ExtensionBindingsApiTest,
898 ChromeExtensionIsAliasedToChromeRuntime) {
899 constexpr char kManifest[] =
900 R"({
901 "name": "Test",
902 "version": "0.1",
903 "manifest_version": 2,
904 "background": { "scripts": ["background.js"] }
905 })";
906 constexpr char kBackground[] =
907 R"(chrome.test.runTests([
908 function chromeExtensionIsAliased() {
909 // Sanity check: chrome.extension is directly aliased to
910 // chrome.runtime.
911 chrome.test.assertTrue(!!chrome.runtime);
912 chrome.test.assertTrue(!!chrome.runtime.sendMessage);
913 chrome.test.assertEq(chrome.runtime.sendMessage,
914 chrome.extension.sendMessage);
915 chrome.test.succeed();
916 },
917 function testOverridingFailsGracefully() {
918 let intercepted = false;
919 // Modify the chrome.runtime object, which is the source for the
920 // chrome.extension API, to throw an error when sendMessage is
921 // accessed. Nothing should blow up.
922 // Regression test for https://crbug.com/949170.
923 Object.defineProperty(
924 chrome.runtime,
925 'sendMessage',
926 {
927 get() {
928 intercepted = true;
929 throw new Error('Mwahaha');
930 }
931 });
932 chrome.extension.sendMessage;
933 chrome.test.assertTrue(intercepted);
934 chrome.test.succeed();
935 }
936 ]);)";
937
938 TestExtensionDir extension_dir;
939 extension_dir.WriteManifest(kManifest);
940 extension_dir.WriteFile(FILE_PATH_LITERAL("background.js"), kBackground);
941 ResultCatcher catcher;
942 ASSERT_TRUE(LoadExtension(extension_dir.UnpackedPath()));
943 EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
944 }
945
946 } // namespace
947 } // namespace extensions
948