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) |
| 40 | : model_(model), desired_count_(0), scoped_observer_(this) { |
| 41 | scoped_observer_.Add(model); |
| 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_; |
| 65 | ScopedObserver<TabStripModel, TabStripModelObserver> scoped_observer_; |
| 66 | |
| 67 | DISALLOW_COPY_AND_ASSIGN(TestTabStripModelObserver); |
| 68 | }; |
| 69 | |
| 70 | } // namespace |
| 71 | |
jam | bb11ed74 | 2017-05-01 17:27:59 | [diff] [blame] | 72 | class ExtensionUnloadBrowserTest : public ExtensionBrowserTest { |
| 73 | public: |
| 74 | void SetUpOnMainThread() override { |
| 75 | ExtensionBrowserTest::SetUpOnMainThread(); |
| 76 | host_resolver()->AddRule("maps.google.com", "127.0.0.1"); |
| 77 | } |
| 78 | }; |
rdevlin.cronin | 23544134 | 2016-09-26 22:39:01 | [diff] [blame] | 79 | |
| 80 | IN_PROC_BROWSER_TEST_F(ExtensionUnloadBrowserTest, TestUnload) { |
| 81 | // Load an extension that installs unload and beforeunload listeners. |
| 82 | const Extension* extension = |
| 83 | LoadExtension(test_data_dir_.AppendASCII("unload_listener")); |
| 84 | ASSERT_TRUE(extension); |
| 85 | std::string id = extension->id(); |
| 86 | ASSERT_EQ(1, browser()->tab_strip_model()->count()); |
| 87 | GURL initial_tab_url = |
| 88 | browser()->tab_strip_model()->GetWebContentsAt(0)->GetLastCommittedURL(); |
| 89 | ui_test_utils::NavigateToURLWithDisposition( |
| 90 | browser(), extension->GetResourceURL("page.html"), |
| 91 | WindowOpenDisposition::NEW_FOREGROUND_TAB, |
| 92 | ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); |
| 93 | EXPECT_EQ(2, browser()->tab_strip_model()->count()); |
creis | caec94b6 | 2017-03-25 00:25:10 | [diff] [blame] | 94 | DisableExtension(id); |
rdevlin.cronin | 23544134 | 2016-09-26 22:39:01 | [diff] [blame] | 95 | // There should only be one remaining web contents - the initial one. |
| 96 | ASSERT_EQ(1, browser()->tab_strip_model()->count()); |
| 97 | EXPECT_EQ( |
| 98 | initial_tab_url, |
| 99 | browser()->tab_strip_model()->GetWebContentsAt(0)->GetLastCommittedURL()); |
| 100 | } |
| 101 | |
creis | caec94b6 | 2017-03-25 00:25:10 | [diff] [blame] | 102 | // After an extension is uninstalled, network requests from its content scripts |
| 103 | // should fail but not kill the renderer process. |
| 104 | IN_PROC_BROWSER_TEST_F(ExtensionUnloadBrowserTest, UnloadWithContentScripts) { |
creis | caec94b6 | 2017-03-25 00:25:10 | [diff] [blame] | 105 | ASSERT_TRUE(embedded_test_server()->Start()); |
| 106 | |
| 107 | // Load an extension with a content script that has a button to send XHRs. |
| 108 | const Extension* extension = |
| 109 | LoadExtension(test_data_dir_.AppendASCII("xhr_from_content_script")); |
| 110 | ASSERT_TRUE(extension); |
| 111 | std::string id = extension->id(); |
| 112 | ASSERT_EQ(1, browser()->tab_strip_model()->count()); |
| 113 | GURL test_url = embedded_test_server()->GetURL("/title1.html"); |
| 114 | ui_test_utils::NavigateToURL(browser(), test_url); |
| 115 | |
| 116 | // Sending an XHR with the extension's Origin header should succeed when the |
| 117 | // extension is installed. |
| 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 | |
| 133 | // Sending an XHR with the extension's Origin header should fail but not kill |
| 134 | // the tab. |
| 135 | EXPECT_TRUE(content::ExecuteScriptAndExtractBool( |
| 136 | browser()->tab_strip_model()->GetActiveWebContents(), kSendXhrScript, |
| 137 | &xhr_result)); |
| 138 | EXPECT_FALSE(xhr_result); |
| 139 | |
| 140 | // Ensure the process has not been killed. |
| 141 | EXPECT_TRUE(browser() |
| 142 | ->tab_strip_model() |
| 143 | ->GetActiveWebContents() |
| 144 | ->GetMainFrame() |
| 145 | ->IsRenderFrameLive()); |
| 146 | } |
| 147 | |
Devlin Cronin | 14025c6 | 2019-06-27 17:56:14 | [diff] [blame] | 148 | // Tests that windows with opaque origins opened by the extension are closed |
| 149 | // when the extension is unloaded. Regression test for https://crbug.com/894477. |
| 150 | IN_PROC_BROWSER_TEST_F(ExtensionUnloadBrowserTest, OpenedOpaqueWindows) { |
| 151 | TestExtensionDir test_dir; |
| 152 | constexpr char kManifest[] = |
| 153 | R"({ |
| 154 | "name": "Test", |
| 155 | "manifest_version": 2, |
| 156 | "version": "0.1", |
| 157 | "background": { |
| 158 | "scripts": ["background.js"] |
| 159 | } |
| 160 | })"; |
| 161 | test_dir.WriteManifest(kManifest); |
| 162 | test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), |
| 163 | "window.open('about:blank');"); |
| 164 | |
| 165 | const GURL about_blank(url::kAboutBlankURL); |
| 166 | content::TestNavigationObserver about_blank_observer(about_blank); |
| 167 | about_blank_observer.StartWatchingNewWebContents(); |
| 168 | const Extension* extension = LoadExtension(test_dir.UnpackedPath()); |
| 169 | ASSERT_TRUE(extension); |
| 170 | about_blank_observer.WaitForNavigationFinished(); |
| 171 | |
| 172 | EXPECT_EQ(2, browser()->tab_strip_model()->count()); |
| 173 | content::WebContents* web_contents = |
| 174 | browser()->tab_strip_model()->GetActiveWebContents(); |
| 175 | EXPECT_EQ(about_blank, web_contents->GetLastCommittedURL()); |
| 176 | url::Origin frame_origin = |
| 177 | web_contents->GetMainFrame()->GetLastCommittedOrigin(); |
| 178 | url::SchemeHostPort precursor_tuple = |
| 179 | frame_origin.GetTupleOrPrecursorTupleIfOpaque(); |
| 180 | EXPECT_EQ(kExtensionScheme, precursor_tuple.scheme()); |
| 181 | EXPECT_EQ(extension->id(), precursor_tuple.host()); |
| 182 | |
| 183 | TestTabStripModelObserver test_tab_strip_model_observer( |
| 184 | browser()->tab_strip_model()); |
| 185 | extension_service()->DisableExtension(extension->id(), |
| 186 | disable_reason::DISABLE_USER_ACTION); |
| 187 | test_tab_strip_model_observer.WaitForTabCount(1); |
| 188 | |
| 189 | EXPECT_EQ(1, browser()->tab_strip_model()->count()); |
| 190 | } |
| 191 | |
Greg Thompson | ce8736b | 2019-07-18 10:52:31 | [diff] [blame^] | 192 | // Flaky timeouts on Win7 Tests (dbg)(1); see https://crbug.com/985255. |
| 193 | #if defined(OS_WIN) && !defined(NDEBUG) |
| 194 | #define MAYBE_CrashedTabs DISABLED_CrashedTabs |
| 195 | #else |
| 196 | #define MAYBE_CrashedTabs CrashedTabs |
| 197 | #endif |
| 198 | IN_PROC_BROWSER_TEST_F(ExtensionUnloadBrowserTest, MAYBE_CrashedTabs) { |
Devlin Cronin | 14025c6 | 2019-06-27 17:56:14 | [diff] [blame] | 199 | TestExtensionDir test_dir; |
| 200 | test_dir.WriteManifest( |
| 201 | R"({ |
| 202 | "name": "test extension", |
| 203 | "manifest_version": 2, |
| 204 | "version": "0.1" |
| 205 | })"); |
| 206 | test_dir.WriteFile(FILE_PATH_LITERAL("page.html"), |
| 207 | "<!doctype html><html><body>Hello world</body></html>"); |
| 208 | scoped_refptr<const Extension> extension( |
| 209 | LoadExtension(test_dir.UnpackedPath())); |
| 210 | ASSERT_TRUE(extension); |
| 211 | const GURL page_url = extension->GetResourceURL("page.html"); |
| 212 | ui_test_utils::NavigateToURLWithDisposition( |
| 213 | browser(), page_url, WindowOpenDisposition::NEW_FOREGROUND_TAB, |
| 214 | ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); |
| 215 | |
| 216 | EXPECT_EQ(2, browser()->tab_strip_model()->count()); |
| 217 | |
| 218 | content::WebContents* active_tab = |
| 219 | browser()->tab_strip_model()->GetActiveWebContents(); |
| 220 | EXPECT_EQ(page_url, active_tab->GetLastCommittedURL()); |
| 221 | |
| 222 | { |
| 223 | content::ScopedAllowRendererCrashes allow_renderer_crashes( |
| 224 | active_tab->GetMainFrame()->GetProcess()); |
| 225 | ui_test_utils::NavigateToURLWithDisposition( |
| 226 | browser(), GURL("chrome://crash"), WindowOpenDisposition::CURRENT_TAB, |
| 227 | ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); |
| 228 | } |
| 229 | |
| 230 | // There should still be two open tabs, but the active one is crashed. |
| 231 | EXPECT_EQ(2, browser()->tab_strip_model()->count()); |
| 232 | EXPECT_TRUE(active_tab->IsCrashed()); |
| 233 | |
| 234 | // Even though the tab is crashed, it should still have the last committed |
| 235 | // URL of the extension page. |
| 236 | EXPECT_EQ(page_url, active_tab->GetLastCommittedURL()); |
| 237 | |
| 238 | // Unloading the extension should close the crashed tab, since its origin was |
| 239 | // still the extension's origin. |
| 240 | TestTabStripModelObserver test_tab_strip_model_observer( |
| 241 | browser()->tab_strip_model()); |
| 242 | extension_service()->DisableExtension(extension->id(), |
| 243 | disable_reason::DISABLE_USER_ACTION); |
| 244 | test_tab_strip_model_observer.WaitForTabCount(1); |
| 245 | |
| 246 | EXPECT_EQ(1, browser()->tab_strip_model()->count()); |
| 247 | EXPECT_NE(extension->url().GetOrigin(), browser() |
| 248 | ->tab_strip_model() |
| 249 | ->GetActiveWebContents() |
| 250 | ->GetLastCommittedURL() |
| 251 | .GetOrigin()); |
| 252 | } |
| 253 | |
rdevlin.cronin | 23544134 | 2016-09-26 22:39:01 | [diff] [blame] | 254 | // TODO(devlin): Investigate what to do for embedded iframes. |
| 255 | |
| 256 | } // namespace extensions |