Reland: Communicate ExtensionSettings policy to renderers

Origional CL (2499493004) was rolled back by sheriff due to failing MSAN tests.
This initial patchset is a direct patch of 2499493004. Second patchset contains the fix.

-Communicate which hosts are runtime blocked to all renderers
-Blocks host permissions for specific hosts by specific extensions
-Tests via blocking content script injection
-Introduces new test class for use with ExtensionSettings policy

BUG=624649

Review-Url: https://codereview.chromium.org/2833843004
Cr-Commit-Position: refs/heads/master@{#466832}
diff --git a/chrome/browser/extensions/api/permissions/permissions_apitest.cc b/chrome/browser/extensions/api/permissions/permissions_apitest.cc
index 3de54c7..910bf62 100644
--- a/chrome/browser/extensions/api/permissions/permissions_apitest.cc
+++ b/chrome/browser/extensions/api/permissions/permissions_apitest.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/extensions/api/permissions/permissions_api.h"
 #include "chrome/browser/extensions/extension_apitest.h"
 #include "chrome/browser/extensions/extension_management_test_util.h"
+#include "chrome/browser/extensions/extension_with_management_policy_apitest.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
 #include "components/policy/core/browser/browser_policy_connector.h"
@@ -33,21 +34,6 @@
   }
 };
 
-class ExtensionApiTestWithManagementPolicy : public ExtensionApiTest {
- public:
-  void SetUpInProcessBrowserTestFixture() override {
-    ExtensionApiTest::SetUpInProcessBrowserTestFixture();
-    EXPECT_CALL(policy_provider_, IsInitializationComplete(testing::_))
-        .WillRepeatedly(testing::Return(true));
-    policy_provider_.SetAutoRefresh();
-    policy::BrowserPolicyConnector::SetPolicyProviderForTesting(
-        &policy_provider_);
-  }
-
- protected:
-  policy::MockConfigurationPolicyProvider policy_provider_;
-};
-
 IN_PROC_BROWSER_TEST_F(ExtensionApiTest, PermissionsFail) {
   ASSERT_TRUE(RunExtensionTest("permissions/disabled")) << message_;
 
diff --git a/chrome/browser/extensions/content_script_apitest.cc b/chrome/browser/extensions/content_script_apitest.cc
index 251ec77..839c2f97 100644
--- a/chrome/browser/extensions/content_script_apitest.cc
+++ b/chrome/browser/extensions/content_script_apitest.cc
@@ -13,7 +13,9 @@
 #include "build/build_config.h"
 #include "chrome/browser/extensions/api/permissions/permissions_api.h"
 #include "chrome/browser/extensions/extension_apitest.h"
+#include "chrome/browser/extensions/extension_management_test_util.h"
 #include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/extension_with_management_policy_apitest.h"
 #include "chrome/browser/extensions/test_extension_dir.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
@@ -503,6 +505,18 @@
   ASSERT_TRUE(RunExtensionTest("content_scripts/permissions")) << message_;
 }
 
+IN_PROC_BROWSER_TEST_F(ExtensionApiTestWithManagementPolicy,
+                       ContentScriptPolicy) {
+  // Set enterprise policy to block injection to policy specified host.
+  {
+    ExtensionManagementPolicyUpdater pref(&policy_provider_);
+    pref.AddRuntimeBlockedHost("*", "*://example.com/*");
+  }
+  host_resolver()->AddRule("*.com", "127.0.0.1");
+  ASSERT_TRUE(StartEmbeddedTestServer());
+  ASSERT_TRUE(RunExtensionTest("content_scripts/policy")) << message_;
+}
+
 IN_PROC_BROWSER_TEST_P(ContentScriptApiTest, ContentScriptBypassPageCSP) {
   ASSERT_TRUE(StartEmbeddedTestServer());
   ASSERT_TRUE(RunExtensionTest("content_scripts/bypass_page_csp")) << message_;
diff --git a/chrome/browser/extensions/extension_management.cc b/chrome/browser/extensions/extension_management.cc
index c564797..559efe8a 100644
--- a/chrome/browser/extensions/extension_management.cc
+++ b/chrome/browser/extensions/extension_management.cc
@@ -198,6 +198,16 @@
   return default_settings_->blocked_permissions;
 }
 
+const URLPatternSet& ExtensionManagement::GetDefaultRuntimeBlockedHosts()
+    const {
+  return default_settings_->runtime_blocked_hosts;
+}
+
+const URLPatternSet& ExtensionManagement::GetDefaultRuntimeAllowedHosts()
+    const {
+  return default_settings_->runtime_allowed_hosts;
+}
+
 const URLPatternSet& ExtensionManagement::GetRuntimeBlockedHosts(
     const Extension* extension) const {
   auto iter_id = settings_by_id_.find(extension->id());
@@ -214,8 +224,13 @@
   return default_settings_->runtime_allowed_hosts;
 }
 
