| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "extensions/browser/url_loader_factory_manager.h" |
| |
| #include "base/macros.h" |
| #include "base/threading/thread_restrictions.h" |
| #include "build/build_config.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/browser/web_contents_delegate.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "extensions/common/constants.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/shell/browser/shell_extension_loader.h" |
| #include "extensions/shell/test/shell_apitest.h" |
| #include "extensions/test/test_extension_dir.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| |
| namespace extensions { |
| |
| class URLLoaderFactoryManagerBrowserTest : public ShellApiTest, |
| public content::WebContentsDelegate { |
| public: |
| URLLoaderFactoryManagerBrowserTest() = default; |
| ~URLLoaderFactoryManagerBrowserTest() override = default; |
| |
| void SetUpOnMainThread() override { |
| ShellApiTest::SetUpOnMainThread(); |
| |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| } |
| |
| void TearDownOnMainThread() override { |
| tab1_.reset(); |
| tab2_.reset(); |
| ShellApiTest::TearDownOnMainThread(); |
| } |
| |
| const Extension* InstallContentScriptsExtension( |
| const std::string& content_scripts_manifest_declaration) { |
| const char kManifestTemplate[] = R"( |
| { |
| "name": "ContentScriptsTest", |
| "version": "1.0", |
| "manifest_version": 2, |
| %s |
| } )"; |
| dir_.WriteManifest(base::StringPrintf( |
| kManifestTemplate, content_scripts_manifest_declaration.c_str())); |
| dir_.WriteFile(FILE_PATH_LITERAL("content_script.css"), ""); |
| dir_.WriteFile(FILE_PATH_LITERAL("content_script.js"), ""); |
| |
| ShellExtensionLoader loader(browser_context()); |
| { |
| base::ScopedAllowBlockingForTesting allow_blocking; |
| extension_ = loader.LoadExtension(dir_.UnpackedPath()); |
| } |
| return extension_; |
| } |
| |
| // Returns whether the class-under-test (URLLoaderFactoryManager) thinks that |
| // the test extension (installed by individual test cases via |
| // InstallContentScriptsExtension) may inject content scripts into the |
| // foo_frame frame in tab1 (see SetUpFrameTree for a list of available test |
| // frames). |
| // |
| // The optional |url| argument may be used to simulate a ready-to-commit |
| // scenario where the frame's last committed URL may differ from the |url| |
| // that a pending navigation is ready to commit. |
| bool DoContentScriptsMatch_Tab1_FooFrame(GURL url = GURL("http://foo.com")) { |
| return DoContentScriptsMatchNavigatingFrame(tab1_fooFrame(), url); |
| } |
| |
| // Like DoContentScriptsMatch_Tab1_FooFrame, but for foo_about_blank_frame. |
| bool DoContentScriptsMatch_Tab1_FooBlankFrame( |
| GURL url = GURL(url::kAboutBlankURL)) { |
| return DoContentScriptsMatchNavigatingFrame(tab1_fooBlankFrame(), url); |
| } |
| |
| // Like DoContentScriptsMatch_Tab1_FooFrame, but for bar_frame. |
| bool DoContentScriptsMatch_Tab1_BarFrame(GURL url = GURL("http://bar.com")) { |
| return DoContentScriptsMatchNavigatingFrame(tab1_barFrame(), url); |
| } |
| |
| // Like DoContentScriptsMatch_Tab1_FooFrame, but for bar_about_blank_frame. |
| bool DoContentScriptsMatch_Tab1_BarBlankFrame( |
| GURL url = GURL(url::kAboutBlankURL)) { |
| return DoContentScriptsMatchNavigatingFrame(tab1_barBlankFrame(), url); |
| } |
| |
| // Like DoContentScriptsMatch_Tab1_FooFrame, but for bar_about_blank_frame1 in |
| // tab2. |
| bool DoContentScriptsMatch_Tab2_BarBlankFrame1( |
| GURL url = GURL(url::kAboutBlankURL)) { |
| return DoContentScriptsMatchNavigatingFrame(tab2_barBlankFrame1(), url); |
| } |
| |
| // Like DoContentScriptsMatch_Tab1_FooFrame, but for bar_about_blank_frame2 in |
| // tab2. |
| bool DoContentScriptsMatch_Tab2_BarBlankFrame2( |
| GURL url = GURL(url::kAboutBlankURL)) { |
| return DoContentScriptsMatchNavigatingFrame(tab2_barBlankFrame2(), url); |
| } |
| |
| // SetUpFrameTree sets up the following frame tree(s) that are used by all the |
| // URLLoaderFactoryManagerBrowserTest.ContentScriptMatching_* tests. |
| // |
| // tab1_: |
| // foo_frame |
| // +-foo_about_blank_frame |
| // +-bar_frame |
| // +-bar_about_blank_frame <---\ |
| // | |
| // tab2_: |^opener |
| // bar_about_blank_frame1--------/ |
| // +-bar_about_blank_frame2 |
| void SetUpFrameTree() { |
| GURL foo_url = embedded_test_server()->GetURL("foo.com", "/empty.html"); |
| GURL bar_url = embedded_test_server()->GetURL("bar.com", "/empty.html"); |
| GURL blank_url = GURL(url::kAboutBlankURL); |
| url::Origin foo_origin = url::Origin::Create(foo_url); |
| url::Origin bar_origin = url::Origin::Create(bar_url); |
| |
| tab1_ = content::WebContents::Create( |
| content::WebContents::CreateParams(browser_context())); |
| tab1_->SetDelegate(this); |
| ASSERT_TRUE(content::NavigateToURL(tab1_.get(), foo_url)); |
| |
| AddFrame(tab1_fooFrame(), "fooBlankFrame"); |
| |
| AddFrame(tab1_fooFrame(), "barFrame"); |
| ASSERT_TRUE(NavigateIframeToURL(tab1_.get(), "barFrame", bar_url)); |
| |
| AddFrame(tab1_barFrame(), "barBlankFrame"); |
| |
| content::WebContentsAddedObserver new_tab_observer; |
| ASSERT_TRUE(content::ExecJs(tab1_barBlankFrame(), |
| "window.open('', 'barBlankFrame1');")); |
| new_tab_observer.GetWebContents(); |
| |
| AddFrame(tab2_barBlankFrame1(), "barBlankFrame2"); |
| |
| EXPECT_EQ(foo_origin, tab1_fooFrame()->GetLastCommittedOrigin()); |
| EXPECT_EQ(foo_origin, tab1_fooBlankFrame()->GetLastCommittedOrigin()); |
| EXPECT_EQ(bar_origin, tab1_barFrame()->GetLastCommittedOrigin()); |
| EXPECT_EQ(bar_origin, tab1_barBlankFrame()->GetLastCommittedOrigin()); |
| EXPECT_EQ(bar_origin, tab2_barBlankFrame1()->GetLastCommittedOrigin()); |
| EXPECT_EQ(bar_origin, tab2_barBlankFrame2()->GetLastCommittedOrigin()); |
| |
| EXPECT_EQ(foo_url, tab1_fooFrame()->GetLastCommittedURL()); |
| EXPECT_EQ(blank_url, tab1_fooBlankFrame()->GetLastCommittedURL()); |
| EXPECT_EQ(bar_url, tab1_barFrame()->GetLastCommittedURL()); |
| EXPECT_EQ(blank_url, tab1_barBlankFrame()->GetLastCommittedURL()); |
| EXPECT_EQ(blank_url, tab2_barBlankFrame1()->GetLastCommittedURL()); |
| EXPECT_EQ(blank_url, tab2_barBlankFrame2()->GetLastCommittedURL()); |
| } |
| |
| private: |
| // WebContentsDelegate overrides: |
| void AddNewContents(content::WebContents* source, |
| std::unique_ptr<content::WebContents> new_contents, |
| WindowOpenDisposition disposition, |
| const gfx::Rect& initial_rect, |
| bool user_gesture, |
| bool* was_blocked) override { |
| DCHECK_EQ(tab1_.get(), source); |
| DCHECK(new_contents); |
| tab2_ = std::move(new_contents); |
| } |
| |
| bool DoContentScriptsMatchNavigatingFrame( |
| content::RenderFrameHost* navigating_frame, |
| const GURL& navigation_target) { |
| return URLLoaderFactoryManager::DoContentScriptsMatchNavigatingFrame( |
| *extension_, navigating_frame, navigation_target); |
| } |
| |
| void AddFrame(content::RenderFrameHost* parent, |
| const std::string& subframe_id) { |
| const char* kScriptTemplate = R"( |
| var bar_frame = document.createElement('iframe'); |
| bar_frame.id = $1; |
| document.body.appendChild(bar_frame); |
| )"; |
| ASSERT_TRUE(content::ExecJs( |
| parent, content::JsReplace(kScriptTemplate, subframe_id))); |
| } |
| |
| content::RenderFrameHost* tab1_fooFrame() { |
| DCHECK(tab1_); |
| return tab1_->GetMainFrame(); |
| } |
| |
| content::RenderFrameHost* tab1_fooBlankFrame() { |
| DCHECK(tab1_); |
| DCHECK_LT(1u, tab1_->GetAllFrames().size()); |
| return tab1_->GetAllFrames()[1]; |
| } |
| |
| content::RenderFrameHost* tab1_barFrame() { |
| DCHECK(tab1_); |
| DCHECK_LT(2u, tab1_->GetAllFrames().size()); |
| return tab1_->GetAllFrames()[2]; |
| } |
| |
| content::RenderFrameHost* tab1_barBlankFrame() { |
| DCHECK(tab1_); |
| DCHECK_LT(3u, tab1_->GetAllFrames().size()); |
| return tab1_->GetAllFrames()[3]; |
| } |
| |
| content::RenderFrameHost* tab2_barBlankFrame1() { |
| DCHECK(tab2_); |
| return tab2_->GetMainFrame(); |
| } |
| |
| content::RenderFrameHost* tab2_barBlankFrame2() { |
| DCHECK(tab2_); |
| DCHECK_LT(1u, tab1_->GetAllFrames().size()); |
| return tab2_->GetAllFrames()[1]; |
| } |
| |
| // Populated by SetUpFrameTree (during test setup / in SetUpOnMainThread). |
| std::unique_ptr<content::WebContents> tab1_; |
| std::unique_ptr<content::WebContents> tab2_; |
| |
| // Populated by InstallContentScriptsExtension (called by individual tests). |
| TestExtensionDir dir_; |
| const Extension* extension_ = nullptr; |
| |
| DISALLOW_COPY_AND_ASSIGN(URLLoaderFactoryManagerBrowserTest); |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(URLLoaderFactoryManagerBrowserTest, |
| ContentScriptMatching_ChainTraversalForBar) { |
| SetUpFrameTree(); |
| ASSERT_FALSE(::testing::Test::HasFailure()); |
| |
| const Extension* extension = InstallContentScriptsExtension(R"( |
| "content_scripts": [{ |
| "all_frames": true, |
| "match_about_blank": true, |
| "matches": ["http://bar.com/*"], |
| "js": ["content_script.js"] |
| }] )"); |
| ASSERT_TRUE(extension); |
| |
| // Matching should consider parent/opener chain. |
| EXPECT_FALSE(DoContentScriptsMatch_Tab1_FooFrame()); |
| EXPECT_FALSE(DoContentScriptsMatch_Tab1_FooBlankFrame()); |
| EXPECT_TRUE(DoContentScriptsMatch_Tab1_BarFrame()); |
| EXPECT_TRUE(DoContentScriptsMatch_Tab1_BarBlankFrame()); |
| EXPECT_TRUE(DoContentScriptsMatch_Tab2_BarBlankFrame1()); |
| EXPECT_TRUE(DoContentScriptsMatch_Tab2_BarBlankFrame2()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(URLLoaderFactoryManagerBrowserTest, |
| ContentScriptMatching_ChainTraversalForFoo) { |
| SetUpFrameTree(); |
| ASSERT_FALSE(::testing::Test::HasFailure()); |
| |
| const Extension* extension = InstallContentScriptsExtension(R"( |
| "content_scripts": [{ |
| "all_frames": true, |
| "match_about_blank": true, |
| "matches": ["http://foo.com/*"], |
| "js": ["content_script.js"] |
| }] )"); |
| ASSERT_TRUE(extension); |
| |
| // Matching should consider parent/opener chain. |
| EXPECT_TRUE(DoContentScriptsMatch_Tab1_FooFrame()); |
| EXPECT_TRUE(DoContentScriptsMatch_Tab1_FooBlankFrame()); |
| EXPECT_FALSE(DoContentScriptsMatch_Tab1_BarFrame()); |
| EXPECT_FALSE(DoContentScriptsMatch_Tab1_BarBlankFrame()); |
| EXPECT_FALSE(DoContentScriptsMatch_Tab2_BarBlankFrame1()); |
| EXPECT_FALSE(DoContentScriptsMatch_Tab2_BarBlankFrame2()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(URLLoaderFactoryManagerBrowserTest, |
| ContentScriptMatching_NoMatchingOfAboutBlank) { |
| SetUpFrameTree(); |
| ASSERT_FALSE(::testing::Test::HasFailure()); |
| |
| const Extension* extension = InstallContentScriptsExtension(R"( |
| "content_scripts": [{ |
| "all_frames": true, |
| "match_about_blank": false, |
| "matches": ["http://bar.com/*"], |
| "js": ["content_script.js"] |
| }] )"); |
| ASSERT_TRUE(extension); |
| |
| // In absence of "match_about_blank", parent/opener chain should not be |
| // considered (and matching against about:blank should fail). |
| EXPECT_FALSE(DoContentScriptsMatch_Tab1_FooFrame()); |
| EXPECT_FALSE(DoContentScriptsMatch_Tab1_FooBlankFrame()); |
| EXPECT_TRUE(DoContentScriptsMatch_Tab1_BarFrame()); |
| EXPECT_FALSE(DoContentScriptsMatch_Tab1_BarBlankFrame()); |
| EXPECT_FALSE(DoContentScriptsMatch_Tab2_BarBlankFrame1()); |
| EXPECT_FALSE(DoContentScriptsMatch_Tab2_BarBlankFrame2()); |
| } |
| |
| // Flaky on MacOS since r622662. See https://crbug.com/921883 |
| #if defined(OS_MACOSX) |
| #define MAYBE_ContentScriptMatching_NotAllFrames \ |
| DISABLED_ContentScriptMatching_NotAllFrames |
| #else |
| #define MAYBE_ContentScriptMatching_NotAllFrames \ |
| ContentScriptMatching_NotAllFrames |
| #endif |
| IN_PROC_BROWSER_TEST_F(URLLoaderFactoryManagerBrowserTest, |
| MAYBE_ContentScriptMatching_NotAllFrames) { |
| SetUpFrameTree(); |
| ASSERT_FALSE(::testing::Test::HasFailure()); |
| |
| const Extension* extension = InstallContentScriptsExtension(R"( |
| "content_scripts": [{ |
| "all_frames": false, |
| "match_about_blank": true, |
| "matches": ["http://foo.com/*", "http://bar.com/*"], |
| "js": ["content_script.js"] |
| }] )"); |
| ASSERT_TRUE(extension); |
| |
| // Main frame should be matched. |
| EXPECT_TRUE(DoContentScriptsMatch_Tab1_FooFrame()); |
| |
| // Subframe should not be matched (even though the patterns in the manifest do |
| // match bar.com). |
| EXPECT_FALSE(DoContentScriptsMatch_Tab1_BarFrame()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(URLLoaderFactoryManagerBrowserTest, |
| ContentScriptMatching_NotYetCommittedURL) { |
| SetUpFrameTree(); |
| ASSERT_FALSE(::testing::Test::HasFailure()); |
| |
| const Extension* extension = InstallContentScriptsExtension(R"( |
| "content_scripts": [{ |
| "all_frames": true, |
| "match_about_blank": true, |
| "matches": ["http://matching.com/*"], |
| "js": ["content_script.js"] |
| }] )"); |
| ASSERT_TRUE(extension); |
| |
| // Content scripts should match, even though baz.com has not yet committed in |
| // the frames (i.e. GetLastCommittedOrigin() in the frames is different - |
| // either foo.com or bar.com). |
| GURL matching_url("http://matching.com"); |
| EXPECT_TRUE(DoContentScriptsMatch_Tab1_FooFrame(matching_url)); |
| EXPECT_TRUE(DoContentScriptsMatch_Tab1_BarFrame(matching_url)); |
| EXPECT_TRUE(DoContentScriptsMatch_Tab1_FooBlankFrame(matching_url)); |
| |
| // Content scripts should not match, since other.com is not covered by the |
| // extension manifest. |
| GURL other_url("http://other.com"); |
| EXPECT_FALSE(DoContentScriptsMatch_Tab1_FooFrame(other_url)); |
| EXPECT_FALSE(DoContentScriptsMatch_Tab1_BarFrame(other_url)); |
| EXPECT_FALSE(DoContentScriptsMatch_Tab1_FooBlankFrame(other_url)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(URLLoaderFactoryManagerBrowserTest, |
| ContentScriptMatching_CssIsIgnored) { |
| SetUpFrameTree(); |
| ASSERT_FALSE(::testing::Test::HasFailure()); |
| |
| const Extension* extension = InstallContentScriptsExtension(R"( |
| "content_scripts": [{ |
| "all_frames": true, |
| "match_about_blank": false, |
| "matches": ["http://bar.com/*"], |
| "css": ["content_script.css"] |
| }] )"); |
| ASSERT_TRUE(extension); |
| |
| // Only Javascript should result in a match. |
| EXPECT_FALSE(DoContentScriptsMatch_Tab1_FooFrame()); |
| EXPECT_FALSE(DoContentScriptsMatch_Tab1_BarFrame()); |
| } |
| |
| } // namespace extensions |