rdevlin.cronin | 23544134 | 2016-09-26 22:39:01 | [diff] [blame] | 1 | // Copyright 2016 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 | |
Jun Cai | 53c54b22 | 2018-08-16 00:14:04 | [diff] [blame] | 5 | #include "base/feature_list.h" |
Devlin Cronin | 14025c6 | 2019-06-27 17:56:14 | [diff] [blame] | 6 | #include "base/run_loop.h" |
| 7 | #include "base/scoped_observer.h" |
Greg Thompson | ce8736b | 2019-07-18 10:52:31 | [diff] [blame] | 8 | #include "build/build_config.h" |
rdevlin.cronin | 23544134 | 2016-09-26 22:39:01 | [diff] [blame] | 9 | #include "chrome/browser/extensions/extension_browsertest.h" |
| 10 | #include "chrome/browser/extensions/extension_service.h" |
| 11 | #include "chrome/browser/ui/tabs/tab_strip_model.h" |
Devlin Cronin | 14025c6 | 2019-06-27 17:56:14 | [diff] [blame] | 12 | #include "chrome/browser/ui/tabs/tab_strip_model_observer.h" |
rdevlin.cronin | 23544134 | 2016-09-26 22:39:01 | [diff] [blame] | 13 | #include "chrome/test/base/ui_test_utils.h" |
creis | caec94b6 | 2017-03-25 00:25:10 | [diff] [blame] | 14 | #include "content/public/browser/render_frame_host.h" |
Devlin Cronin | 14025c6 | 2019-06-27 17:56:14 | [diff] [blame] | 15 | #include "content/public/browser/web_contents.h" |
creis | caec94b6 | 2017-03-25 00:25:10 | [diff] [blame] | 16 | #include "content/public/test/browser_test_utils.h" |
Devlin Cronin | 14025c6 | 2019-06-27 17:56:14 | [diff] [blame] | 17 | #include "content/public/test/no_renderer_crashes_assertion.h" |
| 18 | #include "content/public/test/test_navigation_observer.h" |
| 19 | #include "content/public/test/test_utils.h" |
| 20 | #include "extensions/browser/disable_reason.h" |
| 21 | #include "extensions/common/constants.h" |
| 22 | #include "extensions/common/extension.h" |
| 23 | #include "extensions/test/test_extension_dir.h" |
creis | caec94b6 | 2017-03-25 00:25:10 | [diff] [blame] | 24 | #include "net/dns/mock_host_resolver.h" |
| 25 | #include "net/test/embedded_test_server/embedded_test_server.h" |
Jun Cai | 53c54b22 | 2018-08-16 00:14:04 | [diff] [blame] | 26 | #include "services/network/public/cpp/features.h" |
rdevlin.cronin | 23544134 | 2016-09-26 22:39:01 | [diff] [blame] | 27 | #include "ui/base/window_open_disposition.h" |
Devlin Cronin | 14025c6 | 2019-06-27 17:56:14 | [diff] [blame] | 28 | #include "url/origin.h" |
| 29 | #include "url/url_constants.h" |
rdevlin.cronin | 23544134 | 2016-09-26 22:39:01 | [diff] [blame] | 30 | |
| 31 | namespace extensions { |
| 32 | |
Devlin Cronin | 14025c6 | 2019-06-27 17:56:14 | [diff] [blame] | 33 | namespace { |
| 34 | |
| 35 | // A helper class to wait for a particular tab count. Requires the tab strip |
| 36 | // to outlive this object. |
| 37 | class TestTabStripModelObserver : public TabStripModelObserver { |
| 38 | public: |
| 39 | explicit TestTabStripModelObserver(TabStripModel* model) |
Elly Fong-Jones | 4e3d25c7 | 2019-08-12 18:17:15 | [diff] [blame] | 40 | : model_(model), desired_count_(0) { |
| 41 | model->AddObserver(this); |
Devlin Cronin | 14025c6 | 2019-06-27 17:56:14 | [diff] [blame] | 42 | } |
| 43 | ~TestTabStripModelObserver() override = default; |
| 44 | |
| 45 | void WaitForTabCount(int count) { |
| 46 | if (model_->count() == count) |
| 47 | return; |
| 48 | desired_count_ = count; |
| 49 | run_loop_.Run(); |
| 50 | } |
| 51 | |
| 52 | private: |
| 53 | // TabStripModelObserver: |
| 54 | void OnTabStripModelChanged( |
| 55 | TabStripModel* tab_strip_model, |
| 56 | const TabStripModelChange& change, |
| 57 | const TabStripSelectionChange& selection) override { |
| 58 | if (model_->count() == desired_count_) |
| 59 | run_loop_.Quit(); |
| 60 | } |
| 61 | |
| 62 | TabStripModel* model_; |
| 63 | int desired_count_; |
| 64 | base::RunLoop run_loop_; |
Devlin Cronin | 14025c6 | 2019-06-27 17:56:14 | [diff] [blame] | 65 | |
| 66 | DISALLOW_COPY_AND_ASSIGN(TestTabStripModelObserver); |
| 67 | }; |
| 68 | |
| 69 | } // namespace |
| 70 | |
jam | bb11ed74 | 2017-05-01 17:27:59 | [diff] [blame] | 71 | class ExtensionUnloadBrowserTest : public ExtensionBrowserTest { |
| 72 | public: |
| 73 | void SetUpOnMainThread() override { |
| 74 | ExtensionBrowserTest::SetUpOnMainThread(); |
| 75 | host_resolver()->AddRule("maps.google.com", "127.0.0.1"); |
| 76 | } |
| 77 | }; |
rdevlin.cronin | 23544134 | 2016-09-26 22:39:01 | [diff] [blame] | 78 | |
| 79 | IN_PROC_BROWSER_TEST_F(ExtensionUnloadBrowserTest, TestUnload) { |
| 80 | // Load an extension that installs unload and beforeunload listeners. |
| 81 | const Extension* extension = |
| 82 | LoadExtension(test_data_dir_.AppendASCII("unload_listener")); |
| 83 | ASSERT_TRUE(extension); |
| 84 | std::string id = extension->id(); |
| 85 | ASSERT_EQ(1, browser()->tab_strip_model()->count()); |
| 86 | GURL initial_tab_url = |
| 87 | browser()->tab_strip_model()->GetWebContentsAt(0)->GetLastCommittedURL(); |
| 88 | ui_test_utils::NavigateToURLWithDisposition( |
| 89 | browser(), extension->GetResourceURL("page.html"), |
| 90 | WindowOpenDisposition::NEW_FOREGROUND_TAB, |
| 91 | ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); |
| 92 | EXPECT_EQ(2, browser()->tab_strip_model()->count()); |
creis | caec94b6 | 2017-03-25 00:25:10 | [diff] [blame] | 93 | DisableExtension(id); |
rdevlin.cronin | 23544134 | 2016-09-26 22:39:01 | [diff] [blame] | 94 | // There should only be one remaining web contents - the initial one. |
| 95 | ASSERT_EQ(1, browser()->tab_strip_model()->count()); |
| 96 | EXPECT_EQ( |
| 97 | initial_tab_url, |
| 98 | browser()->tab_strip_model()->GetWebContentsAt(0)->GetLastCommittedURL()); |
| 99 | } |
| 100 | |
creis | caec94b6 | 2017-03-25 00:25:10 | [diff] [blame] | 101 | // After an extension is uninstalled, network requests from its content scripts |
| 102 | // should fail but not kill the renderer process. |
| 103 | IN_PROC_BROWSER_TEST_F(ExtensionUnloadBrowserTest, UnloadWithContentScripts) { |
creis | caec94b6 | 2017-03-25 00:25:10 | [diff] [blame] | 104 | ASSERT_TRUE(embedded_test_server()->Start()); |
| 105 | |
| 106 | // Load an extension with a content script that has a button to send XHRs. |
| 107 | const Extension* extension = |
| 108 | LoadExtension(test_data_dir_.AppendASCII("xhr_from_content_script")); |
| 109 | ASSERT_TRUE(extension); |
| 110 | std::string id = extension->id(); |
| 111 | ASSERT_EQ(1, browser()->tab_strip_model()->count()); |
| 112 | GURL test_url = embedded_test_server()->GetURL("/title1.html"); |
| 113 | ui_test_utils::NavigateToURL(browser(), test_url); |
| 114 | |
Lukasz Anforowicz | 87d09459 | 2019-09-09 18:13:43 | [diff] [blame] | 115 | // The content script sends an XHR with the webpage's (rather than |
| 116 | // extension's) Origin header - this should succeed (given that |
| 117 | // xhr.txt.mock-http-headers says `Access-Control-Allow-Origin: *`). |
creis | caec94b6 | 2017-03-25 00:25:10 | [diff] [blame] | 118 | const char kSendXhrScript[] = "document.getElementById('xhrButton').click();"; |
| 119 | bool xhr_result = false; |
| 120 | EXPECT_TRUE(content::ExecuteScriptAndExtractBool( |
| 121 | browser()->tab_strip_model()->GetActiveWebContents(), kSendXhrScript, |
| 122 | &xhr_result)); |
| 123 | EXPECT_TRUE(xhr_result); |
| 124 | |
| 125 | DisableExtension(id); |
| 126 | |
| 127 | // The tab should still be open with the content script injected. |
| 128 | ASSERT_EQ(1, browser()->tab_strip_model()->count()); |
| 129 | EXPECT_EQ( |
| 130 | test_url, |
| 131 | browser()->tab_strip_model()->GetWebContentsAt(0)->GetLastCommittedURL()); |
| 132 | |
Lukasz Anforowicz | 87d09459 | 2019-09-09 18:13:43 | [diff] [blame] | 133 | // The content script sends an XHR with the webpage's (rather than |
| 134 | // extension's) Origin header - this should succeed (given that |
| 135 | // xhr.txt.mock-http-headers says `Access-Control-Allow-Origin: *`). |
creis | caec94b6 | 2017-03-25 00:25:10 | [diff] [blame] | 136 | EXPECT_TRUE(content::ExecuteScriptAndExtractBool( |
| 137 | browser()->tab_strip_model()->GetActiveWebContents(), kSendXhrScript, |
| 138 | &xhr_result)); |
Lukasz Anforowicz | 87d09459 | 2019-09-09 18:13:43 | [diff] [blame] | 139 | EXPECT_TRUE(xhr_result); |
creis | caec94b6 | 2017-03-25 00:25:10 | [diff] [blame] | 140 | |
| 141 | // Ensure the process has not been killed. |
| 142 | EXPECT_TRUE(browser() |
| 143 | ->tab_strip_model() |
| 144 | ->GetActiveWebContents() |
| 145 | ->GetMainFrame() |
| 146 | ->IsRenderFrameLive()); |
| 147 | } |
| 148 | |
Devlin Cronin | 14025c6 | 2019-06-27 17:56:14 | [diff] [blame] | 149 | // Tests that windows with opaque origins opened by the extension are closed |
| 150 | // when the extension is unloaded. Regression test for https://crbug.com/894477. |
| 151 | IN_PROC_BROWSER_TEST_F(ExtensionUnloadBrowserTest, OpenedOpaqueWindows) { |
| 152 | TestExtensionDir test_dir; |
| 153 | constexpr char kManifest[] = |
| 154 | R"({ |
| 155 | "name": "Test", |
| 156 | "manifest_version": 2, |
| 157 | "version": "0.1", |
| 158 | "background": { |
| 159 | "scripts": ["background.js"] |
| 160 | } |
| 161 | })"; |
| 162 | test_dir.WriteManifest(kManifest); |
| 163 | test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), |
| 164 | "window.open('about:blank');"); |
| 165 | |
| 166 | const GURL about_blank(url::kAboutBlankURL); |
| 167 | content::TestNavigationObserver about_blank_observer(about_blank); |
| 168 | about_blank_observer.StartWatchingNewWebContents(); |
| 169 | const Extension* extension = LoadExtension(test_dir.UnpackedPath()); |
| 170 | ASSERT_TRUE(extension); |
| 171 | about_blank_observer.WaitForNavigationFinished(); |
| 172 | |
| 173 | EXPECT_EQ(2, browser()->tab_strip_model()->count()); |
| 174 | content::WebContents* web_contents = |
| 175 | browser()->tab_strip_model()->GetActiveWebContents(); |
| 176 | EXPECT_EQ(about_blank, web_contents->GetLastCommittedURL()); |
| 177 | url::Origin frame_origin = |
| 178 | web_contents->GetMainFrame()->GetLastCommittedOrigin(); |
| 179 | url::SchemeHostPort precursor_tuple = |
| 180 | frame_origin.GetTupleOrPrecursorTupleIfOpaque(); |
| 181 | EXPECT_EQ(kExtensionScheme, precursor_tuple.scheme()); |
| 182 | EXPECT_EQ(extension->id(), precursor_tuple.host()); |
| 183 | |
| 184 | TestTabStripModelObserver test_tab_strip_model_observer( |
| 185 | browser()->tab_strip_model()); |
| 186 | extension_service()->DisableExtension(extension->id(), |
| 187 | disable_reason::DISABLE_USER_ACTION); |
| 188 | test_tab_strip_model_observer.WaitForTabCount(1); |
| 189 | |
| 190 | EXPECT_EQ(1, browser()->tab_strip_model()->count()); |
| 191 | } |
| 192 | |
Greg Thompson | ce8736b | 2019-07-18 10:52:31 | [diff] [blame] | 193 | // Flaky timeouts on Win7 Tests (dbg)(1); see https://crbug.com/985255. |
| 194 | #if defined(OS_WIN) && !defined(NDEBUG) |
| 195 | #define MAYBE_CrashedTabs DISABLED_CrashedTabs |
| 196 | #else |
| 197 | #define MAYBE_CrashedTabs CrashedTabs |
| 198 | #endif |
| 199 | IN_PROC_BROWSER_TEST_F(ExtensionUnloadBrowserTest, MAYBE_CrashedTabs) { |
Devlin Cronin | 14025c6 | 2019-06-27 17:56:14 | [diff] [blame] | 200 | TestExtensionDir test_dir; |
| 201 | test_dir.WriteManifest( |
| 202 | R"({ |
| 203 | "name": "test extension", |
| 204 | "manifest_version": 2, |
| 205 | "version": "0.1" |
| 206 | })"); |
| 207 | test_dir.WriteFile(FILE_PATH_LITERAL("page.html"), |
| 208 | "<!doctype html><html><body>Hello world</body></html>"); |
| 209 | scoped_refptr<const Extension> extension( |
| 210 | LoadExtension(test_dir.UnpackedPath())); |
| 211 | ASSERT_TRUE(extension); |
| 212 | const GURL page_url = extension->GetResourceURL("page.html"); |
| 213 | ui_test_utils::NavigateToURLWithDisposition( |
| 214 | browser(), page_url, WindowOpenDisposition::NEW_FOREGROUND_TAB, |
| 215 | ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); |
| 216 | |
| 217 | EXPECT_EQ(2, browser()->tab_strip_model()->count()); |
| 218 | |
| 219 | content::WebContents* active_tab = |
| 220 | browser()->tab_strip_model()->GetActiveWebContents(); |
| 221 | EXPECT_EQ(page_url, active_tab->GetLastCommittedURL()); |
| 222 | |
| 223 | { |
| 224 | content::ScopedAllowRendererCrashes allow_renderer_crashes( |
| 225 | active_tab->GetMainFrame()->GetProcess()); |
| 226 | ui_test_utils::NavigateToURLWithDisposition( |
| 227 | browser(), GURL("chrome://crash"), WindowOpenDisposition::CURRENT_TAB, |
| 228 | ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); |
| 229 | } |
| 230 | |
| 231 | // There should still be two open tabs, but the active one is crashed. |
| 232 | EXPECT_EQ(2, browser()->tab_strip_model()->count()); |
| 233 | EXPECT_TRUE(active_tab->IsCrashed()); |
| 234 | |
| 235 | // Even though the tab is crashed, it should still have the last committed |
| 236 | // URL of the extension page. |
| 237 | EXPECT_EQ(page_url, active_tab->GetLastCommittedURL()); |
| 238 | |
| 239 | // Unloading the extension should close the crashed tab, since its origin was |
| 240 | // still the extension's origin. |
| 241 | TestTabStripModelObserver test_tab_strip_model_observer( |
| 242 | browser()->tab_strip_model()); |
| 243 | extension_service()->DisableExtension(extension->id(), |
| 244 | disable_reason::DISABLE_USER_ACTION); |
| 245 | test_tab_strip_model_observer.WaitForTabCount(1); |
| 246 | |
| 247 | EXPECT_EQ(1, browser()->tab_strip_model()->count()); |
| 248 | EXPECT_NE(extension->url().GetOrigin(), browser() |
| 249 | ->tab_strip_model() |
| 250 | ->GetActiveWebContents() |
| 251 | ->GetLastCommittedURL() |
| 252 | .GetOrigin()); |
| 253 | } |
| 254 | |
rdevlin.cronin | 23544134 | 2016-09-26 22:39:01 | [diff] [blame] | 255 | // TODO(devlin): Investigate what to do for embedded iframes. |
| 256 | |
| 257 | } // namespace extensions |