-bool ExtensionManagement::IsBlockedHost(const Extension* extension,
-                                        const GURL& url) const {
+bool ExtensionManagement::UsesDefaultRuntimeHostRestrictions(
+    const Extension* extension) const {
+  return settings_by_id_.find(extension->id()) == settings_by_id_.end();
+}
+
+bool ExtensionManagement::IsRuntimeBlockedHost(const Extension* extension,
+                                               const GURL& url) const {
   auto iter_id = settings_by_id_.find(extension->id());
   if (iter_id != settings_by_id_.end())
     return iter_id->second->runtime_blocked_hosts.MatchesURL(url);
diff --git a/chrome/browser/extensions/extension_management.h b/chrome/browser/extensions/extension_management.h
index 1dca6061..719c779 100644
--- a/chrome/browser/extensions/extension_management.h
+++ b/chrome/browser/extensions/extension_management.h
@@ -117,12 +117,30 @@
   // Returns the list of hosts blocked by policy for |extension|.
   const URLPatternSet& GetRuntimeBlockedHosts(const Extension* extension) const;
 
-  // Returns the list of hosts |extension| is limited to by policy.
+  // Returns the hosts exempted by policy from the RuntimeBlockedHosts for
+  // |extension|.
   const URLPatternSet& GetRuntimeAllowedHosts(const Extension* extension) const;
 
+  // Returns the list of hosts blocked by policy for Default scope. This can be
+  // overridden by an invividual scope which is queried via
+  // GetRuntimeBlockedHosts.
+  const URLPatternSet& GetDefaultRuntimeBlockedHosts() const;
+
+  // Returns the hosts exempted by policy from RuntimeBlockedHosts for
+  // the default scope. This can be overridden by an individual scope which is
+  // queries via GetRuntimeAllowedHosts. This should only be used to
+  // initialize a new renderer.
+  const URLPatternSet& GetDefaultRuntimeAllowedHosts() const;
+
+  // Checks if an |extension| has its own runtime_blocked_hosts or
+  // runtime_allowed_hosts defined in the individual scope of the
+  // ExtensionSettings policy.
+  // Returns false if an individual scoped setting isn't defined.
+  bool UsesDefaultRuntimeHostRestrictions(const Extension* extension) const;
+
   // Checks if a URL is on the blocked host permissions list for a specific
   // extension.
-  bool IsBlockedHost(const Extension* extension, const GURL& url) const;
+  bool IsRuntimeBlockedHost(const Extension* extension, const GURL& url) const;
 
   // Returns blocked permission set for |extension|.
   std::unique_ptr<const PermissionSet> GetBlockedPermissions(
diff --git a/chrome/browser/extensions/extension_management_constants.cc b/chrome/browser/extensions/extension_management_constants.cc
index 11747e58..29e438b8 100644
--- a/chrome/browser/extensions/extension_management_constants.cc
+++ b/chrome/browser/extensions/extension_management_constants.cc
@@ -22,6 +22,7 @@
 
 const char kRuntimeBlockedHosts[] = "runtime_blocked_hosts";
 const char kRuntimeAllowedHosts[] = "runtime_allowed_hosts";
+const size_t kMaxItemsURLPatternSet = 100;
 
 const char kUpdateUrl[] = "update_url";
 const char kInstallSources[] = "install_sources";
diff --git a/chrome/browser/extensions/extension_management_constants.h b/chrome/browser/extensions/extension_management_constants.h
index a548572..3ab1cb2d 100644
--- a/chrome/browser/extensions/extension_management_constants.h
+++ b/chrome/browser/extensions/extension_management_constants.h
@@ -27,6 +27,7 @@
 
 extern const char kRuntimeBlockedHosts[];
 extern const char kRuntimeAllowedHosts[];
+extern const size_t kMaxItemsURLPatternSet;
 
 extern const char kUpdateUrl[];
 extern const char kInstallSources[];
diff --git a/chrome/browser/extensions/extension_management_internal.cc b/chrome/browser/extensions/extension_management_internal.cc
index a2596ae..cab0415b 100644
--- a/chrome/browser/extensions/extension_management_internal.cc
+++ b/chrome/browser/extensions/extension_management_internal.cc
@@ -115,6 +115,14 @@
     // Get the list of URLPatterns.
     if (dict->GetListWithoutPathExpansion(key,
                                           &host_list_value)) {
+      if (host_list_value->GetSize() >
+          schema_constants::kMaxItemsURLPatternSet) {
+        LOG(WARNING) << "Exceeded maximum number of URL match patterns ("
+                     << schema_constants::kMaxItemsURLPatternSet
+                     << ") for attribute '" << key << "'";
+        return false;
+      }
+
       out_value->ClearPatterns();
       const int extension_scheme_mask =
           URLPattern::GetValidSchemeMaskForExtensions();
@@ -122,7 +130,8 @@
         std::string unparsed_str;
         host_list_value->GetString(i, &unparsed_str);
         URLPattern pattern = URLPattern(extension_scheme_mask);
-        URLPattern::ParseResult parse_result = pattern.Parse(unparsed_str);
+        URLPattern::ParseResult parse_result = pattern.Parse(
+            unparsed_str, URLPattern::ALLOW_WILDCARD_FOR_EFFECTIVE_TLD);
         if (parse_result != URLPattern::PARSE_SUCCESS) {
           LOG(WARNING) << kMalformedPreferenceWarning;
           LOG(WARNING) << "Invalid URL pattern '" + unparsed_str +
diff --git a/chrome/browser/extensions/extension_service.cc b/chrome/browser/extensions/extension_service.cc
index b74712c..42205f6 100644
--- a/chrome/browser/extensions/extension_service.cc
+++ b/chrome/browser/extensions/extension_service.cc
@@ -1255,6 +1255,21 @@
 
   extensions::ExtensionManagement* management =
       extensions::ExtensionManagementFactory::GetForBrowserContext(profile());
+  extensions::PermissionsUpdater(profile()).SetDefaultPolicyHostRestrictions(
+      management->GetDefaultRuntimeBlockedHosts(),
+      management->GetDefaultRuntimeAllowedHosts());
+  for (const auto& extension : registry_->enabled_extensions()) {
+    bool uses_default =
+        management->UsesDefaultRuntimeHostRestrictions(extension.get());
+    if (uses_default) {
+      extensions::PermissionsUpdater(profile()).SetUsesDefaultHostRestrictions(
+          extension.get());
+    } else {
+      extensions::PermissionsUpdater(profile()).SetPolicyHostRestrictions(
+          extension.get(), management->GetRuntimeBlockedHosts(extension.get()),
+          management->GetRuntimeAllowedHosts(extension.get()));
+    }
+  }
 
   // Loop through the disabled extension list, find extensions to re-enable
   // automatically. These extensions are exclusive from the |to_disable| and
diff --git a/chrome/browser/extensions/extension_with_management_policy_apitest.cc b/chrome/browser/extensions/extension_with_management_policy_apitest.cc
new file mode 100644
index 0000000..8ffaef6
--- /dev/null
+++ b/chrome/browser/extensions/extension_with_management_policy_apitest.cc
@@ -0,0 +1,21 @@
+// Copyright 2017 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 "chrome/browser/extensions/extension_with_management_policy_apitest.h"
+#include "components/policy/core/browser/browser_policy_connector.h"
+#include "net/test/embedded_test_server/http_request.h"
+
+ExtensionApiTestWithManagementPolicy::ExtensionApiTestWithManagementPolicy()
+    : ExtensionApiTest() {}
+
+ExtensionApiTestWithManagementPolicy::~ExtensionApiTestWithManagementPolicy() {}
+
+void ExtensionApiTestWithManagementPolicy::SetUpInProcessBrowserTestFixture() {
+  ExtensionApiTest::SetUpInProcessBrowserTestFixture();
+  EXPECT_CALL(policy_provider_, IsInitializationComplete(testing::_))
+      .WillRepeatedly(testing::Return(true));
+  policy_provider_.SetAutoRefresh();
+  policy::BrowserPolicyConnector::SetPolicyProviderForTesting(
+      &policy_provider_);
+}
diff --git a/chrome/browser/extensions/extension_with_management_policy_apitest.h b/chrome/browser/extensions/extension_with_management_policy_apitest.h
new file mode 100644
index 0000000..f0787f6
--- /dev/null
+++ b/chrome/browser/extensions/extension_with_management_policy_apitest.h
@@ -0,0 +1,29 @@
+// Copyright 2017 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.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_EXTENSION_WITH_MANAGEMENT_POLICY_APITEST_H_
+#define CHROME_BROWSER_EXTENSIONS_EXTENSION_WITH_MANAGEMENT_POLICY_APITEST_H_
+
+#include <string>
+#include <vector>
+
+#include "chrome/browser/extensions/extension_apitest.h"
+#include "components/policy/core/common/mock_configuration_policy_provider.h"
+
+// The ExtensionSettings policy affects host permissions which impacts several
+// API integration tests. This class enables easy declaration of
+// ExtensionSettings policies and functions commonly used during these tests.
+class ExtensionApiTestWithManagementPolicy : public ExtensionApiTest {
+ public:
+  ExtensionApiTestWithManagementPolicy();
+  ~ExtensionApiTestWithManagementPolicy() override;
+  void SetUpInProcessBrowserTestFixture() override;
+
+ protected:
+  policy::MockConfigurationPolicyProvider policy_provider_;
+
+  DISALLOW_COPY_AND_ASSIGN(ExtensionApiTestWithManagementPolicy);
+};
+
+#endif  // CHROME_BROWSER_EXTENSIONS_EXTENSION_WITH_MANAGEMENT_POLICY_APITEST_H_
diff --git a/chrome/browser/extensions/permissions_updater.cc b/chrome/browser/extensions/permissions_updater.cc
index a26f0ea..67e3ccb 100644
--- a/chrome/browser/extensions/permissions_updater.cc
+++ b/chrome/browser/extensions/permissions_updater.cc
@@ -146,6 +146,38 @@
   NotifyPermissionsUpdated(REMOVED, extension, to_remove);
 }
 
+void PermissionsUpdater::SetPolicyHostRestrictions(
+    const Extension* extension,
+    const URLPatternSet& runtime_blocked_hosts,
+    const URLPatternSet& runtime_allowed_hosts) {
+  extension->permissions_data()->SetPolicyHostRestrictions(
+      runtime_blocked_hosts, runtime_allowed_hosts);
+
+  // Send notification to the currently running renderers of the runtime block
+  // hosts settings.
+  const PermissionSet perms;
+  NotifyPermissionsUpdated(POLICY, extension, perms);
+}
+
+void PermissionsUpdater::SetUsesDefaultHostRestrictions(
+    const Extension* extension) {
+  extension->permissions_data()->SetUsesDefaultHostRestrictions();
+  const PermissionSet perms;
+  NotifyPermissionsUpdated(POLICY, extension, perms);
+}
+
+void PermissionsUpdater::SetDefaultPolicyHostRestrictions(
+    const URLPatternSet& default_runtime_blocked_hosts,
+    const URLPatternSet& default_runtime_allowed_hosts) {
+  PermissionsData::SetDefaultPolicyHostRestrictions(
+      default_runtime_blocked_hosts, default_runtime_allowed_hosts);
+
+  // Send notification to the currently running renderers of the runtime block
+  // hosts settings.
+  NotifyDefaultPolicyHostRestrictionsUpdated(default_runtime_blocked_hosts,
+                                             default_runtime_allowed_hosts);
+}
+
 void PermissionsUpdater::RemovePermissionsUnsafe(
     const Extension* extension,
     const PermissionSet& to_remove) {
@@ -257,28 +289,31 @@
     const Extension* extension,
     const PermissionSet& changed) {
   DCHECK_EQ(0, init_flag_ & INIT_FLAG_TRANSIENT);
-  if (changed.IsEmpty())
+
+  if (changed.IsEmpty() && event_type != POLICY)
     return;
 
   UpdatedExtensionPermissionsInfo::Reason reason;
-  events::HistogramValue histogram_value;
+  events::HistogramValue histogram_value = events::UNKNOWN;
   const char* event_name = NULL;
+  Profile* profile = Profile::FromBrowserContext(browser_context_);
 
   if (event_type == REMOVED) {
     reason = UpdatedExtensionPermissionsInfo::REMOVED;
     histogram_value = events::PERMISSIONS_ON_REMOVED;
     event_name = permissions::OnRemoved::kEventName;
-  } else {
-    CHECK_EQ(ADDED, event_type);
+  } else if (event_type == ADDED) {
     reason = UpdatedExtensionPermissionsInfo::ADDED;
     histogram_value = events::PERMISSIONS_ON_ADDED;
     event_name = permissions::OnAdded::kEventName;
+  } else {
+    DCHECK_EQ(POLICY, event_type);
+    reason = UpdatedExtensionPermissionsInfo::POLICY;
   }
 
   // Notify other APIs or interested parties.
-  UpdatedExtensionPermissionsInfo info = UpdatedExtensionPermissionsInfo(
-      extension, changed, reason);
-  Profile* profile = Profile::FromBrowserContext(browser_context_);
+  UpdatedExtensionPermissionsInfo info =
+      UpdatedExtensionPermissionsInfo(extension, changed, reason);
   content::NotificationService::current()->Notify(
       extensions::NOTIFICATION_EXTENSION_PERMISSIONS_UPDATED,
       content::Source<Profile>(profile),
@@ -290,6 +325,14 @@
       extension->permissions_data()->active_permissions());
   params.withheld_permissions = ExtensionMsg_PermissionSetStruct(
       extension->permissions_data()->withheld_permissions());
+  params.uses_default_policy_host_restrictions =
+      extension->permissions_data()->UsesDefaultPolicyHostRestrictions();
+  if (!params.uses_default_policy_host_restrictions) {
+    params.policy_blocked_hosts =
+        extension->permissions_data()->policy_blocked_hosts();
+    params.policy_allowed_hosts =
+        extension->permissions_data()->policy_allowed_hosts();
+  }
 
   // Send the new permissions to the renderers.
   for (RenderProcessHost::iterator i(RenderProcessHost::AllHostsIterator());
@@ -301,8 +344,35 @@
     }
   }
 
-  // Trigger the onAdded and onRemoved events in the extension.
-  DispatchEvent(extension->id(), histogram_value, event_name, changed);
+  // Trigger the onAdded and onRemoved events in the extension. We explicitly
+  // don't do this for policy-related events.
+  if (event_name)
+    DispatchEvent(extension->id(), histogram_value, event_name, changed);
+}
+
+// Notify the renderers that extension policy (policy_blocked_hosts) is updated
+// and provide new set of hosts.
+void PermissionsUpdater::NotifyDefaultPolicyHostRestrictionsUpdated(
+    const URLPatternSet& default_runtime_blocked_hosts,
+    const URLPatternSet& default_runtime_allowed_hosts) {
+  DCHECK_EQ(0, init_flag_ & INIT_FLAG_TRANSIENT);
+
+  Profile* profile = Profile::FromBrowserContext(browser_context_);
+
+  ExtensionMsg_UpdateDefaultPolicyHostRestrictions_Params params;
+  params.default_policy_blocked_hosts = default_runtime_blocked_hosts;
+  params.default_policy_allowed_hosts = default_runtime_allowed_hosts;
+
+  // Send the new policy to the renderers.
+  for (RenderProcessHost::iterator host_iterator(
+           RenderProcessHost::AllHostsIterator());
+       !host_iterator.IsAtEnd(); host_iterator.Advance()) {
+    RenderProcessHost* host = host_iterator.GetCurrentValue();
+    if (profile->IsSameProfile(
+            Profile::FromBrowserContext(host->GetBrowserContext()))) {
+      host->Send(new ExtensionMsg_UpdateDefaultPolicyHostRestrictions(params));
+    }
+  }
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/permissions_updater.h b/chrome/browser/extensions/permissions_updater.h
index 249c704..2b8597e7 100644
--- a/chrome/browser/extensions/permissions_updater.h
+++ b/chrome/browser/extensions/permissions_updater.h
@@ -19,6 +19,7 @@
 
 class Extension;
 class PermissionSet;
+class URLPatternSet;
 
 // Updates an Extension's active and granted permissions in persistent storage
 // and notifies interested parties of the changes.
@@ -81,6 +82,21 @@
   void RemovePermissionsUnsafe(const Extension* extension,
                                const PermissionSet& permissions);
 
+  // Sets list of hosts |extension| may not interact with (overrides default).
+  void SetPolicyHostRestrictions(const Extension* extension,
+                                 const URLPatternSet& runtime_blocked_hosts,
+                                 const URLPatternSet& runtime_allowed_hosts);
+
+  // Sets extension to use the default list of policy host restrictions.
+  void SetUsesDefaultHostRestrictions(const Extension* extension);
+
+  // Sets list of hosts extensions may not interact with. Extension specific
+  // exceptions to this default policy are defined with
+  // SetPolicyHostRestrictions.
+  void SetDefaultPolicyHostRestrictions(
+      const URLPatternSet& default_runtime_blocked_hosts,
+      const URLPatternSet& default_runtime_allowed_hosts);
+
   // Returns the set of revokable permissions.
   std::unique_ptr<const PermissionSet> GetRevokablePermissions(
       const Extension* extension) const;
@@ -98,6 +114,7 @@
   enum EventType {
     ADDED,
     REMOVED,
+    POLICY,
   };
 
   // Sets the |extension|'s active permissions to |active| and records the
@@ -123,6 +140,14 @@
                                 const Extension* extension,
                                 const PermissionSet& changed);
 
+  // Issues the relevant events, messages and notifications when the
+  // default scope management policy have changed.
+  // Specifically, this sends the ExtensionMsg_UpdateDefaultHostRestrictions
+  // IPC message.
+  void NotifyDefaultPolicyHostRestrictionsUpdated(
+      const URLPatternSet& default_runtime_blocked_hosts,
+      const URLPatternSet& default_runtime_allowed_hosts);
+
   // The associated BrowserContext.
   content::BrowserContext* browser_context_;
 
diff --git a/chrome/browser/extensions/permissions_updater_unittest.cc b/chrome/browser/extensions/permissions_updater_unittest.cc
index 67e31dfb..fd14606 100644
--- a/chrome/browser/extensions/permissions_updater_unittest.cc
+++ b/chrome/browser/extensions/permissions_updater_unittest.cc
@@ -271,6 +271,15 @@
         APIPermissionSet(), ManifestPermissionSet(), set, URLPatternSet());
   };
 
+  auto can_access_page =
+      [](scoped_refptr<const extensions::Extension> extension,
+         const GURL& document_url) -> bool {
+    PermissionsData::AccessType access =
+        extension.get()->permissions_data()->GetPageAccess(
+            extension.get(), document_url, -1, nullptr);
+    return access == PermissionsData::ACCESS_ALLOWED;
+  };
+
   {
     // Test revoking optional permissions.
     ListBuilder optional_permissions;
@@ -346,6 +355,7 @@
     // By default, all-hosts was withheld, so the extension shouldn't have
     // access to any site (like foo.com).
     const GURL kOrigin("http://foo.com");
+
     EXPECT_FALSE(extension->permissions_data()
                      ->active_permissions()
                      .HasExplicitAccessToOrigin(kOrigin));
@@ -381,6 +391,103 @@
                     .HasExplicitAccessToOrigin(kOrigin));
     EXPECT_TRUE(updater.GetRevokablePermissions(extension.get())->IsEmpty());
   }
