blob: 3d51fb333d7e05aac7baff61ec99c8d587193548 [file] [log] [blame]
// 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