summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/app/generated_resources.grd6
-rw-r--r--chrome/browser/extensions/active_script_controller.cc41
-rw-r--r--chrome/browser/extensions/active_script_controller.h7
-rw-r--r--chrome/browser/extensions/active_script_controller_unittest.cc100
-rw-r--r--chrome/browser/extensions/active_tab_permission_granter.cc8
-rw-r--r--chrome/browser/extensions/extension_context_menu_model.cc28
-rw-r--r--chrome/browser/extensions/extension_context_menu_model.h10
-rw-r--r--chrome/browser/extensions/permissions_updater.cc46
-rw-r--r--extensions/common/url_pattern.cc6
-rw-r--r--extensions/common/url_pattern.h4
-rw-r--r--extensions/common/url_pattern_set.cc12
-rw-r--r--extensions/common/url_pattern_set.h3
-rw-r--r--extensions/common/url_pattern_set_unittest.cc18
-rw-r--r--extensions/common/url_pattern_unittest.cc31
14 files changed, 289 insertions, 31 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 2a4b8ee..8368303 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -4844,6 +4844,9 @@ Make sure you do not expose any sensitive information.
Show button
</message>
<if expr="not use_titlecase">
+ <message name="IDS_EXTENSIONS_ALWAYS_RUN" desc="The text for the 'always run' item in context menus (sentence case).">
+ Always run
+ </message>
<message name="IDS_EXTENSIONS_OPTIONS_MENU_ITEM" desc="The text for the options menu item in context menus (sentence case).">
Options
</message>
@@ -4861,6 +4864,9 @@ Make sure you do not expose any sensitive information.
</message>
</if>
<if expr="use_titlecase">
+ <message name="IDS_EXTENSIONS_ALWAYS_RUN" desc="The text for the 'always run' item in context menus (title case).">
+ Always Run
+ </message>
<message name="IDS_EXTENSIONS_OPTIONS_MENU_ITEM" desc="The text for the options menu item in context menus (title case).">
Options
</message>
diff --git a/chrome/browser/extensions/active_script_controller.cc b/chrome/browser/extensions/active_script_controller.cc
index 60dce03..64de769 100644
--- a/chrome/browser/extensions/active_script_controller.cc
+++ b/chrome/browser/extensions/active_script_controller.cc
@@ -13,6 +13,8 @@
#include "chrome/browser/extensions/extension_action.h"
#include "chrome/browser/extensions/extension_action_manager.h"
#include "chrome/browser/extensions/extension_util.h"
+#include "chrome/browser/extensions/location_bar_controller.h"
+#include "chrome/browser/extensions/permissions_updater.h"
#include "chrome/browser/extensions/tab_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sessions/session_id.h"
@@ -27,6 +29,7 @@
#include "extensions/common/extension_set.h"
#include "extensions/common/feature_switch.h"
#include "extensions/common/manifest.h"
+#include "extensions/common/permissions/permission_set.h"
#include "extensions/common/permissions/permissions_data.h"
#include "ipc/ipc_message_macros.h"
@@ -101,6 +104,44 @@ void ActiveScriptController::OnAdInjectionDetected(
ad_injectors.size() - num_preventable_ad_injectors);
}
+void ActiveScriptController::AlwaysRunOnVisibleOrigin(
+ const Extension* extension) {
+ const GURL& url = web_contents()->GetVisibleURL();
+ URLPatternSet new_explicit_hosts;
+ URLPatternSet new_scriptable_hosts;
+
+ scoped_refptr<const PermissionSet> withheld_permissions =
+ extension->permissions_data()->withheld_permissions();
+ if (withheld_permissions->explicit_hosts().MatchesURL(url)) {
+ new_explicit_hosts.AddOrigin(UserScript::ValidUserScriptSchemes(),
+ url.GetOrigin());
+ }
+ if (withheld_permissions->scriptable_hosts().MatchesURL(url)) {
+ new_scriptable_hosts.AddOrigin(UserScript::ValidUserScriptSchemes(),
+ url.GetOrigin());
+ }
+
+ scoped_refptr<PermissionSet> new_permissions =
+ new PermissionSet(APIPermissionSet(),
+ ManifestPermissionSet(),
+ new_explicit_hosts,
+ new_scriptable_hosts);
+
+ // Update permissions for the session. This adds |new_permissions| to active
+ // permissions and granted permissions.
+ // TODO(devlin): Make sure that the permission is removed from
+ // withheld_permissions if appropriate.
+ PermissionsUpdater(web_contents()->GetBrowserContext())
+ .AddPermissions(extension, new_permissions.get());
+
+ // Allow current tab to run injection.
+ OnClicked(extension);
+}
+
+bool ActiveScriptController::HasActiveScriptAction(const Extension* extension) {
+ return enabled_ && active_script_actions_.count(extension->id()) > 0;
+}
+
ExtensionAction* ActiveScriptController::GetActionForExtension(
const Extension* extension) {
if (!enabled_ || pending_requests_.count(extension->id()) == 0)
diff --git a/chrome/browser/extensions/active_script_controller.h b/chrome/browser/extensions/active_script_controller.h
index d55ec4a..6e4a9ed 100644
--- a/chrome/browser/extensions/active_script_controller.h
+++ b/chrome/browser/extensions/active_script_controller.h
@@ -54,6 +54,13 @@ class ActiveScriptController : public LocationBarController::ActionProvider,
// Notifies the ActiveScriptController of detected ad injection.
void OnAdInjectionDetected(const std::set<std::string>& ad_injectors);
+ // Adds the visible origin to |extension|'s active permissions, granting
+ // |extension| permission to always run script injections on the origin.
+ void AlwaysRunOnVisibleOrigin(const Extension* extension);
+
+ // Returns true if there is an active script injection action for |extension|.
+ bool HasActiveScriptAction(const Extension* extension);
+
// LocationBarControllerProvider implementation.
virtual ExtensionAction* GetActionForExtension(
const Extension* extension) OVERRIDE;
diff --git a/chrome/browser/extensions/active_script_controller_unittest.cc b/chrome/browser/extensions/active_script_controller_unittest.cc
index 01fe26b..f40286b 100644
--- a/chrome/browser/extensions/active_script_controller_unittest.cc
+++ b/chrome/browser/extensions/active_script_controller_unittest.cc
@@ -45,6 +45,9 @@ class ActiveScriptControllerUnitTest : public ChromeRenderViewHostTestHarness {
// Creates an extension with all hosts permission and adds it to the registry.
const Extension* AddExtension();
+ // Reloads |extension_| by removing it from the registry and recreating it.
+ const Extension* ReloadExtension();
+
// Returns true if the |extension| requires user consent before injecting
// a script.
bool RequiresUserConsent(const Extension* extension) const;
@@ -78,6 +81,8 @@ class ActiveScriptControllerUnitTest : public ChromeRenderViewHostTestHarness {
// The map of observed executions, keyed by extension id.
std::map<std::string, int> extension_executions_;
+
+ scoped_refptr<const Extension> extension_;
};
ActiveScriptControllerUnitTest::ActiveScriptControllerUnitTest()
@@ -91,23 +96,27 @@ ActiveScriptControllerUnitTest::~ActiveScriptControllerUnitTest() {
const Extension* ActiveScriptControllerUnitTest::AddExtension() {
const std::string kId = id_util::GenerateId("all_hosts_extension");
- scoped_refptr<const Extension> extension =
- ExtensionBuilder()
- .SetManifest(
- DictionaryBuilder()
- .Set("name", "all_hosts_extension")
- .Set("description", "an extension")
- .Set("manifest_version", 2)
- .Set("version", "1.0.0")
- .Set("permissions",
- ListBuilder().Append(kAllHostsPermission)))
- .SetLocation(Manifest::INTERNAL)
- .SetID(kId)
- .Build();
-
- ExtensionRegistry::Get(profile())->AddEnabled(extension);
- PermissionsUpdater(profile()).InitializePermissions(extension);
- return extension;
+ extension_ = ExtensionBuilder()
+ .SetManifest(
+ DictionaryBuilder()
+ .Set("name", "all_hosts_extension")
+ .Set("description", "an extension")
+ .Set("manifest_version", 2)
+ .Set("version", "1.0.0")
+ .Set("permissions",
+ ListBuilder().Append(kAllHostsPermission)))
+ .SetLocation(Manifest::INTERNAL)
+ .SetID(kId)
+ .Build();
+
+ ExtensionRegistry::Get(profile())->AddEnabled(extension_);
+ PermissionsUpdater(profile()).InitializePermissions(extension_);
+ return extension_;
+}
+
+const Extension* ActiveScriptControllerUnitTest::ReloadExtension() {
+ ExtensionRegistry::Get(profile())->RemoveEnabled(extension_->id());
+ return AddExtension();
}
bool ActiveScriptControllerUnitTest::RequiresUserConsent(
@@ -322,4 +331,61 @@ TEST_F(ActiveScriptControllerUnitTest, ActiveScriptsCanHaveAllUrlsPref) {
EXPECT_TRUE(RequiresUserConsent(extension));
}
+TEST_F(ActiveScriptControllerUnitTest, TestAlwaysRun) {
+ const Extension* extension = AddExtension();
+ ASSERT_TRUE(extension);
+
+ NavigateAndCommit(GURL("https://www.google.com/?gws_rd=ssl"));
+
+ // Ensure that there aren't any executions pending.
+ ASSERT_EQ(0u, GetExecutionCountForExtension(extension->id()));
+ ASSERT_FALSE(controller()->GetActionForExtension(extension));
+
+ // Since the extension requests all_hosts, we should require user consent.
+ EXPECT_TRUE(RequiresUserConsent(extension));
+
+ // Request an injection. There should be an action visible, but no executions.
+ RequestInjection(extension);
+ EXPECT_TRUE(controller()->GetActionForExtension(extension));
+ EXPECT_EQ(0u, GetExecutionCountForExtension(extension->id()));
+
+ // Allow the extension to always run on this origin.
+ controller()->AlwaysRunOnVisibleOrigin(extension);
+
+ // The extension should execute, and the action should go away.
+ EXPECT_EQ(1u, GetExecutionCountForExtension(extension->id()));
+ EXPECT_FALSE(controller()->GetActionForExtension(extension));
+
+ // Since we already executed on the given page, we shouldn't need permission
+ // for a second time.
+ EXPECT_FALSE(RequiresUserConsent(extension));
+
+ // Navigating to another site that hasn't been granted a persisted permission
+ // should necessitate user consent.
+ NavigateAndCommit(GURL("https://www.foo.com/bar"));
+ EXPECT_TRUE(RequiresUserConsent(extension));
+
+ // We shouldn't need user permission upon returning to the original origin.
+ NavigateAndCommit(GURL("https://www.google.com/foo/bar"));
+ EXPECT_FALSE(RequiresUserConsent(extension));
+
+ // Reloading the extension should not clear any granted host permissions.
+ extension = ReloadExtension();
+ Reload();
+ EXPECT_FALSE(RequiresUserConsent(extension));
+
+ // Different host...
+ NavigateAndCommit(GURL("https://www.foo.com/bar"));
+ EXPECT_TRUE(RequiresUserConsent(extension));
+ // Different scheme...
+ NavigateAndCommit(GURL("http://www.google.com/foo/bar"));
+ EXPECT_TRUE(RequiresUserConsent(extension));
+ // Different subdomain...
+ NavigateAndCommit(GURL("https://en.google.com/foo/bar"));
+ EXPECT_TRUE(RequiresUserConsent(extension));
+ // Only the "always run" origin should be allowed to run without user consent.
+ NavigateAndCommit(GURL("https://www.google.com/foo/bar"));
+ EXPECT_FALSE(RequiresUserConsent(extension));
+}
+
} // namespace extensions
diff --git a/chrome/browser/extensions/active_tab_permission_granter.cc b/chrome/browser/extensions/active_tab_permission_granter.cc
index 608eaa1..2f80ad7 100644
--- a/chrome/browser/extensions/active_tab_permission_granter.cc
+++ b/chrome/browser/extensions/active_tab_permission_granter.cc
@@ -47,12 +47,8 @@ void ActiveTabPermissionGranter::GrantIfRequested(const Extension* extension) {
// permission in the manifest.
if (permissions_data->HasAPIPermission(APIPermission::kActiveTab) ||
permissions_data->HasWithheldImpliedAllHosts()) {
- URLPattern pattern(UserScript::ValidUserScriptSchemes());
- // Pattern parsing could fail if this is an unsupported URL e.g. chrome://.
- if (pattern.Parse(web_contents()->GetURL().spec()) ==
- URLPattern::PARSE_SUCCESS) {
- new_hosts.AddPattern(pattern);
- }
+ new_hosts.AddOrigin(UserScript::ValidUserScriptSchemes(),
+ web_contents()->GetVisibleURL().GetOrigin());
new_apis.insert(APIPermission::kTab);
}
diff --git a/chrome/browser/extensions/extension_context_menu_model.cc b/chrome/browser/extensions/extension_context_menu_model.cc
index 2edc1ac..9c1e125 100644
--- a/chrome/browser/extensions/extension_context_menu_model.cc
+++ b/chrome/browser/extensions/extension_context_menu_model.cc
@@ -7,6 +7,7 @@
#include "base/prefs/pref_service.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/app/chrome_command_ids.h"
+#include "chrome/browser/extensions/active_script_controller.h"
#include "chrome/browser/extensions/api/extension_action/extension_action_api.h"
#include "chrome/browser/extensions/context_menu_matcher.h"
#include "chrome/browser/extensions/extension_action.h"
@@ -120,8 +121,7 @@ bool ExtensionContextMenuModel::IsCommandIdEnabled(int command_id) const {
// homepage, we just disable this menu item.
return extensions::ManifestURL::GetHomepageURL(extension).is_valid();
} else if (command_id == INSPECT_POPUP) {
- WebContents* web_contents =
- browser_->tab_strip_model()->GetActiveWebContents();
+ WebContents* web_contents = GetActiveWebContents();
if (!web_contents)
return false;
@@ -164,6 +164,14 @@ void ExtensionContextMenuModel::ExecuteCommand(int command_id,
browser_->OpenURL(params);
break;
}
+ case ALWAYS_RUN: {
+ WebContents* web_contents = GetActiveWebContents();
+ if (web_contents) {
+ extensions::ActiveScriptController::GetForWebContents(web_contents)
+ ->AlwaysRunOnVisibleOrigin(extension);
+ }
+ break;
+ }
case CONFIGURE:
DCHECK(!extensions::ManifestURL::GetOptionsPage(extension).is_empty());
extensions::ExtensionTabUtil::OpenOptionsPage(extension, browser_);
@@ -237,6 +245,18 @@ void ExtensionContextMenuModel::InitMenu(const Extension* extension) {
AddItem(NAME, base::UTF8ToUTF16(extension_name));
AppendExtensionItems();
AddSeparator(ui::NORMAL_SEPARATOR);
+
+ // Add the "Always Allow" item for adding persisted permissions for script
+ // injections if there is an active action for this extension. Note that this
+ // will add it to *all* extension action context menus, not just the one
+ // attached to the script injection request icon, but that's okay.
+ WebContents* web_contents = GetActiveWebContents();
+ if (web_contents &&
+ extensions::ActiveScriptController::GetForWebContents(web_contents)
+ ->HasActiveScriptAction(extension)) {
+ AddItemWithStringId(ALWAYS_RUN, IDS_EXTENSIONS_ALWAYS_RUN);
+ }
+
AddItemWithStringId(CONFIGURE, IDS_EXTENSIONS_OPTIONS_MENU_ITEM);
AddItem(UNINSTALL, l10n_util::GetStringUTF16(IDS_EXTENSIONS_UNINSTALL));
if (extension_action_manager->GetBrowserAction(*extension))
@@ -267,3 +287,7 @@ void ExtensionContextMenuModel::AppendExtensionItems() {
&extension_items_count_,
true); // is_action_menu
}
+
+content::WebContents* ExtensionContextMenuModel::GetActiveWebContents() const {
+ return browser_->tab_strip_model()->GetActiveWebContents();
+}
diff --git a/chrome/browser/extensions/extension_context_menu_model.h b/chrome/browser/extensions/extension_context_menu_model.h
index 0ec50f7..50e9837 100644
--- a/chrome/browser/extensions/extension_context_menu_model.h
+++ b/chrome/browser/extensions/extension_context_menu_model.h
@@ -15,6 +15,10 @@ class Browser;
class ExtensionAction;
class Profile;
+namespace content {
+class WebContents;
+}
+
namespace extensions {
class Extension;
class ContextMenuMatcher;
@@ -34,7 +38,8 @@ class ExtensionContextMenuModel
HIDE,
UNINSTALL,
MANAGE,
- INSPECT_POPUP
+ INSPECT_POPUP,
+ ALWAYS_RUN
};
// Type of action the extension icon represents.
@@ -89,6 +94,9 @@ class ExtensionContextMenuModel
// extension has been uninstalled and no longer exists.
const extensions::Extension* GetExtension() const;
+ // Returns the active web contents.
+ content::WebContents* GetActiveWebContents() const;
+
// Appends the extension's context menu items.
void AppendExtensionItems();
diff --git a/chrome/browser/extensions/permissions_updater.cc b/chrome/browser/extensions/permissions_updater.cc
index 5f2f629..798717c 100644
--- a/chrome/browser/extensions/permissions_updater.cc
+++ b/chrome/browser/extensions/permissions_updater.cc
@@ -36,15 +36,35 @@ namespace permissions = api::permissions;
namespace {
+// Returns a set of single origin permissions from |permissions| that match
+// |bounds|. This is necessary for two reasons:
+// a) single origin active permissions can get filtered out in
+// GetBoundedActivePermissions because they are not recognized as a subset
+// of all-host permissions
+// b) active permissions that do not match any manifest permissions can
+// exist if a manifest permission is dropped
+URLPatternSet FilterSingleOriginPermissions(const URLPatternSet& permissions,
+ const URLPatternSet& bounds) {
+ URLPatternSet single_origin_permissions;
+ for (URLPatternSet::const_iterator iter = permissions.begin();
+ iter != permissions.end();
+ ++iter) {
+ if (iter->MatchesSingleOrigin() &&
+ bounds.MatchesURL(GURL(iter->GetAsString()))) {
+ single_origin_permissions.AddPattern(*iter);
+ }
+ }
+ return single_origin_permissions;
+}
+
// Returns a PermissionSet that has the active permissions of the extension,
// bounded to its current manifest.
scoped_refptr<const PermissionSet> GetBoundedActivePermissions(
- const Extension* extension, ExtensionPrefs* extension_prefs) {
+ const Extension* extension,
+ const scoped_refptr<const PermissionSet>& active_permissions) {
// If the extension has used the optional permissions API, it will have a
// custom set of active permissions defined in the extension prefs. Here,
// we update the extension's active permissions based on the prefs.
- scoped_refptr<const PermissionSet> active_permissions =
- extension_prefs->GetActivePermissions(extension->id());
if (!active_permissions)
return extension->permissions_data()->active_permissions();
@@ -144,9 +164,11 @@ void PermissionsUpdater::GrantActivePermissions(const Extension* extension) {
}
void PermissionsUpdater::InitializePermissions(const Extension* extension) {
+ scoped_refptr<const PermissionSet> active_permissions =
+ ExtensionPrefs::Get(browser_context_)
+ ->GetActivePermissions(extension->id());
scoped_refptr<const PermissionSet> bounded_active =
- GetBoundedActivePermissions(extension,
- ExtensionPrefs::Get(browser_context_));
+ GetBoundedActivePermissions(extension, active_permissions);
// We withhold permissions iff the switch to do so is enabled, the extension
// shows up in chrome:extensions (so the user can grant withheld permissions),
@@ -175,6 +197,20 @@ void PermissionsUpdater::InitializePermissions(const Extension* extension) {
&granted_scriptable_hosts,
&withheld_scriptable_hosts);
+ // After withholding permissions, add back any origins to the active set that
+ // may have been lost during the set operations that would have dropped them.
+ // For example, the union of <all_urls> and "example.com" is <all_urls>, so
+ // we may lose "example.com". However, "example.com" is important once
+ // <all_urls> is stripped during withholding.
+ if (active_permissions) {
+ granted_explicit_hosts.AddPatterns(
+ FilterSingleOriginPermissions(active_permissions->explicit_hosts(),
+ bounded_active->explicit_hosts()));
+ granted_scriptable_hosts.AddPatterns(
+ FilterSingleOriginPermissions(active_permissions->scriptable_hosts(),
+ bounded_active->scriptable_hosts()));
+ }
+
bounded_active = new PermissionSet(bounded_active->apis(),
bounded_active->manifest_permissions(),
granted_explicit_hosts,
diff --git a/extensions/common/url_pattern.cc b/extensions/common/url_pattern.cc
index 735e71e..71b522e 100644
--- a/extensions/common/url_pattern.cc
+++ b/extensions/common/url_pattern.cc
@@ -466,6 +466,12 @@ bool URLPattern::ImpliesAllHosts() const {
return registry_length > 0;
}
+bool URLPattern::MatchesSingleOrigin() const {
+ // Strictly speaking, the port is part of the origin, but in URLPattern it
+ // defaults to *. It's not very interesting anyway, so leave it out.
+ return !ImpliesAllHosts() && scheme_ != "*" && !match_subdomains_;
+}
+
bool URLPattern::MatchesPath(const std::string& test) const {
// Make the behaviour of OverlapsWith consistent with MatchesURL, which is
// need to match hosted apps on e.g. 'google.com' also run on 'google.com/'.
diff --git a/extensions/common/url_pattern.h b/extensions/common/url_pattern.h
index 5a0e8c6..c58c3ef 100644
--- a/extensions/common/url_pattern.h
+++ b/extensions/common/url_pattern.h
@@ -162,6 +162,10 @@ class URLPattern {
// cached.
bool ImpliesAllHosts() const;
+ // Returns true if the pattern only matches a single origin. The pattern may
+ // include a path.
+ bool MatchesSingleOrigin() const;
+
// Sets the port. Returns false if the port is invalid.
bool SetPort(const std::string& port);
const std::string& port() const { return port_; }
diff --git a/extensions/common/url_pattern_set.cc b/extensions/common/url_pattern_set.cc
index ee5ea93..bc1d0d5 100644
--- a/extensions/common/url_pattern_set.cc
+++ b/extensions/common/url_pattern_set.cc
@@ -142,6 +142,18 @@ void URLPatternSet::ClearPatterns() {
patterns_.clear();
}
+bool URLPatternSet::AddOrigin(int valid_schemes, const GURL& origin) {
+ DCHECK_EQ(origin.GetOrigin(), origin);
+ URLPattern origin_pattern(valid_schemes);
+ // Origin adding could fail if |origin| does not match |valid_schemes|.
+ if (origin_pattern.Parse(origin.GetOrigin().spec()) !=
+ URLPattern::PARSE_SUCCESS) {
+ return false;
+ }
+ origin_pattern.SetPath("/*");
+ return AddPattern(origin_pattern);
+}
+
bool URLPatternSet::Contains(const URLPatternSet& other) const {
for (URLPatternSet::const_iterator it = other.begin();
it != other.end(); ++it) {
diff --git a/extensions/common/url_pattern_set.h b/extensions/common/url_pattern_set.h
index c17f613..30db558 100644
--- a/extensions/common/url_pattern_set.h
+++ b/extensions/common/url_pattern_set.h
@@ -69,6 +69,9 @@ class URLPatternSet {
void ClearPatterns();
+ // Adds a pattern based on |origin| to the set.
+ bool AddOrigin(int valid_schemes, const GURL& origin);
+
// Returns true if every URL that matches |set| is matched by this. In other
// words, if every pattern in |set| is encompassed by a pattern in this.
bool Contains(const URLPatternSet& set) const;
diff --git a/extensions/common/url_pattern_set_unittest.cc b/extensions/common/url_pattern_set_unittest.cc
index 1ad223e..ce2c94e 100644
--- a/extensions/common/url_pattern_set_unittest.cc
+++ b/extensions/common/url_pattern_set_unittest.cc
@@ -421,4 +421,22 @@ TEST(URLPatternSetTest, NwayUnion) {
}
}
+TEST(URLPatternSetTest, AddOrigin) {
+ URLPatternSet set;
+ EXPECT_TRUE(set.AddOrigin(
+ URLPattern::SCHEME_ALL, GURL("https://www.google.com/")));
+ EXPECT_TRUE(set.MatchesURL(GURL("https://www.google.com/foo/bar")));
+ EXPECT_FALSE(set.MatchesURL(GURL("http://www.google.com/foo/bar")));
+ EXPECT_FALSE(set.MatchesURL(GURL("https://en.google.com/foo/bar")));
+ set.ClearPatterns();
+
+ EXPECT_TRUE(set.AddOrigin(
+ URLPattern::SCHEME_ALL, GURL("https://google.com/")));
+ EXPECT_FALSE(set.MatchesURL(GURL("https://www.google.com/foo/bar")));
+ EXPECT_TRUE(set.MatchesURL(GURL("https://google.com/foo/bar")));
+
+ EXPECT_FALSE(set.AddOrigin(
+ URLPattern::SCHEME_HTTP, GURL("https://google.com/")));
+}
+
} // namespace extensions
diff --git a/extensions/common/url_pattern_unittest.cc b/extensions/common/url_pattern_unittest.cc
index 9618aeb..d4398a7 100644
--- a/extensions/common/url_pattern_unittest.cc
+++ b/extensions/common/url_pattern_unittest.cc
@@ -810,4 +810,35 @@ TEST(ExtensionURLPatternTest, Subset) {
EXPECT_TRUE(StrictlyContains(pattern12, pattern13));
}
+TEST(ExtensionURLPatternTest, MatchesSingleOrigin) {
+ EXPECT_FALSE(
+ URLPattern(URLPattern::SCHEME_ALL, "http://*/").MatchesSingleOrigin());
+ EXPECT_FALSE(URLPattern(URLPattern::SCHEME_ALL, "https://*.google.com/*")
+ .MatchesSingleOrigin());
+ EXPECT_TRUE(URLPattern(URLPattern::SCHEME_ALL, "http://google.com/")
+ .MatchesSingleOrigin());
+ EXPECT_TRUE(URLPattern(URLPattern::SCHEME_ALL, "http://google.com/*")
+ .MatchesSingleOrigin());
+ EXPECT_TRUE(URLPattern(URLPattern::SCHEME_ALL, "http://www.google.com/")
+ .MatchesSingleOrigin());
+ EXPECT_FALSE(URLPattern(URLPattern::SCHEME_ALL, "*://www.google.com/")
+ .MatchesSingleOrigin());
+ EXPECT_FALSE(URLPattern(URLPattern::SCHEME_ALL, "http://*.com/")
+ .MatchesSingleOrigin());
+ EXPECT_FALSE(URLPattern(URLPattern::SCHEME_ALL, "http://*.google.com/foo/bar")
+ .MatchesSingleOrigin());
+ EXPECT_TRUE(
+ URLPattern(URLPattern::SCHEME_ALL, "http://www.google.com/foo/bar")
+ .MatchesSingleOrigin());
+ EXPECT_FALSE(URLPattern(URLPattern::SCHEME_HTTPS, "*://*.google.com/foo/bar")
+ .MatchesSingleOrigin());
+ EXPECT_TRUE(URLPattern(URLPattern::SCHEME_HTTPS, "https://www.google.com/")
+ .MatchesSingleOrigin());
+ EXPECT_FALSE(URLPattern(URLPattern::SCHEME_HTTP,
+ "http://*.google.com/foo/bar").MatchesSingleOrigin());
+ EXPECT_TRUE(
+ URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com/foo/bar")
+ .MatchesSingleOrigin());
+}
+
} // namespace