+
+  {
+    // Make sure policy restriction updates update permission data.
+    URLPatternSet default_policy_blocked_hosts;
+    URLPatternSet default_policy_allowed_hosts;
+    URLPatternSet policy_blocked_hosts;
+    URLPatternSet policy_allowed_hosts;
+    ListBuilder optional_permissions;
+    ListBuilder required_permissions;
+    required_permissions.Append("tabs").Append("http://*/*");
+    scoped_refptr<const Extension> extension =
+        CreateExtensionWithOptionalPermissions(optional_permissions.Build(),
+                                               required_permissions.Build(),
+                                               "ExtensionSettings");
+    AddPattern(&default_policy_blocked_hosts, "http://*.google.com/*");
+    PermissionsUpdater updater(profile());
+    updater.InitializePermissions(extension.get());
+    extension->permissions_data()->SetDefaultPolicyHostRestrictions(
+        default_policy_blocked_hosts, default_policy_allowed_hosts);
+
+    // By default, all subdomains of google.com should be blocked.
+    const GURL kOrigin("http://foo.com");
+    const GURL kGoogle("http://www.google.com");
+    const GURL kExampleGoogle("http://example.google.com");
+    EXPECT_TRUE(
+        extension->permissions_data()->UsesDefaultPolicyHostRestrictions());
+    EXPECT_TRUE(can_access_page(extension, kOrigin));
+    EXPECT_FALSE(can_access_page(extension, kGoogle));
+    EXPECT_FALSE(can_access_page(extension, kExampleGoogle));
+
+    AddPattern(&default_policy_allowed_hosts, "http://example.google.com/*");
+    // Give the extension access to example.google.com. Now the
+    // example.google.com should not be a runtime blocked host.
+    updater.SetDefaultPolicyHostRestrictions(default_policy_blocked_hosts,
+                                             default_policy_allowed_hosts);
+
+    EXPECT_TRUE(
+        extension->permissions_data()->UsesDefaultPolicyHostRestrictions());
+    EXPECT_TRUE(can_access_page(extension, kOrigin));
+    EXPECT_FALSE(can_access_page(extension, kGoogle));
+    EXPECT_TRUE(can_access_page(extension, kExampleGoogle));
+
+    // Revoke extension access to foo.com. Now, foo.com should be a runtime
+    // blocked host.
+    AddPattern(&default_policy_blocked_hosts, "*://*.foo.com/");
+    updater.SetDefaultPolicyHostRestrictions(default_policy_blocked_hosts,
+                                             default_policy_allowed_hosts);
+    EXPECT_TRUE(
+        extension->permissions_data()->UsesDefaultPolicyHostRestrictions());
+    EXPECT_FALSE(can_access_page(extension, kOrigin));
+    EXPECT_FALSE(can_access_page(extension, kGoogle));
+    EXPECT_TRUE(can_access_page(extension, kExampleGoogle));
+
+    // Remove foo.com from blocked hosts. The extension should no longer have
+    // be a runtime blocked host.
+    default_policy_blocked_hosts.ClearPatterns();
+    AddPattern(&default_policy_blocked_hosts, "*://*.foo.com/");
+    updater.SetDefaultPolicyHostRestrictions(default_policy_blocked_hosts,
+                                             default_policy_allowed_hosts);
+    EXPECT_TRUE(
+        extension->permissions_data()->UsesDefaultPolicyHostRestrictions());
+    EXPECT_FALSE(can_access_page(extension, kOrigin));
+    EXPECT_TRUE(can_access_page(extension, kGoogle));
+    EXPECT_TRUE(can_access_page(extension, kExampleGoogle));
+
+    // Set an empty individual policy, should not affect default policy.
+    updater.SetPolicyHostRestrictions(extension.get(), policy_blocked_hosts,
+                                      policy_allowed_hosts);
+    EXPECT_FALSE(
+        extension->permissions_data()->UsesDefaultPolicyHostRestrictions());
+    EXPECT_TRUE(can_access_page(extension, kOrigin));
+    EXPECT_TRUE(can_access_page(extension, kGoogle));
+    EXPECT_TRUE(can_access_page(extension, kExampleGoogle));
+
+    // Block google.com for the Individual scope.
+    // Whitelist example.google.com for the Indiviaul scope.
+    // Leave google.com and example.google.com off both the whitelist and
+    // blacklist for Default scope.
+    AddPattern(&policy_blocked_hosts, "*://*.google.com/*");
+    AddPattern(&policy_allowed_hosts, "*://example.google.com/*");
+    updater.SetPolicyHostRestrictions(extension.get(), policy_blocked_hosts,
+                                      policy_allowed_hosts);
+    EXPECT_FALSE(
+        extension->permissions_data()->UsesDefaultPolicyHostRestrictions());
+    EXPECT_TRUE(can_access_page(extension, kOrigin));
+    EXPECT_FALSE(can_access_page(extension, kGoogle));
+    EXPECT_TRUE(can_access_page(extension, kExampleGoogle));
+
+    // Switch back to default scope for extension.
+    updater.SetUsesDefaultHostRestrictions(extension.get());
+    EXPECT_TRUE(
+        extension->permissions_data()->UsesDefaultPolicyHostRestrictions());
+    default_policy_blocked_hosts.ClearPatterns();
+    default_policy_allowed_hosts.ClearPatterns();
+    updater.SetDefaultPolicyHostRestrictions(default_policy_blocked_hosts,
+                                             default_policy_allowed_hosts);
+  }
 }
 
 // Test that the permissions updater delegate works - in this test it removes
diff --git a/chrome/common/extensions/permissions/permissions_data_unittest.cc b/chrome/common/extensions/permissions/permissions_data_unittest.cc
index 5147af4..c86741d 100644
--- a/chrome/common/extensions/permissions/permissions_data_unittest.cc
+++ b/chrome/common/extensions/permissions/permissions_data_unittest.cc
@@ -15,6 +15,7 @@
 #include "chrome/common/extensions/extension_test_util.h"
 #include "components/crx_file/id_util.h"
 #include "content/public/common/socket_permission_request.h"
+#include "extensions/common/constants.h"
 #include "extensions/common/error_utils.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/extension_builder.h"
@@ -224,11 +225,13 @@
   extension->permissions_data()->UpdateTabSpecificPermissions(
       1, PermissionSet(APIPermissionSet(), ManifestPermissionSet(), new_hosts,
                        URLPatternSet()));
-  EXPECT_TRUE(extension->permissions_data()->GetEffectiveHostPermissions().
-      MatchesURL(tab_url));
+  EXPECT_TRUE(
+      extension->permissions_data()->GetEffectiveHostPermissions().MatchesURL(
+          tab_url));
   extension->permissions_data()->ClearTabSpecificPermissions(1);
-  EXPECT_FALSE(extension->permissions_data()->GetEffectiveHostPermissions().
-      MatchesURL(tab_url));
+  EXPECT_FALSE(
+      extension->permissions_data()->GetEffectiveHostPermissions().MatchesURL(
+          tab_url));
 }
 
 TEST(PermissionsDataTest, SocketPermissions) {
@@ -236,8 +239,8 @@
   std::string error;
 
   extension = LoadManifest("socket_permissions", "empty.json");
-  EXPECT_FALSE(CheckSocketPermission(extension,
-      SocketPermissionRequest::TCP_CONNECT, "www.example.com", 80));
+  EXPECT_FALSE(CheckSocketPermission(
+      extension, SocketPermissionRequest::TCP_CONNECT, "www.example.com", 80));
 
   extension = LoadManifestUnchecked("socket_permissions",
                                     "socket1.json",
@@ -251,19 +254,18 @@
   EXPECT_EQ(expected_error_msg_header, error);
 
   extension = LoadManifest("socket_permissions", "socket2.json");
-  EXPECT_TRUE(CheckSocketPermission(extension,
-      SocketPermissionRequest::TCP_CONNECT, "www.example.com", 80));
-  EXPECT_FALSE(CheckSocketPermission(
-        extension, SocketPermissionRequest::UDP_BIND, "", 80));
   EXPECT_TRUE(CheckSocketPermission(
-        extension, SocketPermissionRequest::UDP_BIND, "", 8888));
+      extension, SocketPermissionRequest::TCP_CONNECT, "www.example.com", 80));
+  EXPECT_FALSE(CheckSocketPermission(
+      extension, SocketPermissionRequest::UDP_BIND, "", 80));
+  EXPECT_TRUE(CheckSocketPermission(
+      extension, SocketPermissionRequest::UDP_BIND, "", 8888));
 
   EXPECT_FALSE(CheckSocketPermission(
-        extension, SocketPermissionRequest::UDP_SEND_TO, "example.com", 1900));
-  EXPECT_TRUE(CheckSocketPermission(
-        extension,
-        SocketPermissionRequest::UDP_SEND_TO,
-        "239.255.255.250", 1900));
+      extension, SocketPermissionRequest::UDP_SEND_TO, "example.com", 1900));
+  EXPECT_TRUE(CheckSocketPermission(extension,
+                                    SocketPermissionRequest::UDP_SEND_TO,
+                                    "239.255.255.250", 1900));
 }
 
 TEST(PermissionsDataTest, IsRestrictedUrl) {
@@ -381,6 +383,9 @@
       : http_url("http://www.google.com"),
         http_url_with_path("http://www.google.com/index.html"),
         https_url("https://www.google.com"),
+        example_com("https://example.com"),
+        test_example_com("https://test.example.com"),
+        sample_example_com("https://sample.example.com"),
         file_url("file:///foo/bar"),
         favicon_url("chrome://favicon/http://www.google.com"),
         extension_url("chrome-extension://" +
@@ -391,6 +396,9 @@
     urls_.insert(http_url);
     urls_.insert(http_url_with_path);
     urls_.insert(https_url);
+    urls_.insert(example_com);
+    urls_.insert(test_example_com);
+    urls_.insert(sample_example_com);
     urls_.insert(file_url);
     urls_.insert(favicon_url);
     urls_.insert(extension_url);
@@ -476,6 +484,9 @@
   const GURL http_url;
   const GURL http_url_with_path;
   const GURL https_url;
+  const GURL example_com;
+  const GURL test_example_com;
+  const GURL sample_example_com;
   const GURL file_url;
 
   // We should allow host permission but not scripting permission for favicon
@@ -592,8 +603,8 @@
   scoped_refptr<Extension> extension;
 
   // Test <all_urls> for regular extensions.
-  extension = LoadManifestStrict("script_and_capture",
-      "extension_regular_all.json");
+  extension =
+      LoadManifestStrict("script_and_capture", "extension_regular_all.json");
   EXPECT_TRUE(Allowed(extension.get(), http_url));
   EXPECT_TRUE(Allowed(extension.get(), https_url));
   EXPECT_TRUE(CaptureOnly(extension.get(), file_url));
@@ -616,8 +627,8 @@
   EXPECT_TRUE(permissions_data->HasHostPermission(favicon_url));
 
   // Test * for scheme, which implies just the http/https schemes.
-  extension = LoadManifestStrict("script_and_capture",
-      "extension_wildcard.json");
+  extension =
+      LoadManifestStrict("script_and_capture", "extension_wildcard.json");
   EXPECT_TRUE(ScriptOnly(extension.get(), http_url));
   EXPECT_TRUE(ScriptOnly(extension.get(), https_url));
   EXPECT_TRUE(Blocked(extension.get(), settings_url));
@@ -645,21 +656,21 @@
 
   // Having chrome://favicon/* should not give you chrome://*
   extension = LoadManifestStrict("script_and_capture",
-      "extension_chrome_favicon_wildcard.json");
+                                 "extension_chrome_favicon_wildcard.json");
   EXPECT_TRUE(Blocked(extension.get(), settings_url));
   EXPECT_TRUE(ScriptOnly(extension.get(), favicon_url));
   EXPECT_TRUE(Blocked(extension.get(), about_url));
   EXPECT_TRUE(extension->permissions_data()->HasHostPermission(favicon_url));
 
   // Having http://favicon should not give you chrome://favicon
-  extension = LoadManifestStrict("script_and_capture",
-      "extension_http_favicon.json");
+  extension =
+      LoadManifestStrict("script_and_capture", "extension_http_favicon.json");
   EXPECT_TRUE(Blocked(extension.get(), settings_url));
   EXPECT_TRUE(Blocked(extension.get(), favicon_url));
 
   // Component extensions with <all_urls> should get everything.
   extension = LoadManifest("script_and_capture", "extension_component_all.json",
-      Manifest::COMPONENT, Extension::NO_FLAGS);
+                           Manifest::COMPONENT, Extension::NO_FLAGS);
   EXPECT_TRUE(Allowed(extension.get(), http_url));
   EXPECT_TRUE(Allowed(extension.get(), https_url));
   EXPECT_TRUE(Allowed(extension.get(), settings_url));
