Index: chrome/browser/extensions/process_manager_browsertest.cc |
diff --git a/chrome/browser/extensions/process_manager_browsertest.cc b/chrome/browser/extensions/process_manager_browsertest.cc |
index 3596cbc704611f3a872ebc07db0d972000264b6a..d1e3013722b3b2a1335389b747ddea7aea80cc6c 100644 |
--- a/chrome/browser/extensions/process_manager_browsertest.cc |
+++ b/chrome/browser/extensions/process_manager_browsertest.cc |
@@ -4,23 +4,182 @@ |
#include "extensions/browser/process_manager.h" |
+#include "base/callback.h" |
+#include "base/memory/scoped_ptr.h" |
+#include "base/run_loop.h" |
+#include "base/strings/stringprintf.h" |
#include "chrome/browser/extensions/browser_action_test_util.h" |
#include "chrome/browser/extensions/extension_browsertest.h" |
#include "chrome/browser/extensions/extension_service.h" |
+#include "chrome/browser/extensions/test_extension_dir.h" |
#include "chrome/browser/ui/tabs/tab_strip_model.h" |
+#include "chrome/common/extensions/extension_process_policy.h" |
#include "chrome/test/base/in_process_browser_test.h" |
#include "chrome/test/base/ui_test_utils.h" |
#include "content/public/browser/notification_service.h" |
+#include "content/public/browser/render_frame_host.h" |
#include "content/public/browser/web_contents.h" |
+#include "content/public/test/browser_test_utils.h" |
#include "content/public/test/test_utils.h" |
+#include "extensions/common/value_builder.h" |
+#include "extensions/test/background_page_watcher.h" |
+#include "extensions/test/extension_test_message_listener.h" |
#include "net/dns/mock_host_resolver.h" |
#include "net/test/embedded_test_server/embedded_test_server.h" |
namespace extensions { |
+namespace { |
+ |
+void AddFrameToSet(std::set<content::RenderFrameHost*>* frames, |
+ content::RenderFrameHost* rfh) { |
+ if (rfh->IsRenderFrameLive()) |
+ frames->insert(rfh); |
+} |
+ |
+} // namespace |
+ |
+// Takes a snapshot of all frames upon construction. When Wait() is called, a |
+// MessageLoop is created and Quit when all previously recorded frames are |
+// either present in the tab, or deleted. If a navigation happens between the |
+// construction and the Wait() call, then this logic ensures that all obsolete |
+// RenderFrameHosts have been destructed when Wait() returns. |
+// See also the comment at ProcessManagerBrowserTest::NavigateToURL. |
+class NavigationCompletedObserver : public content::WebContentsObserver { |
+ public: |
+ explicit NavigationCompletedObserver(content::WebContents* web_contents) |
+ : content::WebContentsObserver(web_contents), |
+ message_loop_runner_(new content::MessageLoopRunner) { |
+ web_contents->ForEachFrame( |
+ base::Bind(&AddFrameToSet, base::Unretained(&frames_))); |
+ } |
+ |
+ void Wait() { |
+ if (!AreAllFramesInTab()) |
+ message_loop_runner_->Run(); |
+ } |
+ |
+ void RenderFrameDeleted(content::RenderFrameHost* rfh) override { |
+ if (frames_.erase(rfh) != 0 && message_loop_runner_->loop_running() && |
+ AreAllFramesInTab()) |
+ message_loop_runner_->Quit(); |
+ } |
+ |
+ private: |
+ // Check whether all frames that were recorded at the construction of this |
+ // class are still part of the tab. |
+ bool AreAllFramesInTab() { |
+ std::set<content::RenderFrameHost*> current_frames; |
+ web_contents()->ForEachFrame( |
+ base::Bind(&AddFrameToSet, base::Unretained(¤t_frames))); |
+ for (content::RenderFrameHost* frame : frames_) { |
+ if (current_frames.find(frame) == current_frames.end()) |
+ return false; |
+ } |
+ return true; |
+ } |
+ |
+ std::set<content::RenderFrameHost*> frames_; |
+ scoped_refptr<content::MessageLoopRunner> message_loop_runner_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(NavigationCompletedObserver); |
+}; |
+ |
// Exists as a browser test because ExtensionHosts are hard to create without |
// a real browser. |
-typedef ExtensionBrowserTest ProcessManagerBrowserTest; |
+class ProcessManagerBrowserTest : public ExtensionBrowserTest { |
+ public: |
+ // Create an extension with web-accessible frames and an optional background |
+ // page. |
+ const Extension* CreateExtension(const std::string& name, |
+ bool has_background_process) { |
+ scoped_ptr<TestExtensionDir> dir(new TestExtensionDir()); |
+ |
+ DictionaryBuilder manifest; |
+ manifest.Set("name", name) |
+ .Set("version", "1") |
+ .Set("manifest_version", 2) |
+ // To allow ExecuteScript* to work. |
+ .Set("content_security_policy", |
+ "script-src 'self' 'unsafe-eval'; object-src 'self'") |
+ .Set("sandbox", DictionaryBuilder().Set( |
+ "pages", ListBuilder().Append("sandboxed.html"))) |
+ .Set("web_accessible_resources", ListBuilder().Append("*")); |
+ |
+ if (has_background_process) { |
+ manifest.Set("background", DictionaryBuilder().Set("page", "bg.html")); |
+ dir->WriteFile(FILE_PATH_LITERAL("bg.html"), |
+ "<iframe id='bgframe' src='empty.html'></iframe>"); |
+ } |
+ |
+ dir->WriteFile(FILE_PATH_LITERAL("blank_iframe.html"), |
+ "<iframe id='frame0' src='about:blank'></iframe>"); |
+ |
+ dir->WriteFile(FILE_PATH_LITERAL("srcdoc_iframe.html"), |
+ "<iframe id='frame0' srcdoc='Hello world'></iframe>"); |
+ |
+ dir->WriteFile(FILE_PATH_LITERAL("two_iframes.html"), |
+ "<iframe id='frame1' src='empty.html'></iframe>" |
+ "<iframe id='frame2' src='empty.html'></iframe>"); |
+ |
+ dir->WriteFile(FILE_PATH_LITERAL("sandboxed.html"), "Some sandboxed page"); |
+ |
+ dir->WriteFile(FILE_PATH_LITERAL("empty.html"), ""); |
+ |
+ dir->WriteManifest(manifest.ToJSON()); |
+ |
+ const Extension* extension = LoadExtension(dir->unpacked_path()); |
+ EXPECT_TRUE(extension); |
+ temp_dirs_.push_back(dir.Pass()); |
+ return extension; |
+ } |
+ |
+ // ui_test_utils::NavigateToURL sometimes returns too early: It returns as |
+ // soon as the StopLoading notification has been triggered. This does not |
+ // imply that RenderFrameDeleted was called, so the test may continue too |
+ // early and fail when ProcessManager::GetAllFrames() returns too many frames |
+ // (namely frames that are in the process of being deleted). To work around |
+ // this problem, we also wait until all previous frames have been deleted. |
+ void NavigateToURL(const GURL& url) { |
+ NavigationCompletedObserver observer( |
+ browser()->tab_strip_model()->GetActiveWebContents()); |
+ |
+ ui_test_utils::NavigateToURL(browser(), url); |
+ |
+ // Wait until the last RenderFrameHosts are deleted. This wait doesn't take |
+ // long. |
+ observer.Wait(); |
+ } |
+ |
+ void NavigateIframeToURLAndWait(content::WebContents* web_contents, |
+ const std::string iframe_id, |
+ const GURL& url) { |
+ // This is an improved version of content::NavigateIframeToURL. Unlike the |
+ // other method, this does actually wait until the load of all child frames |
+ // completes. |
+ std::string script = base::StringPrintf( |
+ "var frame = document.getElementById('%s');" |
+ "frame.onload = frame.onerror = function(event) {" |
+ " frame.onload = frame.onerror = null;" |
+ " domAutomationController.send(event.type === 'load');" |
+ "};" |
+ "frame.src = '%s';", |
+ iframe_id.c_str(), url.spec().c_str()); |
+ bool is_loaded = false; |
+ EXPECT_TRUE(ExecuteScriptAndExtractBool(web_contents, script, &is_loaded)); |
+ EXPECT_TRUE(is_loaded); |
+ } |
+ |
+ size_t IfExtensionsIsolated(size_t if_enabled, size_t if_disabled) { |
+ return content::AreAllSitesIsolatedForTesting() || |
+ IsIsolateExtensionsEnabled() |
+ ? if_enabled |
+ : if_disabled; |
+ } |
+ |
+ private: |
+ std::vector<scoped_ptr<TestExtensionDir>> temp_dirs_; |
+}; |
// Test that basic extension loading creates the appropriate ExtensionHosts |
// and background pages. |
@@ -157,6 +316,193 @@ IN_PROC_BROWSER_TEST_F(ProcessManagerBrowserTest, HttpHostMatchingExtensionId) { |
EXPECT_TRUE(pm->GetBackgroundHostForExtension(extension->id())); |
} |
+IN_PROC_BROWSER_TEST_F(ProcessManagerBrowserTest, NoBackgroundPage) { |
+ ASSERT_TRUE(embedded_test_server()->Start()); |
+ |
+ ProcessManager* pm = ProcessManager::Get(profile()); |
+ const Extension* extension = |
+ LoadExtension(test_data_dir_.AppendASCII("api_test") |
+ .AppendASCII("messaging") |
+ .AppendASCII("connect_nobackground")); |
+ ASSERT_TRUE(extension); |
+ |
+ // The extension has no background page. |
+ EXPECT_EQ(0u, pm->GetRenderFrameHostsForExtension(extension->id()).size()); |
+ |
+ // Start in a non-extension process, then navigate to an extension process. |
+ NavigateToURL(embedded_test_server()->GetURL("/empty.html")); |
+ EXPECT_EQ(0u, pm->GetRenderFrameHostsForExtension(extension->id()).size()); |
+ |
+ const GURL extension_url = extension->url().Resolve("manifest.json"); |
+ NavigateToURL(extension_url); |
+ EXPECT_EQ(1u, pm->GetRenderFrameHostsForExtension(extension->id()).size()); |
+ |
+ NavigateToURL(GURL("about:blank")); |
+ EXPECT_EQ(0u, pm->GetRenderFrameHostsForExtension(extension->id()).size()); |
+ |
+ ui_test_utils::NavigateToURLWithDisposition( |
+ browser(), extension_url, NEW_FOREGROUND_TAB, |
+ ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); |
+ EXPECT_EQ(1u, pm->GetRenderFrameHostsForExtension(extension->id()).size()); |
+} |
+ |
+// Tests whether frames are correctly classified. Non-extension frames should |
+// never appear in the list. Top-level extension frames should always appear. |
+// Child extension frames should only appear if it is hosted in an extension |
+// process (i.e. if the top-level frame is an extension page, or if OOP frames |
+// are enabled for extensions). |
+IN_PROC_BROWSER_TEST_F(ProcessManagerBrowserTest, FrameClassification) { |
+ const Extension* extension1 = CreateExtension("Extension 1", false); |
+ const Extension* extension2 = CreateExtension("Extension 2", true); |
+ embedded_test_server()->ServeFilesFromDirectory(extension1->path()); |
+ ASSERT_TRUE(embedded_test_server()->Start()); |
+ |
+ const GURL kExt1TwoFramesUrl(extension1->url().Resolve("two_iframes.html")); |
+ const GURL kExt1EmptyUrl(extension1->url().Resolve("empty.html")); |
+ const GURL kExt2TwoFramesUrl(extension2->url().Resolve("two_iframes.html")); |
+ const GURL kExt2EmptyUrl(extension2->url().Resolve("empty.html")); |
+ |
+ ProcessManager* pm = ProcessManager::Get(profile()); |
+ |
+ // 1 background page + 1 frame in background page from Extension 2. |
+ BackgroundPageWatcher(pm, extension2).WaitForOpen(); |
+ EXPECT_EQ(2u, pm->GetAllFrames().size()); |
+ EXPECT_EQ(0u, pm->GetRenderFrameHostsForExtension(extension1->id()).size()); |
+ EXPECT_EQ(2u, pm->GetRenderFrameHostsForExtension(extension2->id()).size()); |
+ |
+ ExecuteScriptInBackgroundPageNoWait(extension2->id(), |
+ "setTimeout(window.close, 0)"); |
+ BackgroundPageWatcher(pm, extension2).WaitForClose(); |
+ EXPECT_EQ(0u, pm->GetAllFrames().size()); |
+ EXPECT_EQ(0u, pm->GetRenderFrameHostsForExtension(extension2->id()).size()); |
+ |
+ NavigateToURL(embedded_test_server()->GetURL("/two_iframes.html")); |
+ EXPECT_EQ(0u, pm->GetAllFrames().size()); |
+ |
+ content::WebContents* tab = |
+ browser()->tab_strip_model()->GetActiveWebContents(); |
+ |
+ // Tests extension frames in non-extension page. |
+ NavigateIframeToURLAndWait(tab, "frame1", kExt1EmptyUrl); |
+ EXPECT_EQ(IfExtensionsIsolated(1, 0), |
+ pm->GetRenderFrameHostsForExtension(extension1->id()).size()); |
+ EXPECT_EQ(IfExtensionsIsolated(1, 0), pm->GetAllFrames().size()); |
+ |
+ NavigateIframeToURLAndWait(tab, "frame2", kExt2EmptyUrl); |
+ EXPECT_EQ(IfExtensionsIsolated(1, 0), |
+ pm->GetRenderFrameHostsForExtension(extension2->id()).size()); |
+ EXPECT_EQ(IfExtensionsIsolated(2, 0), pm->GetAllFrames().size()); |
+ |
+ // Tests non-extension page in extension frame. |
+ NavigateToURL(kExt1TwoFramesUrl); |
+ // 1 top-level + 2 child frames from Extension 1. |
+ EXPECT_EQ(3u, pm->GetAllFrames().size()); |
+ EXPECT_EQ(3u, pm->GetRenderFrameHostsForExtension(extension1->id()).size()); |
+ EXPECT_EQ(0u, pm->GetRenderFrameHostsForExtension(extension2->id()).size()); |
+ |
+ NavigateIframeToURLAndWait(tab, "frame1", |
+ embedded_test_server()->GetURL("/empty.html")); |
+ // 1 top-level + 1 child frame from Extension 1. |
+ EXPECT_EQ(2u, pm->GetRenderFrameHostsForExtension(extension1->id()).size()); |
+ EXPECT_EQ(2u, pm->GetAllFrames().size()); |
+ |
+ NavigateIframeToURLAndWait(tab, "frame1", kExt1EmptyUrl); |
+ // 1 top-level + 2 child frames from Extension 1. |
+ EXPECT_EQ(3u, pm->GetAllFrames().size()); |
+ EXPECT_EQ(3u, pm->GetRenderFrameHostsForExtension(extension1->id()).size()); |
+ |
+ // Load a frame from another extension. |
+ NavigateIframeToURLAndWait(tab, "frame1", kExt2EmptyUrl); |
+ // 1 top-level + 1 child frame from Extension 1, |
+ // 1 child frame from Extension 2. |
+ EXPECT_EQ(IfExtensionsIsolated(3, 2), pm->GetAllFrames().size()); |
+ EXPECT_EQ(2u, pm->GetRenderFrameHostsForExtension(extension1->id()).size()); |
+ EXPECT_EQ(IfExtensionsIsolated(1, 0), |
+ pm->GetRenderFrameHostsForExtension(extension2->id()).size()); |
+ |
+ // Destroy all existing frames by navigating to another extension. |
+ NavigateToURL(extension2->url().Resolve("empty.html")); |
+ EXPECT_EQ(1u, pm->GetAllFrames().size()); |
+ EXPECT_EQ(0u, pm->GetRenderFrameHostsForExtension(extension1->id()).size()); |
+ EXPECT_EQ(1u, pm->GetRenderFrameHostsForExtension(extension2->id()).size()); |
+ |
+ // Test about:blank and about:srcdoc child frames. |
+ NavigateToURL(extension2->url().Resolve("srcdoc_iframe.html")); |
+ // 1 top-level frame + 1 child frame from Extension 2. |
+ EXPECT_EQ(2u, pm->GetAllFrames().size()); |
+ EXPECT_EQ(2u, pm->GetRenderFrameHostsForExtension(extension2->id()).size()); |
+ |
+ NavigateToURL(extension2->url().Resolve("blank_iframe.html")); |
+ // 1 top-level frame + 1 child frame from Extension 2. |
+ EXPECT_EQ(2u, pm->GetAllFrames().size()); |
+ EXPECT_EQ(2u, pm->GetRenderFrameHostsForExtension(extension2->id()).size()); |
+ |
+ // Sandboxed frames are not viewed as extension frames. |
+ NavigateIframeToURLAndWait(tab, "frame0", |
+ extension2->url().Resolve("sandboxed.html")); |
+ // 1 top-level frame from Extension 2. |
+ EXPECT_EQ(1u, pm->GetAllFrames().size()); |
+ EXPECT_EQ(1u, pm->GetRenderFrameHostsForExtension(extension2->id()).size()); |
+ |
+ NavigateToURL(extension2->url().Resolve("sandboxed.html")); |
+ EXPECT_EQ(0u, pm->GetAllFrames().size()); |
+ EXPECT_EQ(0u, pm->GetRenderFrameHostsForExtension(extension2->id()).size()); |
+ |
+ // Test nested frames (same extension). |
+ NavigateToURL(kExt2TwoFramesUrl); |
+ // 1 top-level + 2 child frames from Extension 2. |
+ EXPECT_EQ(3u, pm->GetAllFrames().size()); |
+ EXPECT_EQ(3u, pm->GetRenderFrameHostsForExtension(extension2->id()).size()); |
+ |
+ NavigateIframeToURLAndWait(tab, "frame1", kExt2TwoFramesUrl); |
+ // 1 top-level + 2 child frames from Extension 1, |
+ // 2 child frames in frame1 from Extension 2. |
+ EXPECT_EQ(5u, pm->GetAllFrames().size()); |
+ EXPECT_EQ(5u, pm->GetRenderFrameHostsForExtension(extension2->id()).size()); |
+ |
+ // The extension frame from the other extension should not be classified as an |
+ // extension (unless out-of-process frames are enabled). |
+ NavigateIframeToURLAndWait(tab, "frame1", kExt1EmptyUrl); |
+ // 1 top-level + 1 child frames from Extension 2, |
+ // 1 child frame from Extension 1. |
+ EXPECT_EQ(IfExtensionsIsolated(3, 2), pm->GetAllFrames().size()); |
+ EXPECT_EQ(IfExtensionsIsolated(1, 0), |
+ pm->GetRenderFrameHostsForExtension(extension1->id()).size()); |
+ EXPECT_EQ(2u, pm->GetRenderFrameHostsForExtension(extension2->id()).size()); |
+ |
+ NavigateIframeToURLAndWait(tab, "frame2", kExt1TwoFramesUrl); |
+ // 1 top-level + 1 child frames from Extension 2, |
+ // 1 child frame + 2 child frames in frame2 from Extension 1. |
+ EXPECT_EQ(IfExtensionsIsolated(5, 1), pm->GetAllFrames().size()); |
+ EXPECT_EQ(IfExtensionsIsolated(4, 0), |
+ pm->GetRenderFrameHostsForExtension(extension1->id()).size()); |
+ EXPECT_EQ(1u, pm->GetRenderFrameHostsForExtension(extension2->id()).size()); |
+ |
+ // Crash tab where the top-level frame is an extension frame. |
+ content::CrashTab(tab); |
+ EXPECT_EQ(0u, pm->GetAllFrames().size()); |
+ EXPECT_EQ(0u, pm->GetRenderFrameHostsForExtension(extension1->id()).size()); |
+ EXPECT_EQ(0u, pm->GetRenderFrameHostsForExtension(extension2->id()).size()); |
+ |
+ // Now load an extension page and a non-extension page... |
+ ui_test_utils::NavigateToURLWithDisposition( |
+ browser(), kExt1EmptyUrl, NEW_BACKGROUND_TAB, |
+ ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); |
+ NavigateToURL(embedded_test_server()->GetURL("/two_iframes.html")); |
+ EXPECT_EQ(1u, pm->GetAllFrames().size()); |
+ |
+ // ... load an extension frame in the non-extension process |
+ NavigateIframeToURLAndWait(tab, "frame1", kExt1EmptyUrl); |
+ EXPECT_EQ(IfExtensionsIsolated(2, 1), |
+ pm->GetRenderFrameHostsForExtension(extension1->id()).size()); |
+ |
+ // ... and take down the tab. The extension process is not part of the tab, |
+ // so it should be kept alive (minus the frames that died). |
+ content::CrashTab(tab); |
+ EXPECT_EQ(1u, pm->GetAllFrames().size()); |
+ EXPECT_EQ(1u, pm->GetRenderFrameHostsForExtension(extension1->id()).size()); |
+} |
+ |
// Verify correct keepalive count behavior on network request events. |
// Regression test for http://crbug.com/535716. |
IN_PROC_BROWSER_TEST_F(ProcessManagerBrowserTest, KeepaliveOnNetworkRequest) { |