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