@@ -668,9 +679,9 @@
   EXPECT_TRUE(extension->permissions_data()->HasHostPermission(favicon_url));
 
   // Component extensions should only get access to what they ask for.
-  extension = LoadManifest("script_and_capture",
-      "extension_component_google.json", Manifest::COMPONENT,
-      Extension::NO_FLAGS);
+  extension =
+      LoadManifest("script_and_capture", "extension_component_google.json",
+                   Manifest::COMPONENT, Extension::NO_FLAGS);
   EXPECT_TRUE(ScriptOnly(extension.get(), http_url));
   EXPECT_TRUE(Blocked(extension.get(), https_url));
   EXPECT_TRUE(Blocked(extension.get(), file_url));
@@ -832,4 +843,164 @@
   }
 }
 
+TEST_F(ExtensionScriptAndCaptureVisibleTest, PolicyHostRestrictionsSwap) {
+  // Makes sure when an extension gets an individual policy for host
+  // restrictions it overrides the default policy. Also tests transitioning back
+  // to the default policy when an individual policy is removed.
+  URLPattern example_com_pattern =
+      URLPattern(URLPattern::SCHEME_ALL, "*://*.example.com/*");
+  URLPattern test_example_com_pattern =
+      URLPattern(URLPattern::SCHEME_ALL, "*://test.example.com/*");
+  URLPatternSet default_blocked;
+  URLPatternSet default_allowed;
+  default_blocked.AddPattern(example_com_pattern);
+  default_allowed.AddPattern(test_example_com_pattern);
+
+  // Test <all_urls> for regular extensions.
+  scoped_refptr<Extension> extension =
+      LoadManifestStrict("script_and_capture", "extension_regular_all.json");
+  extension->permissions_data()->SetDefaultPolicyHostRestrictions(
+      default_blocked, default_allowed);
+
+  // The default policy applies to all extensions at this point. The extension
+  // should be able to access test.example.com but be blocked from
+  // accessing any other subdomains of example.com or example.com itself.
+  EXPECT_TRUE(CaptureOnly(extension.get(), example_com));
+  EXPECT_TRUE(CaptureOnly(extension.get(), sample_example_com));
+  EXPECT_TRUE(Allowed(extension.get(), test_example_com));
+
+  URLPatternSet blocked;
+  blocked.AddPattern(test_example_com_pattern);
+  URLPatternSet allowed;
+  extension->permissions_data()->SetPolicyHostRestrictions(blocked, allowed);
+
+  // We've applied an individual policy which overrides the default policy.
+  // The only URL that should be blocked is test.example.com.
+  EXPECT_TRUE(Allowed(extension.get(), example_com));
+  EXPECT_TRUE(Allowed(extension.get(), sample_example_com));
+  EXPECT_TRUE(CaptureOnly(extension.get(), test_example_com));
+
+  blocked.AddPattern(example_com_pattern);
+  allowed.AddPattern(test_example_com_pattern);
+  extension->permissions_data()->SetPolicyHostRestrictions(blocked, allowed);
+
+  // Adding example.com and all its subdomains to the blocked list and
+  // test.example.com to the whitelist. This is still the individual policy
+  // Since the whitelist overrides a blacklist we expect to allow access to
+  // test.example.com but block access to all other example.com subdomains
+  // (sample.example.com) and example.com itself.
+  EXPECT_TRUE(CaptureOnly(extension.get(), example_com));
+  EXPECT_TRUE(CaptureOnly(extension.get(), sample_example_com));
+  EXPECT_TRUE(Allowed(extension.get(), test_example_com));
+
+  blocked.ClearPatterns();
+  allowed.ClearPatterns();
+  extension->permissions_data()->SetPolicyHostRestrictions(blocked, allowed);
+
+  // Cleared all URLs from the individual policy, so all URLs should have
+  // access. We want to make sure that a block at the default level doesn't
+  // apply since we're still definining an individual policy.
+  EXPECT_TRUE(Allowed(extension.get(), example_com));
+  EXPECT_TRUE(Allowed(extension.get(), sample_example_com));
+  EXPECT_TRUE(Allowed(extension.get(), test_example_com));
+
+  // Flip back to using default policy for this extension.
+  extension->permissions_data()->SetUsesDefaultHostRestrictions();
+
+  // Make sure the default policy has the same effect as before we defined an
+  // individual policy. Access to test.example.com should be allowed, but all
+  // other subdomains and example.com itself should be blocked.
+  EXPECT_TRUE(CaptureOnly(extension.get(), example_com));
+  EXPECT_TRUE(CaptureOnly(extension.get(), sample_example_com));
+  EXPECT_TRUE(Allowed(extension.get(), test_example_com));
+}
+
+TEST_F(ExtensionScriptAndCaptureVisibleTest, PolicyHostRestrictions) {
+  // Test that host restrictions applied by policy take effect on normal URLs,
+  // iframe urls, different schemes, and components.
+  URLPatternSet default_blocked;
+  URLPatternSet default_allowed;
+  default_blocked.AddPattern(
+      URLPattern(URLPattern::SCHEME_ALL, "https://*.example.com/*"));
+  default_allowed.AddPattern(
+      URLPattern(URLPattern::SCHEME_ALL, "https://test.example.com/*"));
+
+  // In all of these tests, test.example.com should have scripting allowed, with
+  // all other subdomains and example.com itself blocked.
+
+  // Test <all_urls> for regular extensions.
+  scoped_refptr<Extension> extension =
+      LoadManifestStrict("script_and_capture", "extension_regular_all.json");
+  extension->permissions_data()->SetDefaultPolicyHostRestrictions(
+      default_blocked, default_allowed);
+
+  EXPECT_TRUE(Allowed(extension.get(), http_url));
+  EXPECT_TRUE(Allowed(extension.get(), https_url));
+  EXPECT_TRUE(CaptureOnly(extension.get(), example_com));
+  EXPECT_TRUE(Allowed(extension.get(), test_example_com));
+  EXPECT_TRUE(CaptureOnly(extension.get(), sample_example_com));
+  EXPECT_TRUE(CaptureOnly(extension.get(), file_url));
+  EXPECT_TRUE(CaptureOnly(extension.get(), settings_url));
+  EXPECT_TRUE(CaptureOnly(extension.get(), favicon_url));
+  EXPECT_TRUE(CaptureOnly(extension.get(), about_url));
+  EXPECT_TRUE(CaptureOnly(extension.get(), extension_url));
+
+  // Test access to iframed content.
+  GURL within_extension_url = extension->GetResourceURL("page.html");
+  EXPECT_TRUE(AllowedScript(extension.get(), http_url));
+  EXPECT_TRUE(AllowedScript(extension.get(), http_url_with_path));
+  EXPECT_TRUE(BlockedScript(extension.get(), example_com));
+  EXPECT_TRUE(AllowedScript(extension.get(), test_example_com));
+  EXPECT_TRUE(BlockedScript(extension.get(), sample_example_com));
+  EXPECT_TRUE(AllowedScript(extension.get(), https_url));
+  EXPECT_TRUE(BlockedScript(extension.get(), within_extension_url));
+  EXPECT_TRUE(BlockedScript(extension.get(), extension_url));
+
+  // Supress host permission for example.com since its on the blocklist
+  EXPECT_FALSE(extension->permissions_data()->HasHostPermission(example_com));
+  // Allow host permission for test.example.com since its on the whitelist and
+  // blacklist. The whitelist overrides the blacklist.
+  EXPECT_TRUE(
+      extension->permissions_data()->HasHostPermission(test_example_com));
+  EXPECT_FALSE(extension->permissions_data()->HasHostPermission(settings_url));
+  EXPECT_FALSE(extension->permissions_data()->HasHostPermission(about_url));
+  EXPECT_TRUE(extension->permissions_data()->HasHostPermission(favicon_url));
+
+  // Test * for scheme, which implies just the http/https schemes.
+  extension =
+      LoadManifestStrict("script_and_capture", "extension_wildcard.json");
+  extension->permissions_data()->SetDefaultPolicyHostRestrictions(
+      default_blocked, default_allowed);
+  EXPECT_TRUE(ScriptOnly(extension.get(), http_url));
+  EXPECT_TRUE(Blocked(extension.get(), example_com));
+  EXPECT_TRUE(ScriptOnly(extension.get(), test_example_com));
+  EXPECT_TRUE(Blocked(extension.get(), sample_example_com));
+  EXPECT_TRUE(ScriptOnly(extension.get(), https_url));
+  EXPECT_TRUE(Blocked(extension.get(), settings_url));
+  EXPECT_TRUE(Blocked(extension.get(), about_url));
+  EXPECT_TRUE(Blocked(extension.get(), file_url));
+  EXPECT_TRUE(Blocked(extension.get(), favicon_url));
+  extension =
+      LoadManifest("script_and_capture", "extension_wildcard_settings.json");
+  extension->permissions_data()->SetDefaultPolicyHostRestrictions(
+      default_blocked, default_allowed);
+  EXPECT_TRUE(Blocked(extension.get(), settings_url));
+
+  // Component extensions with <all_urls> should get everything regardless of
+  // policy.
+  extension = LoadManifest("script_and_capture", "extension_component_all.json",
+                           Manifest::COMPONENT, Extension::NO_FLAGS);
+  extension->permissions_data()->SetDefaultPolicyHostRestrictions(
+      default_blocked, default_allowed);
+  EXPECT_TRUE(Allowed(extension.get(), http_url));
+  EXPECT_TRUE(Allowed(extension.get(), https_url));
+  EXPECT_TRUE(Allowed(extension.get(), example_com));
+  EXPECT_TRUE(Allowed(extension.get(), test_example_com));
+  EXPECT_TRUE(Allowed(extension.get(), sample_example_com));
+  EXPECT_TRUE(Allowed(extension.get(), settings_url));
+  EXPECT_TRUE(Allowed(extension.get(), about_url));
+  EXPECT_TRUE(Allowed(extension.get(), favicon_url));
+  EXPECT_TRUE(extension->permissions_data()->HasHostPermission(favicon_url));
+}
+
 }  // namespace extensions
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 05034c5..b149c1b1 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -2072,6 +2072,8 @@
         "../browser/extensions/extension_browsertest.h",
         "../browser/extensions/extension_function_test_utils.cc",
         "../browser/extensions/extension_function_test_utils.h",
+        "../browser/extensions/extension_with_management_policy_apitest.cc",
+        "../browser/extensions/extension_with_management_policy_apitest.h",
         "../browser/extensions/updater/extension_cache_fake.cc",
         "../browser/extensions/updater/extension_cache_fake.h",
         "../browser/safe_browsing/settings_reset_prompt/settings_reset_prompt_model_browsertest_win.cc",
diff --git a/chrome/test/data/extensions/api_test/content_scripts/policy/background.js b/chrome/test/data/extensions/api_test/content_scripts/policy/background.js
new file mode 100644
index 0000000..7376923
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/content_scripts/policy/background.js
@@ -0,0 +1,53 @@
+// Copyright 2017 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.
+
+var pass = chrome.test.callbackPass;
+var callbackFail = chrome.test.callbackFail;
+var listenForever = chrome.test.listenForever;
+
+var port;
+
+function testUrl(domain) {
+  return 'http://' + domain + ':' + port +
+      '/extensions/test_file.html';
+}
+
+function error(domain) {
+  return 'Cannot access contents of url "' + testUrl(domain) + '".' +
+    ' Extension manifest must request permission to access this host.';
+}
+
+// Creates a new tab, navigated to the specified |domain|.
+function createTestTab(domain, callback) {
+  var createdTabId = -1;
+  var done = listenForever(
+      chrome.tabs.onUpdated,
+      function(tabId, changeInfo, tab) {
+    if (tabId == createdTabId && changeInfo.status != 'loading') {
+      callback(tab);
+      done();
+    }
+  });
+
+  chrome.tabs.create({url: testUrl(domain)}, pass(function(tab) {
+    createdTabId = tab.id;
+  }));
+}
+
+chrome.test.getConfig(function(config) {
+  port = config.testServer.port;
+  chrome.test.runTests([
+
+   // Make sure we can't inject a script into a policy blocked host.
+   function policyBlocksInjection() {
+    createTestTab('example.com', pass(function(tab) {
+        chrome.tabs.executeScript(
+            tab.id, {code: 'document.title = "success"'},
+            callbackFail(
+                'This page cannot be scripted due to ' +
+                'an ExtensionsSettings policy.'));
+        }));
+   },
+  ]);
+});
diff --git a/chrome/test/data/extensions/api_test/content_scripts/policy/manifest.json b/chrome/test/data/extensions/api_test/content_scripts/policy/manifest.json
new file mode 100644
index 0000000..48de7c0
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/content_scripts/policy/manifest.json
@@ -0,0 +1,7 @@
+{
+  "name": "content_scripts/policy",
+  "version": "1",
+  "manifest_version": 2,
+  "permissions": ["tabs", "*://example.com/*"],
+  "background": { "scripts": ["background.js"] }
+}
diff --git a/extensions/browser/renderer_startup_helper.cc b/extensions/browser/renderer_startup_helper.cc
index 30bd75ee..55f6509 100644
--- a/extensions/browser/renderer_startup_helper.cc
+++ b/extensions/browser/renderer_startup_helper.cc
@@ -23,6 +23,7 @@
 #include "extensions/common/extensions_client.h"
 #include "extensions/common/features/feature_channel.h"
 #include "extensions/common/features/feature_session_type.h"
+#include "extensions/common/permissions/permissions_data.h"
 #include "ui/base/webui/web_ui_util.h"
 
 using content::BrowserContext;
@@ -123,6 +124,15 @@
         WebViewGuest::GetPartitionID(process)));
   }
 
+  // Load default policy_blocked_hosts and policy_allowed_hosts settings, part
+  // of the ExtensionSettings policy.
+  ExtensionMsg_UpdateDefaultPolicyHostRestrictions_Params params;
+  params.default_policy_blocked_hosts =
+      PermissionsData::default_policy_blocked_hosts();
+  params.default_policy_allowed_hosts =
+      PermissionsData::default_policy_allowed_hosts();
+  process->Send(new ExtensionMsg_UpdateDefaultPolicyHostRestrictions(params));
+
   // Loaded extensions.
   std::vector<ExtensionMsg_Loaded_Params> loaded_extensions;
   BrowserContext* renderer_context = process->GetBrowserContext();
diff --git a/extensions/common/constants.cc b/extensions/common/constants.cc
index c569a2d..7770085 100644
--- a/extensions/common/constants.cc
+++ b/extensions/common/constants.cc
@@ -112,4 +112,8 @@
     // Keep in sync with _api_features.json and _manifest_features.json.
 };
 
+// Error returned when scripting of a page is denied due to enterprise policy.
+const char kPolicyBlockedScripting[] =
+    "This page cannot be scripted due to an ExtensionsSettings policy.";
+
 }  // namespace extension_misc
diff --git a/extensions/common/constants.h b/extensions/common/constants.h
index 587299f..06f7f6f 100644
--- a/extensions/common/constants.h
+++ b/extensions/common/constants.h
@@ -220,6 +220,9 @@
 // Extension ids used by Hangouts.
 extern const char* const kHangoutsExtensionIds[6];
 
+// Error message when enterprise policy blocks scripting of webpage.
+extern const char kPolicyBlockedScripting[];
+
 }  // namespace extension_misc
 
 #endif  // EXTENSIONS_COMMON_CONSTANTS_H_
diff --git a/extensions/common/extension.h b/extensions/common/extension.h
index 97c0ca6eb..b87cc5f2dd 100644
--- a/extensions/common/extension.h
+++ b/extensions/common/extension.h
@@ -542,6 +542,7 @@
   enum Reason {
     ADDED,    // The permissions were added to the extension.
     REMOVED,  // The permissions were removed from the extension.
+    POLICY,   // The policy that affects permissions was updated.
   };
 
   Reason reason;
diff --git a/extensions/common/extension_messages.cc b/extensions/common/extension_messages.cc
index 8e56f09..c7d25b7 100644
--- a/extensions/common/extension_messages.cc
+++ b/extensions/common/extension_messages.cc
@@ -64,8 +64,14 @@
       location(extension->location()),
       path(extension->path()),
       active_permissions(extension->permissions_data()->active_permissions()),
-      withheld_permissions(extension->permissions_data()
-                               ->withheld_permissions()),
+      withheld_permissions(
+          extension->permissions_data()->withheld_permissions()),
+      policy_blocked_hosts(
+          extension->permissions_data()->policy_blocked_hosts()),
+      policy_allowed_hosts(
+          extension->permissions_data()->policy_allowed_hosts()),
+      uses_default_policy_blocked_allowed_hosts(
+          extension->permissions_data()->UsesDefaultPolicyHostRestrictions()),
       id(extension->id()),
       creation_flags(extension->creation_flags()) {
   if (include_tab_permissions) {
@@ -92,6 +98,12 @@
         extension->permissions_data();
     permissions_data->SetPermissions(active_permissions.ToPermissionSet(),
                                      withheld_permissions.ToPermissionSet());
+    if (uses_default_policy_blocked_allowed_hosts) {
+      permissions_data->SetUsesDefaultHostRestrictions();
+    } else {
+      permissions_data->SetPolicyHostRestrictions(policy_blocked_hosts,
+                                                  policy_allowed_hosts);
+    }
     for (const auto& pair : tab_specific_permissions) {
       permissions_data->UpdateTabSpecificPermissions(
           pair.first, *pair.second.ToPermissionSet());
@@ -359,6 +371,9 @@
   WriteParam(m, p.active_permissions);
   WriteParam(m, p.withheld_permissions);
   WriteParam(m, p.tab_specific_permissions);
+  WriteParam(m, p.policy_blocked_hosts);
+  WriteParam(m, p.policy_allowed_hosts);
+  WriteParam(m, p.uses_default_policy_blocked_allowed_hosts);
 }
 
 bool ParamTraits<ExtensionMsg_Loaded_Params>::Read(const base::Pickle* m,
@@ -370,7 +385,10 @@
          ReadParam(m, iter, &p->creation_flags) && ReadParam(m, iter, &p->id) &&
          ReadParam(m, iter, &p->active_permissions) &&
          ReadParam(m, iter, &p->withheld_permissions) &&
-         ReadParam(m, iter, &p->tab_specific_permissions);
+         ReadParam(m, iter, &p->tab_specific_permissions) &&
+         ReadParam(m, iter, &p->policy_blocked_hosts) &&
+         ReadParam(m, iter, &p->policy_allowed_hosts) &&
+         ReadParam(m, iter, &p->uses_default_policy_blocked_allowed_hosts);
 }
 
 void ParamTraits<ExtensionMsg_Loaded_Params>::Log(const param_type& p,
diff --git a/extensions/common/extension_messages.h b/extensions/common/extension_messages.h
index f7952e8..456bff1 100644
--- a/extensions/common/extension_messages.h
+++ b/extensions/common/extension_messages.h
@@ -321,6 +321,14 @@
   ExtensionMsg_PermissionSetStruct withheld_permissions;
   std::map<int, ExtensionMsg_PermissionSetStruct> tab_specific_permissions;
 
+  // Contains URLPatternSets defining which URLs an extension may not interact
+  // with by policy.
+  extensions::URLPatternSet policy_blocked_hosts;
+  extensions::URLPatternSet policy_allowed_hosts;
+
+  // If the extension uses the default list of blocked / allowed URLs.
+  bool uses_default_policy_blocked_allowed_hosts = true;
+
   // We keep this separate so that it can be used in logging.
   std::string id;
 
@@ -442,6 +450,15 @@
   IPC_STRUCT_MEMBER(std::string, extension_id)
   IPC_STRUCT_MEMBER(ExtensionMsg_PermissionSetStruct, active_permissions)
   IPC_STRUCT_MEMBER(ExtensionMsg_PermissionSetStruct, withheld_permissions)
+  IPC_STRUCT_MEMBER(extensions::URLPatternSet, policy_blocked_hosts)
+  IPC_STRUCT_MEMBER(extensions::URLPatternSet, policy_allowed_hosts)
+  IPC_STRUCT_MEMBER(bool, uses_default_policy_host_restrictions)
+IPC_STRUCT_END()
+
+// Parameters structure for ExtensionMsg_UpdateDefaultPolicyHostRestrictions.
+IPC_STRUCT_BEGIN(ExtensionMsg_UpdateDefaultPolicyHostRestrictions_Params)
+  IPC_STRUCT_MEMBER(extensions::URLPatternSet, default_policy_blocked_hosts)
+  IPC_STRUCT_MEMBER(extensions::URLPatternSet, default_policy_allowed_hosts)
 IPC_STRUCT_END()
 
 // Messages sent from the browser to the renderer:
@@ -548,6 +565,10 @@
 IPC_MESSAGE_CONTROL1(ExtensionMsg_UpdatePermissions,
                      ExtensionMsg_UpdatePermissions_Params)
 
+// Tell the renderer to update an extension's policy_blocked_hosts set.
+IPC_MESSAGE_CONTROL1(ExtensionMsg_UpdateDefaultPolicyHostRestrictions,
+                     ExtensionMsg_UpdateDefaultPolicyHostRestrictions_Params)
+
 // Tell the render view about new tab-specific permissions for an extension.
 IPC_MESSAGE_CONTROL5(ExtensionMsg_UpdateTabSpecificPermissions,
                      GURL /* url */,
diff --git a/extensions/common/permissions/permissions_data.cc b/extensions/common/permissions/permissions_data.cc
index 4025d23c..fb9cecb 100644
--- a/extensions/common/permissions/permissions_data.cc
+++ b/extensions/common/permissions/permissions_data.cc
@@ -8,6 +8,7 @@
 #include <utility>
 
 #include "base/command_line.h"
+#include "base/lazy_instance.h"
 #include "base/macros.h"
 #include "content/public/common/url_constants.h"
 #include "extensions/common/constants.h"
@@ -30,6 +31,17 @@
 
 PermissionsData::PolicyDelegate* g_policy_delegate = nullptr;
 
+struct DefaultRuntimePolicy {
+  URLPatternSet blocked_hosts;
+  URLPatternSet allowed_hosts;
+};
+
+// URLs an extension can't interact with. An extension can override these
+// settings by declaring its own list of blocked and allowed hosts using
+// policy_blocked_hosts and policy_allowed_hosts.
+base::LazyInstance<DefaultRuntimePolicy>::Leaky default_runtime_policy =
+    LAZY_INSTANCE_INITIALIZER;
+
 class AutoLockOnValidThread {
  public:
   AutoLockOnValidThread(base::Lock& lock, base::ThreadChecker* thread_checker)
@@ -127,6 +139,45 @@
   return false;
 }
 
+bool PermissionsData::UsesDefaultPolicyHostRestrictions() const {
+  DCHECK(!thread_checker_ || thread_checker_->CalledOnValidThread());
+  return uses_default_policy_host_restrictions;
+}
+
+const URLPatternSet& PermissionsData::default_policy_blocked_hosts() {
+  return default_runtime_policy.Get().blocked_hosts;
+}
+
+const URLPatternSet& PermissionsData::default_policy_allowed_hosts() {
+  return default_runtime_policy.Get().allowed_hosts;
+}
+
+const URLPatternSet PermissionsData::policy_blocked_hosts() const {
+  base::AutoLock auto_lock(runtime_lock_);
+  return PolicyBlockedHostsUnsafe();
+}
+
+const URLPatternSet& PermissionsData::PolicyBlockedHostsUnsafe() const {
+  DCHECK(!thread_checker_ || thread_checker_->CalledOnValidThread());
+  if (uses_default_policy_host_restrictions)
+    return default_policy_blocked_hosts();
+  runtime_lock_.AssertAcquired();
+  return policy_blocked_hosts_unsafe_;
+}
+
+const URLPatternSet PermissionsData::policy_allowed_hosts() const {
+  base::AutoLock auto_lock(runtime_lock_);
+  return PolicyAllowedHostsUnsafe();
+}
+
+const URLPatternSet& PermissionsData::PolicyAllowedHostsUnsafe() const {
+  DCHECK(!thread_checker_ || thread_checker_->CalledOnValidThread());
+  if (uses_default_policy_host_restrictions)
+    return default_policy_allowed_hosts();
+  runtime_lock_.AssertAcquired();
+  return policy_allowed_hosts_unsafe_;
+}
+
 void PermissionsData::BindToCurrentThread() const {
   DCHECK(!thread_checker_);
   thread_checker_.reset(new base::ThreadChecker());
@@ -140,6 +191,28 @@
   withheld_permissions_unsafe_ = std::move(withheld);
 }
 
+void PermissionsData::SetPolicyHostRestrictions(
+    const URLPatternSet& runtime_blocked_hosts,
+    const URLPatternSet& runtime_allowed_hosts) const {
+  AutoLockOnValidThread lock(runtime_lock_, thread_checker_.get());
+  policy_blocked_hosts_unsafe_ = runtime_blocked_hosts;
+  policy_allowed_hosts_unsafe_ = runtime_allowed_hosts;
+  uses_default_policy_host_restrictions = false;
+}
+
+void PermissionsData::SetUsesDefaultHostRestrictions() const {
+  AutoLockOnValidThread lock(runtime_lock_, thread_checker_.get());
+  uses_default_policy_host_restrictions = true;
+}
+
+// static
+void PermissionsData::SetDefaultPolicyHostRestrictions(
+    const URLPatternSet& default_runtime_blocked_hosts,
+    const URLPatternSet& default_runtime_allowed_hosts) {
+  default_runtime_policy.Get().blocked_hosts = default_runtime_blocked_hosts;
+  default_runtime_policy.Get().allowed_hosts = default_runtime_allowed_hosts;
+}
+
 void PermissionsData::SetActivePermissions(
     std::unique_ptr<const PermissionSet> active) const {
   AutoLockOnValidThread lock(runtime_lock_, thread_checker_.get());
@@ -208,7 +281,8 @@
 
 bool PermissionsData::HasHostPermission(const GURL& url) const {
   base::AutoLock auto_lock(runtime_lock_);
-  return active_permissions_unsafe_->HasExplicitAccessToOrigin(url);
+  return active_permissions_unsafe_->HasExplicitAccessToOrigin(url) &&
+         !IsRuntimeBlockedHost(url);
 }
 
 bool PermissionsData::HasEffectiveAccessToAllHosts() const {
@@ -327,6 +401,12 @@
   return false;
 }
 
+bool PermissionsData::IsRuntimeBlockedHost(const GURL& url) const {
+  runtime_lock_.AssertAcquired();
+  return PolicyBlockedHostsUnsafe().MatchesURL(url) &&
+         !PolicyAllowedHostsUnsafe().MatchesURL(url);
+}
+
 PermissionsData::AccessType PermissionsData::CanRunOnPage(
     const Extension* extension,
     const GURL& document_url,
@@ -335,9 +415,14 @@
     const URLPatternSet& withheld_url_patterns,
     std::string* error) const {
   runtime_lock_.AssertAcquired();
-  if (g_policy_delegate &&
-      !g_policy_delegate->CanExecuteScriptOnPage(extension, document_url,
-                                                 tab_id, error)) {
+  if (g_policy_delegate && !g_policy_delegate->CanExecuteScriptOnPage(
+                               extension, document_url, tab_id, error))
+    return ACCESS_DENIED;
+
+  if (extension->location() != Manifest::COMPONENT &&
+      extension->permissions_data()->IsRuntimeBlockedHost(document_url)) {
+    if (error)
+      *error = extension_misc::kPolicyBlockedScripting;
     return ACCESS_DENIED;
   }
 
diff --git a/extensions/common/permissions/permissions_data.h b/extensions/common/permissions/permissions_data.h
index 3b87e79..6997403 100644
--- a/extensions/common/permissions/permissions_data.h
+++ b/extensions/common/permissions/permissions_data.h
@@ -82,6 +82,10 @@
                               const Extension* extension,
                               std::string* error);
 
+  // Is this extension using the default scope for policy_blocked_hosts and
+  // policy_allowed_hosts of the ExtensionSettings policy.
+  bool UsesDefaultPolicyHostRestrictions() const;
+
   // Locks the permissions data to the current thread. We don't do this on
   // construction, since extensions are initialized across multiple threads.
   void BindToCurrentThread() const;
@@ -91,6 +95,27 @@
   void SetPermissions(std::unique_ptr<const PermissionSet> active,
                       std::unique_ptr<const PermissionSet> withheld) const;
 
+  // Applies restrictions from enterprise policy limiting which URLs this
+  // extension can interact with. The same policy can also define a default set
+  // of URL restrictions using SetDefaultPolicyHostRestrictions. This function
+  // overrides any default host restriction policy.
+  void SetPolicyHostRestrictions(
+      const URLPatternSet& runtime_blocked_hosts,
+      const URLPatternSet& runtime_allowed_hosts) const;
+
+  // Marks this extension as using default enterprise policy limiting
+  // which URLs extensions can interact with. A default policy can be set with
+  // SetDefaultPolicyHostRestrictions. A policy specific to this extension
+  // can be set with SetPolicyHostRestrictions.
+  void SetUsesDefaultHostRestrictions() const;
+
+  // Applies restrictions from enterprise policy limiting which URLs all
+  // extensions can interact with. This restriction can be overridden on a
+  // per-extension basis with SetPolicyHostRestrictions.
+  static void SetDefaultPolicyHostRestrictions(
+      const URLPatternSet& default_runtime_blocked_hosts,
+      const URLPatternSet& default_runtime_allowed_hosts);
+
   // Sets the active permissions, leaving withheld the same.
   void SetActivePermissions(std::unique_ptr<const PermissionSet> active) const;
 
@@ -201,11 +226,42 @@
     return *withheld_permissions_unsafe_;
   }
 
+  // Returns list of hosts this extension may not interact with by policy.
+  // This should only be used for 1. Serialization when initializing renderers
+  // or 2. Called from utility methods above. For all other uses, call utility
+  // methods instead (e.g. CanAccessPage()).
+  static const URLPatternSet& default_policy_blocked_hosts();
+
+  // Returns list of hosts this extension may interact with regardless of
+  // what is defined by policy_blocked_hosts().
+  // This should only be used for 1. Serialization when initializing renderers
+  // or 2. Called from utility methods above. For all other uses, call utility
+  // methods instead (e.g. CanAccessPage()).
+  static const URLPatternSet& default_policy_allowed_hosts();
+
+  // Returns list of hosts this extension may not interact with by policy.
+  // This should only be used for 1. Serialization when initializing renderers
+  // or 2. Called from utility methods above. For all other uses, call utility
+  // methods instead (e.g. CanAccessPage()).
+  const URLPatternSet policy_blocked_hosts() const;
+
+  // Returns list of hosts this extension may interact with regardless of
+  // what is defined by policy_blocked_hosts().
+  // This should only be used for 1. Serialization when initializing renderers
+  // or 2. Called from utility methods above. For all other uses, call utility
+  // methods instead (e.g. CanAccessPage()).
+  const URLPatternSet policy_allowed_hosts() const;
+
 #if defined(UNIT_TEST)
   const PermissionSet* GetTabSpecificPermissionsForTesting(int tab_id) const {
     base::AutoLock auto_lock(runtime_lock_);
     return GetTabSpecificPermissions(tab_id);
   }
+
+  bool IsRuntimeBlockedHostForTesting(const GURL& url) const {
+    base::AutoLock auto_lock(runtime_lock_);
+    return IsRuntimeBlockedHost(url);
+  }
 #endif
 
  private:
@@ -233,6 +289,17 @@
                           const URLPatternSet& withheld_url_patterns,
                           std::string* error) const;
 
+  // Check if a specific URL is blocked by policy from extension use at runtime.
+  bool IsRuntimeBlockedHost(const GURL& url) const;
+
+  // Same as policy_blocked_hosts but instead returns a reference.
+  // You must acquire runtime_lock_ before calling this.
+  const URLPatternSet& PolicyBlockedHostsUnsafe() const;
+
+  // Same as policy_allowed_hosts but instead returns a reference.
+  // You must acquire runtime_lock_ before calling this.
+  const URLPatternSet& PolicyAllowedHostsUnsafe() const;
+
   // The associated extension's id.
   std::string extension_id_;
 
@@ -255,6 +322,20 @@
   // withheld_permissions() accessor.
   mutable std::unique_ptr<const PermissionSet> withheld_permissions_unsafe_;
 
+  // The list of hosts an extension may not interact with by policy.
+  // Unless you need to change |policy_blocked_hosts_unsafe_|, use the (safe)
+  // policy_blocked_hosts() accessor.
+  mutable URLPatternSet policy_blocked_hosts_unsafe_;
+
+  // The exclusive list of hosts an extension may interact with by policy.
+  // Unless you need to change |policy_allowed_hosts_unsafe_|, use the (safe)
+  // policy_allowed_hosts() accessor.
+  mutable URLPatternSet policy_allowed_hosts_unsafe_;
+
+  // If the ExtensionSettings policy is not being used, or no per-extension
+  // exception to the default policy was declared for this extension.
+  mutable bool uses_default_policy_host_restrictions = true;
+
   mutable TabPermissionsMap tab_specific_permissions_;
 
   mutable std::unique_ptr<base::ThreadChecker> thread_checker_;
diff --git a/extensions/common/url_pattern.cc b/extensions/common/url_pattern.cc
index 5677fbc83..dba84a5 100644
--- a/extensions/common/url_pattern.cc
+++ b/extensions/common/url_pattern.cc
@@ -141,12 +141,14 @@
     : valid_schemes_(SCHEME_NONE),
       match_all_urls_(false),
       match_subdomains_(false),
+      match_effective_tld_(true),
       port_("*") {}
 
 URLPattern::URLPattern(int valid_schemes)
     : valid_schemes_(valid_schemes),
       match_all_urls_(false),
       match_subdomains_(false),
+      match_effective_tld_(true),
       port_("*") {}
 
 URLPattern::URLPattern(int valid_schemes, base::StringPiece pattern)
@@ -155,6 +157,7 @@
     : valid_schemes_(valid_schemes),
       match_all_urls_(false),
       match_subdomains_(false),
+      match_effective_tld_(true),
       port_("*") {
   ParseResult result = Parse(pattern);
   if (PARSE_SUCCESS != result)
@@ -183,9 +186,15 @@
 }
 
 URLPattern::ParseResult URLPattern::Parse(base::StringPiece pattern) {
+  return Parse(pattern, DENY_WILDCARD_FOR_EFFECTIVE_TLD);
+}
+
+URLPattern::ParseResult URLPattern::Parse(base::StringPiece pattern,
+                                          ParseOptions parse_options) {
   spec_.clear();
   SetMatchAllURLs(false);
   SetMatchSubdomains(false);
+  SetMatchEffectiveTld(true);
   SetPort("*");
 
   // Special case pattern to match every valid URL.
@@ -267,6 +276,14 @@
       host_components.erase(host_components.begin(),
                             host_components.begin() + 1);
     }
+
+    // If explicitly allowed, the last component can optionally be '*' to
+    // match all effective TLDs.
+    if (parse_options == ALLOW_WILDCARD_FOR_EFFECTIVE_TLD &&
+        host_components.size() > 1 && host_components.back() == "*") {
+      match_effective_tld_ = false;
+      host_components.pop_back();
+    }
     host_ = base::JoinString(host_components, ".");
 
     path_start_pos = host_end_pos;
@@ -321,6 +338,11 @@
   match_subdomains_ = val;
 }
 
+void URLPattern::SetMatchEffectiveTld(bool val) {
+  spec_.clear();
+  match_effective_tld_ = val;
+}
+
 bool URLPattern::SetScheme(base::StringPiece scheme) {
   spec_.clear();
   scheme.CopyToString(&scheme_);
@@ -421,10 +443,19 @@
 }
 
 bool URLPattern::MatchesHost(const GURL& test) const {
-  const base::StringPiece test_host(
-      CanonicalizeHostForMatching(test.host_piece()));
+  base::StringPiece test_host(CanonicalizeHostForMatching(test.host_piece()));
   const base::StringPiece pattern_host(CanonicalizeHostForMatching(host_));
 
+  // If we don't care about matching the effective TLD, remove it.
+  if (!match_effective_tld_) {
+    int reg_length = net::registry_controlled_domains::GetRegistryLength(
+        test, net::registry_controlled_domains::EXCLUDE_UNKNOWN_REGISTRIES,
+        net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES);
+    if (reg_length > 0) {
+      test_host = test_host.substr(0, test_host.size() - reg_length - 1);
+    }
+  }
+
   // If the hosts are exactly equal, we have a match.
   if (test_host == pattern_host)
     return true;
@@ -529,6 +560,12 @@
     if (!host_.empty())
       spec += host_;
 
+    if (!match_effective_tld_) {
+      if (!host_.empty())
+        spec += ".";
+      spec += "*";
+    }
+
     if (port_ != "*") {
       spec += ":";
       spec += port_;
diff --git a/extensions/common/url_pattern.h b/extensions/common/url_pattern.h
index 23f06878..efb93fa 100644
--- a/extensions/common/url_pattern.h
+++ b/extensions/common/url_pattern.h
@@ -81,6 +81,12 @@
     NUM_PARSE_RESULTS
   };
 
+  // Types of URLPattern that Parse() considers valid.
+  enum ParseOptions {
+    DENY_WILDCARD_FOR_EFFECTIVE_TLD,
+    ALLOW_WILDCARD_FOR_EFFECTIVE_TLD,
+  };
+
   // The <all_urls> string pattern.
   static const char kAllUrlsPattern[];
 
@@ -107,8 +113,10 @@
   // Initializes this instance by parsing the provided string. Returns
   // URLPattern::PARSE_SUCCESS on success, or an error code otherwise. On
   // failure, this instance will have some intermediate values and is in an
-  // invalid state.
+  // invalid state. If you want to allow the match pattern to specify a wildcard
+  // for the effective TLD, specify in |parse_options|.
   ParseResult Parse(base::StringPiece pattern_str);
+  ParseResult Parse(base::StringPiece pattern_str, ParseOptions parse_options);
 
   // Gets the bitmask of valid schemes.
   int valid_schemes() const { return valid_schemes_; }
@@ -123,6 +131,15 @@
   bool match_subdomains() const { return match_subdomains_; }
   void SetMatchSubdomains(bool val);
 
+  // Gets whether host() contains an effective TLD. If false, during
+  // a match, the URL you're comparing must have its TLD removed
+  // prior to comparison.
+  // e.g. For the match pattern https://google.com/*
+  //      If this is true: host() would be google.com
+  //      If this is false: host() would be google
+  bool match_effective_tld() const { return match_effective_tld_; }
+  void SetMatchEffectiveTld(bool val);
+
   // Gets the path the pattern matches with the leading slash. This can have
   // embedded asterisks which are interpreted using glob rules.
   const std::string& path() const { return path_; }
@@ -247,6 +264,12 @@
   // component of the pattern's host was "*".
   bool match_subdomains_;
 
+  // Whether we should match the effective TLD of the host. This is true by
+  // default and only false if ParseOptions is ALLOW_WILDCARD_FOR_EFFECTIVE_TLD
+  // and is only applicable when the the pattern's host ends with ".*"
+  // (e.g. https://example.*/*).
+  bool match_effective_tld_;
+
   // The port.
   std::string port_;
 
diff --git a/extensions/common/url_pattern_unittest.cc b/extensions/common/url_pattern_unittest.cc
index 62419f6..bd60583 100644
--- a/extensions/common/url_pattern_unittest.cc
+++ b/extensions/common/url_pattern_unittest.cc
@@ -28,19 +28,19 @@
     const char* pattern;
     URLPattern::ParseResult expected_result;
   } kInvalidPatterns[] = {
-    { "http", URLPattern::PARSE_ERROR_MISSING_SCHEME_SEPARATOR },
-    { "http:", URLPattern::PARSE_ERROR_WRONG_SCHEME_SEPARATOR },
-    { "http:/", URLPattern::PARSE_ERROR_WRONG_SCHEME_SEPARATOR },
-    { "about://", URLPattern::PARSE_ERROR_WRONG_SCHEME_SEPARATOR },
-    { "http://", URLPattern::PARSE_ERROR_EMPTY_HOST },
-    { "http:///", URLPattern::PARSE_ERROR_EMPTY_HOST },
-    { "http:// /", URLPattern::PARSE_ERROR_EMPTY_HOST },
-    { "http://*foo/bar", URLPattern::PARSE_ERROR_INVALID_HOST_WILDCARD },
-    { "http://foo.*.bar/baz", URLPattern::PARSE_ERROR_INVALID_HOST_WILDCARD },
-    { "http://fo.*.ba:123/baz", URLPattern::PARSE_ERROR_INVALID_HOST_WILDCARD },
-    { "http:/bar", URLPattern::PARSE_ERROR_WRONG_SCHEME_SEPARATOR },
-    { "http://bar", URLPattern::PARSE_ERROR_EMPTY_PATH },
-  };
+      {"http", URLPattern::PARSE_ERROR_MISSING_SCHEME_SEPARATOR},
+      {"http:", URLPattern::PARSE_ERROR_WRONG_SCHEME_SEPARATOR},
+      {"http:/", URLPattern::PARSE_ERROR_WRONG_SCHEME_SEPARATOR},
+      {"about://", URLPattern::PARSE_ERROR_WRONG_SCHEME_SEPARATOR},
+      {"http://", URLPattern::PARSE_ERROR_EMPTY_HOST},
+      {"http:///", URLPattern::PARSE_ERROR_EMPTY_HOST},
+      {"http:// /", URLPattern::PARSE_ERROR_EMPTY_HOST},
+      {"http://*foo/bar", URLPattern::PARSE_ERROR_INVALID_HOST_WILDCARD},
+      {"http://foo.*.bar/baz", URLPattern::PARSE_ERROR_INVALID_HOST_WILDCARD},
+      {"http://fo.*.ba:123/baz", URLPattern::PARSE_ERROR_INVALID_HOST_WILDCARD},
+      {"http:/bar", URLPattern::PARSE_ERROR_WRONG_SCHEME_SEPARATOR},
+      {"http://bar", URLPattern::PARSE_ERROR_EMPTY_PATH},
+      {"http://foo.*/bar", URLPattern::PARSE_ERROR_INVALID_HOST_WILDCARD}};
 
   for (size_t i = 0; i < arraysize(kInvalidPatterns); ++i) {
     URLPattern pattern(URLPattern::SCHEME_ALL);
@@ -65,30 +65,31 @@
     URLPattern::ParseResult expected_result;
     const char* expected_port;
   } kTestPatterns[] = {
-    { "http://foo:1234/", URLPattern::PARSE_SUCCESS, "1234" },
-    { "http://foo:1234/bar", URLPattern::PARSE_SUCCESS, "1234" },
-    { "http://*.foo:1234/", URLPattern::PARSE_SUCCESS, "1234" },
-    { "http://*.foo:1234/bar", URLPattern::PARSE_SUCCESS, "1234" },
-    { "http://:1234/", URLPattern::PARSE_SUCCESS, "1234" },
-    { "http://foo:/", URLPattern::PARSE_ERROR_INVALID_PORT, "*" },
-    { "http://foo:*/", URLPattern::PARSE_SUCCESS, "*" },
-    { "http://*.foo:/", URLPattern::PARSE_ERROR_INVALID_PORT, "*" },
-    { "http://foo:com/", URLPattern::PARSE_ERROR_INVALID_PORT, "*" },
-    { "http://foo:123456/", URLPattern::PARSE_ERROR_INVALID_PORT, "*" },
-    { "http://foo:80:80/monkey", URLPattern::PARSE_ERROR_INVALID_PORT, "*" },
-    { "file://foo:1234/bar", URLPattern::PARSE_SUCCESS, "*" },
-    { "chrome://foo:1234/bar", URLPattern::PARSE_ERROR_INVALID_PORT, "*" },
+      {"http://foo:1234/", URLPattern::PARSE_SUCCESS, "1234"},
+      {"http://foo:1234/bar", URLPattern::PARSE_SUCCESS, "1234"},
+      {"http://*.foo:1234/", URLPattern::PARSE_SUCCESS, "1234"},
+      {"http://*.foo:1234/bar", URLPattern::PARSE_SUCCESS, "1234"},
+      {"http://:1234/", URLPattern::PARSE_SUCCESS, "1234"},
+      {"http://foo:/", URLPattern::PARSE_ERROR_INVALID_PORT, "*"},
+      {"http://foo:*/", URLPattern::PARSE_SUCCESS, "*"},
+      {"http://*.foo:/", URLPattern::PARSE_ERROR_INVALID_PORT, "*"},
+      {"http://foo:com/", URLPattern::PARSE_ERROR_INVALID_PORT, "*"},
+      {"http://foo:123456/", URLPattern::PARSE_ERROR_INVALID_PORT, "*"},
+      {"http://foo:80:80/monkey", URLPattern::PARSE_ERROR_INVALID_PORT, "*"},
+      {"file://foo:1234/bar", URLPattern::PARSE_SUCCESS, "*"},
+      {"chrome://foo:1234/bar", URLPattern::PARSE_ERROR_INVALID_PORT, "*"},
 
-    // Port-like strings in the path should not trigger a warning.
-    { "http://*/:1234", URLPattern::PARSE_SUCCESS, "*" },
-    { "http://*.foo/bar:1234", URLPattern::PARSE_SUCCESS, "*" },
-    { "http://foo/bar:1234/path", URLPattern::PARSE_SUCCESS, "*" },
-  };
+      // Port-like strings in the path should not trigger a warning.
+      {"http://*/:1234", URLPattern::PARSE_SUCCESS, "*"},
+      {"http://*.foo/bar:1234", URLPattern::PARSE_SUCCESS, "*"},
+      {"http://foo/bar:1234/path", URLPattern::PARSE_SUCCESS, "*"},
+      {"http://*.foo.*/:1234", URLPattern::PARSE_SUCCESS, "*"}};
 
   for (size_t i = 0; i < arraysize(kTestPatterns); ++i) {
     URLPattern pattern(URLPattern::SCHEME_ALL);
     EXPECT_EQ(kTestPatterns[i].expected_result,
-              pattern.Parse(kTestPatterns[i].pattern))
+              pattern.Parse(kTestPatterns[i].pattern,
+                            URLPattern::ALLOW_WILDCARD_FOR_EFFECTIVE_TLD))
         << "Got unexpected result for URL pattern: "
         << kTestPatterns[i].pattern;
     EXPECT_EQ(kTestPatterns[i].expected_port, pattern.port())
@@ -103,6 +104,7 @@
   EXPECT_EQ("http", pattern.scheme());
   EXPECT_EQ("", pattern.host());
   EXPECT_TRUE(pattern.match_subdomains());
+  EXPECT_TRUE(pattern.match_effective_tld());
   EXPECT_FALSE(pattern.match_all_urls());
   EXPECT_EQ("/*", pattern.path());
   EXPECT_TRUE(pattern.MatchesURL(GURL("http://google.com")));
@@ -119,6 +121,7 @@
   EXPECT_EQ("https", pattern.scheme());
   EXPECT_EQ("", pattern.host());
   EXPECT_TRUE(pattern.match_subdomains());
+  EXPECT_TRUE(pattern.match_effective_tld());
   EXPECT_FALSE(pattern.match_all_urls());
   EXPECT_EQ("/foo*", pattern.path());
   EXPECT_TRUE(pattern.MatchesURL(GURL("https://www.google.com/foo")));
@@ -137,6 +140,7 @@
   EXPECT_EQ("http", pattern.scheme());
   EXPECT_EQ("google.com", pattern.host());
   EXPECT_TRUE(pattern.match_subdomains());
+  EXPECT_TRUE(pattern.match_effective_tld());
   EXPECT_FALSE(pattern.match_all_urls());
   EXPECT_EQ("/foo*bar", pattern.path());
   EXPECT_TRUE(pattern.MatchesURL(GURL("http://google.com/foobar")));
@@ -157,6 +161,7 @@
   EXPECT_EQ("file", pattern.scheme());
   EXPECT_EQ("", pattern.host());
   EXPECT_FALSE(pattern.match_subdomains());
+  EXPECT_TRUE(pattern.match_effective_tld());
   EXPECT_FALSE(pattern.match_all_urls());
   EXPECT_EQ("/foo?bar\\*baz", pattern.path());
   EXPECT_TRUE(pattern.MatchesURL(GURL("file:///foo?bar\\hellobaz")));
@@ -170,6 +175,7 @@
   EXPECT_EQ("http", pattern.scheme());
   EXPECT_EQ("127.0.0.1", pattern.host());
   EXPECT_FALSE(pattern.match_subdomains());
+  EXPECT_TRUE(pattern.match_effective_tld());
   EXPECT_FALSE(pattern.match_all_urls());
   EXPECT_EQ("/*", pattern.path());
   EXPECT_TRUE(pattern.MatchesURL(GURL("http://127.0.0.1")));
@@ -183,6 +189,7 @@
   EXPECT_EQ("http", pattern.scheme());
   EXPECT_EQ("0.0.1", pattern.host());
   EXPECT_TRUE(pattern.match_subdomains());
+  EXPECT_TRUE(pattern.match_effective_tld());
   EXPECT_FALSE(pattern.match_all_urls());
   EXPECT_EQ("/*", pattern.path());
   // Subdomain matching is never done if the argument has an IP address host.
@@ -199,6 +206,7 @@
   EXPECT_EQ("http", pattern.scheme());
   EXPECT_EQ("xn--gkd", pattern.host());
   EXPECT_TRUE(pattern.match_subdomains());
+  EXPECT_TRUE(pattern.match_effective_tld());
   EXPECT_FALSE(pattern.match_all_urls());
   EXPECT_EQ("/a%C2%81%E1*", pattern.path());
   EXPECT_TRUE(pattern.MatchesURL(
@@ -214,6 +222,7 @@
   EXPECT_EQ("chrome", pattern.scheme());
   EXPECT_EQ("favicon", pattern.host());
   EXPECT_FALSE(pattern.match_subdomains());
+  EXPECT_TRUE(pattern.match_effective_tld());
   EXPECT_FALSE(pattern.match_all_urls());
   EXPECT_EQ("/*", pattern.path());
   EXPECT_TRUE(pattern.MatchesURL(GURL("chrome://favicon/http://google.com")));
@@ -231,6 +240,7 @@
   EXPECT_FALSE(pattern.MatchesScheme("file"));
   EXPECT_FALSE(pattern.MatchesScheme("ftp"));
   EXPECT_TRUE(pattern.match_subdomains());
+  EXPECT_TRUE(pattern.match_effective_tld());
   EXPECT_FALSE(pattern.match_all_urls());
   EXPECT_EQ("/*", pattern.path());
   EXPECT_TRUE(pattern.MatchesURL(GURL("http://127.0.0.1")));
@@ -250,6 +260,7 @@
   EXPECT_TRUE(pattern.MatchesScheme("filesystem"));
   EXPECT_TRUE(pattern.MatchesScheme("chrome-extension"));
   EXPECT_TRUE(pattern.match_subdomains());
+  EXPECT_TRUE(pattern.match_effective_tld());
   EXPECT_TRUE(pattern.match_all_urls());
   EXPECT_EQ("/*", pattern.path());
   EXPECT_TRUE(pattern.MatchesURL(GURL("chrome://favicon/http://google.com")));
@@ -285,6 +296,7 @@
   EXPECT_TRUE(pattern.MatchesScheme("about"));
   EXPECT_TRUE(pattern.MatchesScheme("chrome-extension"));
   EXPECT_TRUE(pattern.match_subdomains());
+  EXPECT_TRUE(pattern.match_effective_tld());
   EXPECT_TRUE(pattern.match_all_urls());
   EXPECT_EQ("/*", pattern.path());
   EXPECT_TRUE(pattern.MatchesURL(GURL("chrome://favicon/http://google.com")));
@@ -336,6 +348,7 @@
   EXPECT_EQ("", pattern.host());
   EXPECT_FALSE(pattern.match_subdomains());
   EXPECT_FALSE(pattern.match_all_urls());
+  EXPECT_TRUE(pattern.match_effective_tld());
   EXPECT_EQ("/foo*", pattern.path());
   EXPECT_FALSE(pattern.MatchesURL(GURL("file://foo")));
   EXPECT_FALSE(pattern.MatchesURL(GURL("file://foobar")));
@@ -352,6 +365,7 @@
   EXPECT_EQ("", pattern.host());
   EXPECT_FALSE(pattern.match_subdomains());
   EXPECT_FALSE(pattern.match_all_urls());
+  EXPECT_TRUE(pattern.match_effective_tld());
   EXPECT_EQ("/foo*", pattern.path());
   EXPECT_FALSE(pattern.MatchesURL(GURL("file://foo")));
   EXPECT_FALSE(pattern.MatchesURL(GURL("file://foobar")));
@@ -368,6 +382,7 @@
   // Since hostname is ignored for file://.
   EXPECT_EQ("", pattern.host());
   EXPECT_FALSE(pattern.match_subdomains());
+  EXPECT_TRUE(pattern.match_effective_tld());
   EXPECT_FALSE(pattern.match_all_urls());
   EXPECT_EQ("/foo*", pattern.path());
   EXPECT_FALSE(pattern.MatchesURL(GURL("file://foo")));
@@ -435,6 +450,39 @@
       GURL("filesystem:chrome-extension://ftw/t/file.txt")));
 }
 
+// effective TLD wildcard
+TEST(URLPatternTest, EffectiveTldWildcard) {
+  URLPattern pattern(kAllSchemes);
+  EXPECT_EQ(URLPattern::PARSE_SUCCESS,
+            pattern.Parse("http://*.google.*/foo*bar",
+                          URLPattern::ALLOW_WILDCARD_FOR_EFFECTIVE_TLD));
+  EXPECT_EQ("http", pattern.scheme());
+  EXPECT_EQ("google", pattern.host());
+  EXPECT_TRUE(pattern.match_subdomains());
+  EXPECT_FALSE(pattern.match_effective_tld());
+  EXPECT_FALSE(pattern.match_all_urls());
+  EXPECT_EQ("/foo*bar", pattern.path());
+  EXPECT_TRUE(pattern.MatchesURL(GURL("http://google.com/foobar")));
+  EXPECT_TRUE(pattern.MatchesURL(GURL("http://www.google.com.br/foo?bar")));
+  EXPECT_TRUE(
+      pattern.MatchesURL(GURL("http://monkey.images.google.co.uk/foooobar")));
+  EXPECT_FALSE(pattern.MatchesURL(GURL("http://yahoo.com/foobar")));
+  EXPECT_TRUE(pattern.MatchesURL(GURL("filesystem:http://google.com/foo/bar")));
+  EXPECT_FALSE(pattern.MatchesURL(
+      GURL("filesystem:http://google.com/temporary/foobar")));
+  URLPattern pattern_sub(kAllSchemes);
+  EXPECT_EQ(URLPattern::PARSE_SUCCESS,
+            pattern_sub.Parse("https://maps.google.*/",
+                              URLPattern::ALLOW_WILDCARD_FOR_EFFECTIVE_TLD));
+  EXPECT_EQ("https", pattern_sub.scheme());
+  EXPECT_EQ("maps.google", pattern_sub.host());
+  EXPECT_FALSE(pattern_sub.match_subdomains());
+  EXPECT_FALSE(pattern_sub.match_all_urls());
+  EXPECT_EQ("/", pattern_sub.path());
+  EXPECT_TRUE(pattern_sub.MatchesURL(GURL("https://maps.google.co.uk/")));
+  EXPECT_FALSE(pattern_sub.MatchesURL(GURL("https://sub.maps.google.co.uk/")));
+}
+
 static const struct GetAsStringPatterns {
   const char* pattern;
 } kGetAsStringTestCases[] = {
diff --git a/extensions/renderer/dispatcher.cc b/extensions/renderer/dispatcher.cc
index 86aa533..429056c 100644
--- a/extensions/renderer/dispatcher.cc
+++ b/extensions/renderer/dispatcher.cc
@@ -908,6 +908,8 @@
   IPC_MESSAGE_HANDLER(ExtensionMsg_TransferBlobs, OnTransferBlobs)
   IPC_MESSAGE_HANDLER(ExtensionMsg_Unloaded, OnUnloaded)
   IPC_MESSAGE_HANDLER(ExtensionMsg_UpdatePermissions, OnUpdatePermissions)
+  IPC_MESSAGE_HANDLER(ExtensionMsg_UpdateDefaultPolicyHostRestrictions,
+                      OnUpdateDefaultPolicyHostRestrictions)
   IPC_MESSAGE_HANDLER(ExtensionMsg_UpdateTabSpecificPermissions,
                       OnUpdateTabSpecificPermissions)
   IPC_MESSAGE_HANDLER(ExtensionMsg_ClearTabSpecificPermissions,
@@ -1031,7 +1033,6 @@
       extension_load_errors_[param.id] = error;
       continue;
     }
-
     RendererExtensionRegistry* extension_registry =
         RendererExtensionRegistry::Get();
     // TODO(kalman): This test is deliberately not a CHECK (though I wish it
@@ -1048,12 +1049,18 @@
       // consider making this a release CHECK.
       NOTREACHED();
     }
+    if (param.uses_default_policy_blocked_allowed_hosts) {
+      extension->permissions_data()->SetUsesDefaultHostRestrictions();
+    } else {
+      extension->permissions_data()->SetPolicyHostRestrictions(
+          param.policy_blocked_hosts, param.policy_allowed_hosts);
+    }
   }
 
   // Update the available bindings for all contexts. These may have changed if
   // an externally_connectable extension was loaded that can connect to an
   // open webpage.
-  UpdateBindings("");
+  UpdateBindings(std::string());
 }
 
 void Dispatcher::OnMessageInvoke(const std::string& extension_id,
@@ -1180,6 +1187,13 @@
   // extension's URL just won't match anything anymore.
 }
 
+void Dispatcher::OnUpdateDefaultPolicyHostRestrictions(
+    const ExtensionMsg_UpdateDefaultPolicyHostRestrictions_Params& params) {
+  PermissionsData::SetDefaultPolicyHostRestrictions(
+      params.default_policy_blocked_hosts, params.default_policy_allowed_hosts);
+  UpdateBindings(std::string());
+}
+
 void Dispatcher::OnUpdatePermissions(
     const ExtensionMsg_UpdatePermissions_Params& params) {
   const Extension* extension =
@@ -1199,6 +1213,12 @@
 
   extension->permissions_data()->SetPermissions(std::move(active),
                                                 std::move(withheld));
+  if (params.uses_default_policy_host_restrictions) {
+    extension->permissions_data()->SetUsesDefaultHostRestrictions();
+  } else {
+    extension->permissions_data()->SetPolicyHostRestrictions(
+        params.policy_blocked_hosts, params.policy_allowed_hosts);
+  }
   UpdateBindings(extension->id());
 }
 
diff --git a/extensions/renderer/dispatcher.h b/extensions/renderer/dispatcher.h
index 29b7bb8..4ef3c004 100644
--- a/extensions/renderer/dispatcher.h
+++ b/extensions/renderer/dispatcher.h
@@ -42,6 +42,7 @@
 struct ExtensionMsg_Loaded_Params;
 struct ExtensionMsg_TabConnectionInfo;
 struct ExtensionMsg_UpdatePermissions_Params;
+struct ExtensionMsg_UpdateDefaultPolicyHostRestrictions_Params;
 
 namespace blink {
 class WebLocalFrame;
@@ -183,6 +184,8 @@
   void OnTransferBlobs(const std::vector<std::string>& blob_uuids);
   void OnUnloaded(const std::string& id);
   void OnUpdatePermissions(const ExtensionMsg_UpdatePermissions_Params& params);
+  void OnUpdateDefaultPolicyHostRestrictions(
+      const ExtensionMsg_UpdateDefaultPolicyHostRestrictions_Params& params);
   void OnUpdateTabSpecificPermissions(const GURL& visible_url,
                                       const std::string& extension_id,
                                       const URLPatternSet& new_hosts,