Implement the basic declarativeContent API.

This CL includes the top_url and css conditions and the ShowPageAction
action.  CSS matching is implemented inefficiently using a
MutationObserver, but I plan to push that into WebKit before releasing
this to stable.

BUG=172011

Review URL: https://chromiumcodereview.appspot.com/11547033

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@179051 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/extensions/api/declarative/rules_registry_service.cc b/chrome/browser/extensions/api/declarative/rules_registry_service.cc
index 24e7f32..58a849d4 100644
--- a/chrome/browser/extensions/api/declarative/rules_registry_service.cc
+++ b/chrome/browser/extensions/api/declarative/rules_registry_service.cc
@@ -8,6 +8,8 @@
 #include "base/logging.h"
 #include "chrome/browser/extensions/api/declarative/initializing_rules_registry.h"
 #include "chrome/browser/extensions/api/declarative/rules_registry_storage_delegate.h"
+#include "chrome/browser/extensions/api/declarative_content/content_constants.h"
+#include "chrome/browser/extensions/api/declarative_content/content_rules_registry.h"
 #include "chrome/browser/extensions/api/declarative_webrequest/webrequest_constants.h"
 #include "chrome/browser/extensions/api/declarative_webrequest/webrequest_rules_registry.h"
 #include "chrome/browser/extensions/api/web_request/web_request_api.h"
@@ -69,6 +71,21 @@
       content::BrowserThread::IO, FROM_HERE,
       base::Bind(&RegisterToExtensionWebRequestEventRouterOnIO,
           profile_, web_request_rules_registry));
+
+#if defined(ENABLE_EXTENSIONS)
+  delegate = new RulesRegistryStorageDelegate();
+  scoped_refptr<ContentRulesRegistry> content_rules_registry(
+      new ContentRulesRegistry(profile_, delegate));
+  delegate->InitOnUIThread(profile_, content_rules_registry,
+      GetDeclarativeRuleStorageKey(
+          declarative_content_constants::kOnPageChanged,
+          profile_->IsOffTheRecord()));
+  delegates_.push_back(delegate);
+
+  RegisterRulesRegistry(declarative_content_constants::kOnPageChanged,
+                        content_rules_registry);
+  content_rules_registry_ = content_rules_registry.get();
+#endif  // defined(ENABLE_EXTENSIONS)
 }
 
 void RulesRegistryService::Shutdown() {
diff --git a/chrome/browser/extensions/api/declarative/rules_registry_service.h b/chrome/browser/extensions/api/declarative/rules_registry_service.h
index db0485b2..1910d16 100644
--- a/chrome/browser/extensions/api/declarative/rules_registry_service.h
+++ b/chrome/browser/extensions/api/declarative/rules_registry_service.h
@@ -21,6 +21,7 @@
 }
 
 namespace extensions {
+class ContentRulesRegistry;
 class RulesRegistry;
 class RulesRegistryStorageDelegate;
 }
@@ -50,6 +51,11 @@
   scoped_refptr<RulesRegistry> GetRulesRegistry(
       const std::string& event_name) const;
 
+  // Accessors for each type of rules registry.
+  ContentRulesRegistry* content_rules_registry() const {
+    return content_rules_registry_;
+  }
+
   // For testing.
   void SimulateExtensionUnloaded(const std::string& extension_id);
  private:
@@ -72,6 +78,10 @@
   // keep track of them so we can tell them to do cleanup on shutdown.
   std::vector<RulesRegistryStorageDelegate*> delegates_;
 
+  // Weak pointer into rule_registries_ to make it easier to handle content rule
+  // conditions.
+  ContentRulesRegistry* content_rules_registry_;
+
   content::NotificationRegistrar registrar_;
 
   Profile* profile_;
diff --git a/chrome/browser/extensions/api/declarative/rules_registry_storage_delegate.cc b/chrome/browser/extensions/api/declarative/rules_registry_storage_delegate.cc
index 2cb62d3..ce9fb2c4 100644
--- a/chrome/browser/extensions/api/declarative/rules_registry_storage_delegate.cc
+++ b/chrome/browser/extensions/api/declarative/rules_registry_storage_delegate.cc
@@ -194,7 +194,8 @@
   const ExtensionSet* extensions = extension_service->extensions();
   for (ExtensionSet::const_iterator i = extensions->begin();
        i != extensions->end(); ++i) {
-    if ((*i)->HasAPIPermission(APIPermission::kDeclarativeWebRequest) &&
+    if (((*i)->HasAPIPermission(APIPermission::kDeclarativeContent) ||
+         (*i)->HasAPIPermission(APIPermission::kDeclarativeWebRequest)) &&
         extension_service->IsIncognitoEnabled((*i)->id()))
       ReadFromStorage((*i)->id());
   }
@@ -211,8 +212,8 @@
         content::Details<const extensions::Extension>(details).ptr();
     // TODO(mpcomplete): This API check should generalize to any use of
     // declarative rules, not just webRequest.
-    if (extension->HasAPIPermission(
-            APIPermission::kDeclarativeWebRequest)) {
+    if (extension->HasAPIPermission(APIPermission::kDeclarativeContent) ||
+        extension->HasAPIPermission(APIPermission::kDeclarativeWebRequest)) {
       ExtensionInfoMap* extension_info_map =
           ExtensionSystem::Get(profile_)->info_map();
       if (profile_->IsOffTheRecord() &&
diff --git a/chrome/browser/extensions/api/declarative_content/OWNERS b/chrome/browser/extensions/api/declarative_content/OWNERS
new file mode 100644
index 0000000..5ee81759
--- /dev/null
+++ b/chrome/browser/extensions/api/declarative_content/OWNERS
@@ -0,0 +1,2 @@
[email protected]
[email protected]
diff --git a/chrome/browser/extensions/api/declarative_content/content_action.cc b/chrome/browser/extensions/api/declarative_content/content_action.cc
new file mode 100644
index 0000000..24661dd2
--- /dev/null
+++ b/chrome/browser/extensions/api/declarative_content/content_action.cc
@@ -0,0 +1,148 @@
+// Copyright (c) 2012 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/api/declarative_content/content_action.h"
+
+#include <map>
+
+#include "base/lazy_instance.h"
+#include "base/stringprintf.h"
+#include "base/values.h"
+#include "chrome/browser/extensions/api/declarative_content/content_constants.h"
+#include "chrome/browser/extensions/extension_action.h"
+#include "chrome/browser/extensions/extension_action_manager.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/extension_system.h"
+#include "chrome/browser/extensions/extension_tab_util.h"
+#include "chrome/common/extensions/extension.h"
+#include "content/public/browser/invalidate_type.h"
+#include "content/public/browser/web_contents.h"
+
+namespace extensions {
+
+namespace keys = declarative_content_constants;
+
+namespace {
+// Error messages.
+const char kInvalidInstanceTypeError[] =
+    "An action has an invalid instanceType: %s";
+
+#define INPUT_FORMAT_VALIDATE(test) do { \
+    if (!(test)) { \
+      *bad_message = true; \
+      return scoped_ptr<ContentAction>(NULL); \
+    } \
+  } while (0)
+
+//
+// The following are concrete actions.
+//
+
+// Action that instructs to show an extension's page action.
+class ShowPageAction : public ContentAction {
+ public:
+  ShowPageAction() {}
+  virtual ~ShowPageAction() {}
+
+  // Implementation of ContentAction:
+  virtual Type GetType() const OVERRIDE { return ACTION_SHOW_PAGE_ACTION; }
+  virtual void Apply(const std::string& extension_id,
+                     const base::Time& extension_install_time,
+                     ApplyInfo* apply_info) const OVERRIDE {
+    GetPageAction(apply_info->profile, extension_id)->DeclarativeShow(
+        ExtensionTabUtil::GetTabId(apply_info->tab));
+    apply_info->tab->NotifyNavigationStateChanged(
+        content::INVALIDATE_TYPE_PAGE_ACTIONS);
+  }
+  virtual void Revert(const std::string& extension_id,
+                      const base::Time& extension_install_time,
+                      ApplyInfo* apply_info) const OVERRIDE {
+    GetPageAction(apply_info->profile, extension_id)->
+        UndoDeclarativeShow(ExtensionTabUtil::GetTabId(apply_info->tab));
+    apply_info->tab->NotifyNavigationStateChanged(
+        content::INVALIDATE_TYPE_PAGE_ACTIONS);
+  }
+
+ private:
+  static ExtensionAction* GetPageAction(Profile* profile,
+                                        const std::string& extension_id) {
+    ExtensionService* service =
+        ExtensionSystem::Get(profile)->extension_service();
+    const Extension* extension = service->GetInstalledExtension(extension_id);
+    DCHECK(extension);
+    return ExtensionActionManager::Get(profile)->GetPageAction(*extension);
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(ShowPageAction);
+};
+
+// Helper function for ContentActions that can be instantiated by just
+// calling the constructor.
+template <class T>
+scoped_ptr<ContentAction> CallConstructorFactoryMethod(
+    const base::DictionaryValue* dict,
+    std::string* error,
+    bool* bad_message) {
+  return scoped_ptr<ContentAction>(new T);
+}
+
+struct ContentActionFactory {
+  // Factory methods for ContentAction instances. |dict| contains the json
+  // dictionary that describes the action. |error| is used to return error
+  // messages in case the extension passed an action that was syntactically
+  // correct but semantically incorrect. |bad_message| is set to true in case
+  // |dict| does not confirm to the validated JSON specification.
+  typedef scoped_ptr<ContentAction>
+      (* FactoryMethod)(const base::DictionaryValue* /* dict */,
+                        std::string* /* error */,
+                        bool* /* bad_message */);
+  // Maps the name of a declarativeContent action type to the factory
+  // function creating it.
+  std::map<std::string, FactoryMethod> factory_methods;
+
+  ContentActionFactory() {
+    factory_methods[keys::kShowPageAction] =
+        &CallConstructorFactoryMethod<ShowPageAction>;
+  }
+};
+
+base::LazyInstance<ContentActionFactory>::Leaky
+    g_content_action_factory = LAZY_INSTANCE_INITIALIZER;
+
+}  // namespace
+
+//
+// ContentAction
+//
+
+ContentAction::ContentAction() {}
+
+ContentAction::~ContentAction() {}
+
+// static
+scoped_ptr<ContentAction> ContentAction::Create(
+    const base::Value& json_action,
+    std::string* error,
+    bool* bad_message) {
+  *error = "";
+  *bad_message = false;
+
+  const base::DictionaryValue* action_dict = NULL;
+  INPUT_FORMAT_VALIDATE(json_action.GetAsDictionary(&action_dict));
+
+  std::string instance_type;
+  INPUT_FORMAT_VALIDATE(
+      action_dict->GetString(keys::kInstanceType, &instance_type));
+
+  ContentActionFactory& factory = g_content_action_factory.Get();
+  std::map<std::string, ContentActionFactory::FactoryMethod>::iterator
+      factory_method_iter = factory.factory_methods.find(instance_type);
+  if (factory_method_iter != factory.factory_methods.end())
+    return (*factory_method_iter->second)(action_dict, error, bad_message);
+
+  *error = base::StringPrintf(kInvalidInstanceTypeError, instance_type.c_str());
+  return scoped_ptr<ContentAction>();
+}
+
+}  // namespace extensions
diff --git a/chrome/browser/extensions/api/declarative_content/content_action.h b/chrome/browser/extensions/api/declarative_content/content_action.h
new file mode 100644
index 0000000..4750ac9
--- /dev/null
+++ b/chrome/browser/extensions/api/declarative_content/content_action.h
@@ -0,0 +1,70 @@
+// Copyright (c) 2012 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_API_DECLARATIVE_CONTENT_CONTENT_ACTION_H_
+#define CHROME_BROWSER_EXTENSIONS_API_DECLARATIVE_CONTENT_CONTENT_ACTION_H_
+
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "chrome/browser/extensions/api/declarative/declarative_rule.h"
+
+class Profile;
+
+namespace base {
+class Time;
+class Value;
+}
+
+namespace content {
+class WebContents;
+}
+
+namespace extensions {
+
+// Base class for all ContentActions of the declarative content API.
+class ContentAction {
+ public:
+  // Type identifiers for concrete ContentActions.
+  enum Type {
+    ACTION_SHOW_PAGE_ACTION,
+  };
+
+  struct ApplyInfo {
+    Profile* profile;
+    content::WebContents* tab;
+  };
+
+  ContentAction();
+  virtual ~ContentAction();
+
+  virtual Type GetType() const = 0;
+
+  // Applies or reverts this ContentAction on a particular tab for a particular
+  // extension.  Revert exists to keep the actions up to date as the page
+  // changes.
+  virtual void Apply(const std::string& extension_id,
+                     const base::Time& extension_install_time,
+                     ApplyInfo* apply_info) const = 0;
+  virtual void Revert(const std::string& extension_id,
+                      const base::Time& extension_install_time,
+                      ApplyInfo* apply_info) const = 0;
+
+  // Factory method that instantiates a concrete ContentAction
+  // implementation according to |json_action|, the representation of the
+  // ContentAction as received from the extension API.
+  // Sets |error| and returns NULL in case of a semantic error that cannot
+  // be caught by schema validation. Sets |bad_message| and returns NULL
+  // in case the input is syntactically unexpected.
+  static scoped_ptr<ContentAction> Create(const base::Value& json_action,
+                                          std::string* error,
+                                          bool* bad_message);
+};
+
+typedef DeclarativeActionSet<ContentAction> ContentActionSet;
+
+}  // namespace extensions
+
+#endif  // CHROME_BROWSER_EXTENSIONS_API_DECLARATIVE_CONTENT_CONTENT_ACTION_H_
diff --git a/chrome/browser/extensions/api/declarative_content/content_action_unittest.cc b/chrome/browser/extensions/api/declarative_content/content_action_unittest.cc
new file mode 100644
index 0000000..099bebf8
--- /dev/null
+++ b/chrome/browser/extensions/api/declarative_content/content_action_unittest.cc
@@ -0,0 +1,155 @@
+// Copyright (c) 2012 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/api/declarative_content/content_action.h"
+
+#include "base/command_line.h"
+#include "base/message_loop.h"
+#include "base/test/values_test_util.h"
+#include "base/values.h"
+#include "chrome/browser/extensions/extension_action.h"
+#include "chrome/browser/extensions/extension_action_manager.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/extension_tab_util.h"
+#include "chrome/browser/extensions/test_extension_system.h"
+#include "chrome/browser/sessions/session_tab_helper.h"
+#include "chrome/common/extensions/extension_builder.h"
+#include "chrome/common/extensions/value_builder.h"
+#include "chrome/test/base/testing_profile.h"
+#include "content/public/test/test_browser_thread.h"
+#include "content/public/test/web_contents_tester.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_WIN)
+#include "ui/base/win/scoped_ole_initializer.h"
+#endif
+
+namespace extensions {
+namespace {
+
+using base::test::ParseJson;
+using testing::HasSubstr;
+
+class TestExtensionEnvironment {
+ public:
+  TestExtensionEnvironment()
+      : ui_thread_(BrowserThread::UI, &loop_),
+        extension_service_(NULL) {}
+
+  TestingProfile* profile() { return &profile_; }
+
+  ExtensionService* GetExtensionService() {
+    if (extension_service_ == NULL) {
+      TestExtensionSystem* extension_system =
+          static_cast<TestExtensionSystem*>(ExtensionSystem::Get(&profile_));
+      extension_service_ = extension_system->CreateExtensionService(
+          CommandLine::ForCurrentProcess(), FilePath(), false);
+    }
+    return extension_service_;
+  }
+
+  scoped_refptr<Extension> MakeExtension(
+      const DictionaryValue& manifest_extra) {
+    scoped_ptr<base::DictionaryValue> manifest = DictionaryBuilder()
+        .Set("name", "Extension")
+        .Set("version", "1.0")
+        .Set("manifest_version", 2)
+        .Build();
+    manifest->MergeDictionary(&manifest_extra);
+
+    scoped_refptr<Extension> result =
+        ExtensionBuilder().SetManifest(manifest.Pass()).Build();
+    GetExtensionService()->AddExtension(result);
+    return result;
+  }
+
+  scoped_ptr<content::WebContents> MakeTab() {
+    scoped_ptr<content::WebContents> contents(
+        content::WebContentsTester::CreateTestWebContents(&profile_, NULL));
+    // Create a tab id.
+    SessionTabHelper::CreateForWebContents(contents.get());
+    return contents.Pass();
+  }
+
+ private:
+  MessageLoopForUI loop_;
+  content::TestBrowserThread ui_thread_;
+#if defined(OS_WIN)
+  ui::ScopedOleInitializer ole_initializer_;
+#endif
+  TestingProfile profile_;
+  ExtensionService* extension_service_;
+};
+
+TEST(DeclarativeContentActionTest, InvalidCreation) {
+  TestExtensionEnvironment env;
+  std::string error;
+  bool bad_message = false;
+  scoped_ptr<ContentAction> result;
+
+  // Test wrong data type passed.
+  error.clear();
+  result = ContentAction::Create(*ParseJson("[]"), &error, &bad_message);
+  EXPECT_TRUE(bad_message);
+  EXPECT_EQ("", error);
+  EXPECT_FALSE(result);
+
+  // Test missing instanceType element.
+  error.clear();
+  result = ContentAction::Create(*ParseJson("{}"), &error, &bad_message);
+  EXPECT_TRUE(bad_message);
+  EXPECT_EQ("", error);
+  EXPECT_FALSE(result);
+
+  // Test wrong instanceType element.
+  error.clear();
+  result = ContentAction::Create(*ParseJson(
+      "{\n"
+      "  \"instanceType\": \"declarativeContent.UnknownType\",\n"
+      "}"),
+                                 &error, &bad_message);
+  EXPECT_THAT(error, HasSubstr("invalid instanceType"));
+  EXPECT_FALSE(result);
+}
+
+TEST(DeclarativeContentActionTest, ShowPageAction) {
+  TestExtensionEnvironment env;
+
+  std::string error;
+  bool bad_message = false;
+  scoped_ptr<ContentAction> result = ContentAction::Create(
+      *ParseJson("{\n"
+                 "  \"instanceType\": \"declarativeContent.ShowPageAction\",\n"
+                 "}"),
+      &error, &bad_message);
+  EXPECT_EQ("", error);
+  EXPECT_FALSE(bad_message);
+  ASSERT_TRUE(result);
+  EXPECT_EQ(ContentAction::ACTION_SHOW_PAGE_ACTION, result->GetType());
+
+  scoped_refptr<Extension> extension = env.MakeExtension(
+      *DictionaryBuilder().Set("page_action", DictionaryBuilder()
+                               .Set("default_title", "Extension"))
+      .Build());
+  ExtensionAction* page_action =
+      ExtensionActionManager::Get(env.profile())->GetPageAction(*extension);
+  scoped_ptr<content::WebContents> contents = env.MakeTab();
+  const int tab_id = ExtensionTabUtil::GetTabId(contents.get());
+  EXPECT_FALSE(page_action->GetIsVisible(tab_id));
+  ContentAction::ApplyInfo apply_info = {
+    env.profile(), contents.get()
+  };
+  result->Apply(extension->id(), base::Time(), &apply_info);
+  EXPECT_TRUE(page_action->GetIsVisible(tab_id));
+  result->Apply(extension->id(), base::Time(), &apply_info);
+  EXPECT_TRUE(page_action->GetIsVisible(tab_id));
+  result->Revert(extension->id(), base::Time(), &apply_info);
+  EXPECT_TRUE(page_action->GetIsVisible(tab_id));
+  result->Revert(extension->id(), base::Time(), &apply_info);
+  EXPECT_FALSE(page_action->GetIsVisible(tab_id));
+}
+
+}  // namespace
+}  // namespace extensions
diff --git a/chrome/browser/extensions/api/declarative_content/content_condition.cc b/chrome/browser/extensions/api/declarative_content/content_condition.cc
new file mode 100644
index 0000000..8ae49e5
--- /dev/null
+++ b/chrome/browser/extensions/api/declarative_content/content_condition.cc
@@ -0,0 +1,140 @@
+// Copyright (c) 2012 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/api/declarative_content/content_condition.h"
+
+#include "base/stringprintf.h"
+#include "base/values.h"
+#include "chrome/browser/extensions/api/declarative_content/content_constants.h"
+#include "chrome/common/extensions/matcher/url_matcher.h"
+#include "chrome/common/extensions/matcher/url_matcher_factory.h"
+
+namespace keys = extensions::declarative_content_constants;
+
+namespace {
+static extensions::URLMatcherConditionSet::ID g_next_id = 0;
+
+// TODO(jyasskin): improve error messaging to give more meaningful messages
+// to the extension developer.
+// Error messages:
+const char kExpectedDictionary[] = "A condition has to be a dictionary.";
+const char kConditionWithoutInstanceType[] = "A condition had no instanceType";
+const char kExpectedOtherConditionType[] = "Expected a condition of type "
+    "declarativeContent.PageStateMatcher";
+const char kUnknownConditionAttribute[] = "Unknown condition attribute '%s'";
+const char kInvalidTypeOfParamter[] = "Attribute '%s' has an invalid type";
+}  // namespace
+
+namespace extensions {
+
+namespace keys = declarative_content_constants;
+
+RendererContentMatchData::RendererContentMatchData() {}
+RendererContentMatchData::~RendererContentMatchData() {}
+
+//
+// ContentCondition
+//
+
+ContentCondition::ContentCondition(
+    scoped_refptr<URLMatcherConditionSet> url_matcher_conditions,
+    const std::vector<std::string>& css_selectors)
+    : url_matcher_conditions_(url_matcher_conditions),
+      css_selectors_(css_selectors) {
+  CHECK(url_matcher_conditions.get());
+}
+
+ContentCondition::~ContentCondition() {}
+
+bool ContentCondition::IsFulfilled(
+    const RendererContentMatchData& renderer_data) const {
+  if (!ContainsKey(renderer_data.page_url_matches,
+                   url_matcher_conditions_->id()))
+    return false;
+
+  // All attributes must be fulfilled for a fulfilled condition.
+  for (std::vector<std::string>::const_iterator i =
+       css_selectors_.begin(); i != css_selectors_.end(); ++i) {
+    if (!ContainsKey(renderer_data.css_selectors, *i))
+      return false;
+  }
+  return true;
+}
+
+// static
+scoped_ptr<ContentCondition> ContentCondition::Create(
+    URLMatcherConditionFactory* url_matcher_condition_factory,
+    const base::Value& condition,
+    std::string* error) {
+  const base::DictionaryValue* condition_dict = NULL;
+  if (!condition.GetAsDictionary(&condition_dict)) {
+    *error = kExpectedDictionary;
+    return scoped_ptr<ContentCondition>(NULL);
+  }
+
+  // Verify that we are dealing with a Condition whose type we understand.
+  std::string instance_type;
+  if (!condition_dict->GetString(keys::kInstanceType, &instance_type)) {
+    *error = kConditionWithoutInstanceType;
+    return scoped_ptr<ContentCondition>(NULL);
+  }
+  if (instance_type != keys::kPageStateMatcherType) {
+    *error = kExpectedOtherConditionType;
+    return scoped_ptr<ContentCondition>(NULL);
+  }
+
+  scoped_refptr<URLMatcherConditionSet> url_matcher_condition_set;
+  std::vector<std::string> css_rules;
+
+  for (base::DictionaryValue::Iterator iter(*condition_dict);
+       iter.HasNext(); iter.Advance()) {
+    const std::string& condition_attribute_name = iter.key();
+    const Value& condition_attribute_value = iter.value();
+    if (condition_attribute_name == keys::kInstanceType) {
+      // Skip this.
+    } else if (condition_attribute_name == keys::kPageUrl) {
+      const base::DictionaryValue* dict = NULL;
+      if (!condition_attribute_value.GetAsDictionary(&dict)) {
+        *error = base::StringPrintf(kInvalidTypeOfParamter,
+                                    condition_attribute_name.c_str());
+      } else {
+        url_matcher_condition_set =
+            URLMatcherFactory::CreateFromURLFilterDictionary(
+                url_matcher_condition_factory, dict, ++g_next_id, error);
+      }
+    } else if (condition_attribute_name == keys::kCss) {
+      const base::ListValue* css_rules_value = NULL;
+      if (!condition_attribute_value.GetAsList(&css_rules_value)) {
+        *error = base::StringPrintf(kInvalidTypeOfParamter,
+                                    condition_attribute_name.c_str());
+      }
+      for (size_t i = 0; i < css_rules_value->GetSize(); ++i) {
+        std::string css_rule;
+        if (!css_rules_value->GetString(i, &css_rule)) {
+          *error = base::StringPrintf(kInvalidTypeOfParamter,
+                                      condition_attribute_name.c_str());
+          break;
+        }
+        css_rules.push_back(css_rule);
+      }
+    } else {
+      *error = base::StringPrintf(kUnknownConditionAttribute,
+                                  condition_attribute_name.c_str());
+    }
+    if (!error->empty())
+      return scoped_ptr<ContentCondition>(NULL);
+  }
+
+  if (!url_matcher_condition_set) {
+    URLMatcherConditionSet::Conditions url_matcher_conditions;
+    url_matcher_conditions.insert(
+        url_matcher_condition_factory->CreateHostPrefixCondition(""));
+    url_matcher_condition_set =
+        new URLMatcherConditionSet(++g_next_id, url_matcher_conditions);
+  }
+  return scoped_ptr<ContentCondition>(
+      new ContentCondition(url_matcher_condition_set, css_rules));
+}
+
+}  // namespace extensions
diff --git a/chrome/browser/extensions/api/declarative_content/content_condition.h b/chrome/browser/extensions/api/declarative_content/content_condition.h
new file mode 100644
index 0000000..c07afdbe
--- /dev/null
+++ b/chrome/browser/extensions/api/declarative_content/content_condition.h
@@ -0,0 +1,110 @@
+// Copyright (c) 2012 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_API_DECLARATIVE_CONTENT_CONTENT_CONDITION_H_
+#define CHROME_BROWSER_EXTENSIONS_API_DECLARATIVE_CONTENT_CONTENT_CONDITION_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/hash_tables.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "chrome/browser/extensions/api/declarative/declarative_rule.h"
+#include "chrome/common/extensions/matcher/url_matcher.h"
+#include "googleurl/src/gurl.h"
+
+namespace extensions {
+
+struct RendererContentMatchData {
+  RendererContentMatchData();
+  ~RendererContentMatchData();
+  // Match IDs for the URL of the top-level page the renderer is
+  // returning data for.
+  std::set<URLMatcherConditionSet::ID> page_url_matches;
+  // All watched CSS selectors that match on frames with the same
+  // origin as the page's main frame.
+  base::hash_set<std::string> css_selectors;
+};
+
+// Representation of a condition in the Declarative Content API. A condition
+// consists of an optional URL and a list of CSS selectors. Each of these
+// attributes needs to be fulfilled in order for the condition to be fulfilled.
+//
+// We distinguish between two types of attributes:
+// - URL Matcher attributes are attributes that test the URL of a page.
+//   These are treated separately because we use a URLMatcher to efficiently
+//   test many of these attributes in parallel by using some advanced
+//   data structures. The URLMatcher tells us if all URL Matcher attributes
+//   are fulfilled for a ContentCondition.
+// - All other conditions are represented individually as fields in
+//   ContentCondition.  These conditions are probed linearly (only if the URL
+//   Matcher found a hit).
+//
+// TODO(battre) Consider making the URLMatcher an owner of the
+// URLMatcherConditionSet and only pass a pointer to URLMatcherConditionSet
+// in url_matcher_condition_set(). This saves some copying in
+// ContentConditionSet::GetURLMatcherConditionSets.
+class ContentCondition {
+ public:
+  typedef RendererContentMatchData MatchData;
+
+  ContentCondition(
+      scoped_refptr<URLMatcherConditionSet> url_matcher_conditions,
+      const std::vector<std::string>& css_selectors);
+  ~ContentCondition();
+
+  // Factory method that instantiates a ContentCondition according to the
+  // description |condition| passed by the extension API.  |condition| should be
+  // an instance of declarativeContent.PageStateMatcher.
+  static scoped_ptr<ContentCondition> Create(
+      URLMatcherConditionFactory* url_matcher_condition_factory,
+      const base::Value& condition,
+      std::string* error);
+
+  // Returns whether the request is a match, given that the URLMatcher found
+  // a match for |url_matcher_conditions_|.
+  bool IsFulfilled(const RendererContentMatchData& renderer_data) const;
+
+  // Returns a URLMatcherConditionSet::ID which is the canonical representation
+  // for all URL patterns that need to be matched by this ContentCondition.
+  // This ID is registered in a URLMatcher that can inform us in case of a
+  // match.
+  URLMatcherConditionSet::ID url_matcher_condition_set_id() const {
+    return url_matcher_conditions_->id();
+  }
+
+  // If this Condition has a url filter, appends it to |condition_sets|.
+  void GetURLMatcherConditionSets(
+      URLMatcherConditionSet::Vector* condition_sets) const {
+    if (url_matcher_conditions_)
+      condition_sets->push_back(url_matcher_conditions_);
+  }
+
+  // True if GetURLMatcherConditionSets would append anything to its
+  // argument.
+  bool has_url_matcher_condition_set() const {
+    return url_matcher_conditions_ != NULL;
+  }
+
+  // Returns the CSS selectors required to match by this condition.
+  const std::vector<std::string>& css_selectors() const {
+    return css_selectors_;
+  }
+
+ private:
+  scoped_refptr<URLMatcherConditionSet> url_matcher_conditions_;
+  std::vector<std::string> css_selectors_;
+
+  DISALLOW_COPY_AND_ASSIGN(ContentCondition);
+};
+
+typedef DeclarativeConditionSet<ContentCondition> ContentConditionSet;
+
+}  // namespace extensions
+
+#endif  // CHROME_BROWSER_EXTENSIONS_API_DECLARATIVE_CONTENT_CONTENT_CONDITION_H_
diff --git a/chrome/browser/extensions/api/declarative_content/content_condition_unittest.cc b/chrome/browser/extensions/api/declarative_content/content_condition_unittest.cc
new file mode 100644
index 0000000..a26bf4c
--- /dev/null
+++ b/chrome/browser/extensions/api/declarative_content/content_condition_unittest.cc
@@ -0,0 +1,97 @@
+// Copyright (c) 2012 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/api/declarative_content/content_condition.h"
+
+#include <set>
+
+#include "base/message_loop.h"
+#include "base/test/values_test_util.h"
+#include "base/values.h"
+#include "chrome/browser/extensions/api/declarative_content/content_constants.h"
+#include "chrome/common/extensions/matcher/url_matcher.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+namespace {
+
+using testing::ElementsAre;
+using testing::HasSubstr;
+
+TEST(DeclarativeContentConditionTest, ErroneousCondition) {
+  URLMatcher matcher;
+
+  std::string error;
+  scoped_ptr<ContentCondition> result;
+
+  // Test unknown condition name passed.
+  error.clear();
+  result = ContentCondition::Create(
+      matcher.condition_factory(),
+      *base::test::ParseJson(
+          "{\n"
+          "  \"invalid\": \"foobar\",\n"
+          "  \"instanceType\": \"declarativeContent.PageStateMatcher\",\n"
+          "}"),
+      &error);
+  EXPECT_THAT(error, HasSubstr("Unknown condition attribute"));
+  EXPECT_FALSE(result);
+
+  // Test wrong datatype in pageUrl.
+  error.clear();
+  result = ContentCondition::Create(
+      matcher.condition_factory(),
+      *base::test::ParseJson(
+          "{\n"
+          "  \"pageUrl\": [],\n"
+          "  \"instanceType\": \"declarativeContent.PageStateMatcher\",\n"
+          "}"),
+      &error);
+  EXPECT_THAT(error, HasSubstr("invalid type"));
+  EXPECT_FALSE(result);
+
+  EXPECT_TRUE(matcher.IsEmpty()) << "Errors shouldn't add URL conditions";
+}
+
+TEST(DeclarativeContentConditionTest, ConditionWithUrlAndCss) {
+  URLMatcher matcher;
+
+  std::string error;
+  scoped_ptr<ContentCondition> result = ContentCondition::Create(
+      matcher.condition_factory(),
+      *base::test::ParseJson(
+          "{\n"
+          "  \"instanceType\": \"declarativeContent.PageStateMatcher\",\n"
+          "  \"pageUrl\": {\"hostSuffix\": \"example.com\"},\n"
+          "  \"css\": [\"input\"],\n"
+          "}"),
+      &error);
+  EXPECT_EQ("", error);
+  ASSERT_TRUE(result);
+
+  URLMatcherConditionSet::Vector all_new_condition_sets;
+  result->GetURLMatcherConditionSets(&all_new_condition_sets);
+  matcher.AddConditionSets(all_new_condition_sets);
+  EXPECT_FALSE(matcher.IsEmpty());
+
+  RendererContentMatchData match_data;
+  match_data.css_selectors.insert("input");
+
+  EXPECT_THAT(matcher.MatchURL(GURL("http://google.com/")),
+              ElementsAre(/*empty*/));
+  match_data.page_url_matches = matcher.MatchURL(
+      GURL("http://www.example.com/foobar"));
+  EXPECT_THAT(match_data.page_url_matches,
+              ElementsAre(result->url_matcher_condition_set_id()));
+
+  EXPECT_TRUE(result->IsFulfilled(match_data));
+
+  match_data.css_selectors.clear();
+  match_data.css_selectors.insert("body");
+  EXPECT_FALSE(result->IsFulfilled(match_data));
+}
+
+}  // namespace
+}  // namespace extensions
diff --git a/chrome/browser/extensions/api/declarative_content/content_constants.cc b/chrome/browser/extensions/api/declarative_content/content_constants.cc
new file mode 100644
index 0000000..dda4f93b
--- /dev/null
+++ b/chrome/browser/extensions/api/declarative_content/content_constants.cc
@@ -0,0 +1,23 @@
+// Copyright (c) 2012 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/api/declarative_content/content_constants.h"
+
+namespace extensions {
+namespace declarative_content_constants {
+
+// Signals to which ContentRulesRegistries are registered.
+const char kOnPageChanged[] = "declarativeContent.onPageChanged";
+
+// Keys of dictionaries.
+const char kCss[] = "css";
+const char kInstanceType[] = "instanceType";
+const char kPageUrl[] = "pageUrl";
+
+// Values of dictionaries, in particular instance types
+const char kPageStateMatcherType[] = "declarativeContent.PageStateMatcher";
+const char kShowPageAction[] = "declarativeContent.ShowPageAction";
+
+}  // namespace declarative_content_constants
+}  // namespace extensions
diff --git a/chrome/browser/extensions/api/declarative_content/content_constants.h b/chrome/browser/extensions/api/declarative_content/content_constants.h
new file mode 100644
index 0000000..cae250b
--- /dev/null
+++ b/chrome/browser/extensions/api/declarative_content/content_constants.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2012 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.
+
+// Constants used for the declarativeContent API.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_API_DECLARATIVE_CONTENT_CONTENT_CONSTANTS_H_
+#define CHROME_BROWSER_EXTENSIONS_API_DECLARATIVE_CONTENT_CONTENT_CONSTANTS_H_
+
+namespace extensions {
+namespace declarative_content_constants {
+
+// Signals to which ContentRulesRegistries are registered.
+extern const char kOnPageChanged[];
+
+// Keys of dictionaries.
+extern const char kCss[];
+extern const char kInstanceType[];
+extern const char kPageUrl[];
+
+// Values of dictionaries, in particular instance types
+extern const char kPageStateMatcherType[];
+extern const char kShowPageAction[];
+
+}  // namespace declarative_content_constants
+}  // namespace extensions
+
+#endif  // CHROME_BROWSER_EXTENSIONS_API_DECLARATIVE_CONTENT_CONTENT_CONSTANTS_H_
diff --git a/chrome/browser/extensions/api/declarative_content/content_rules_registry.cc b/chrome/browser/extensions/api/declarative_content/content_rules_registry.cc
new file mode 100644
index 0000000..53bae93
--- /dev/null
+++ b/chrome/browser/extensions/api/declarative_content/content_rules_registry.cc
@@ -0,0 +1,276 @@
+// Copyright (c) 2012 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/api/declarative_content/content_rules_registry.h"
+
+#include "chrome/browser/extensions/api/declarative_content/content_action.h"
+#include "chrome/browser/extensions/api/declarative_content/content_condition.h"
+#include "chrome/browser/extensions/extension_system.h"
+#include "chrome/browser/extensions/extension_tab_util.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/extensions/extension_messages.h"
+#include "content/public/browser/navigation_details.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/web_contents.h"
+
+namespace extensions {
+
+ContentRulesRegistry::ContentRulesRegistry(Profile* profile, Delegate* delegate)
+    : RulesRegistryWithCache(delegate),
+      profile_(profile) {
+  extension_info_map_ = ExtensionSystem::Get(profile)->info_map();
+
+  registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CREATED,
+                 content::NotificationService::AllBrowserContextsAndSources());
+}
+
+void ContentRulesRegistry::Observe(
+    int type,
+    const content::NotificationSource& source,
+    const content::NotificationDetails& details) {
+  switch (type) {
+    case content::NOTIFICATION_RENDERER_PROCESS_CREATED:
+      content::RenderProcessHost* process =
+          content::Source<content::RenderProcessHost>(source).ptr();
+      if (process->GetBrowserContext() == profile_)
+        InstructRenderProcess(process);
+      break;
+  }
+}
+
+void ContentRulesRegistry::Apply(
+    content::WebContents* contents,
+    const std::vector<std::string>& matching_css_selectors) {
+  const int tab_id = ExtensionTabUtil::GetTabId(contents);
+  RendererContentMatchData renderer_data;
+  renderer_data.page_url_matches = url_matcher_.MatchURL(contents->GetURL());
+  renderer_data.css_selectors.insert(matching_css_selectors.begin(),
+                                     matching_css_selectors.end());
+  std::set<ContentRule*> matching_rules = GetMatches(renderer_data);
+  std::set<ContentRule*>& prev_matching_rules = active_rules_[tab_id];
+  ContentAction::ApplyInfo apply_info = {
+    profile_, contents
+  };
+  for (std::set<ContentRule*>::const_iterator it = matching_rules.begin();
+       it != matching_rules.end(); ++it) {
+    if (!ContainsKey(prev_matching_rules, *it))
+      (*it)->actions().Apply((*it)->extension_id(), base::Time(), &apply_info);
+  }
+  for (std::set<ContentRule*>::const_iterator it = prev_matching_rules.begin();
+       it != prev_matching_rules.end(); ++it) {
+    if (!ContainsKey(matching_rules, *it))
+      (*it)->actions().Revert((*it)->extension_id(), base::Time(), &apply_info);
+  }
+  swap(matching_rules, prev_matching_rules);
+}
+
+void ContentRulesRegistry::DidNavigateMainFrame(
+    content::WebContents* contents,
+    const content::LoadCommittedDetails& details,
+    const content::FrameNavigateParams& params) {
+  if (details.is_in_page) {
+    // Within-page navigations don't change the set of elements that
+    // exist, and we only support filtering on the top-level URL, so
+    // this can't change which rules match.
+    return;
+  }
+
+  // Top-level navigation produces a new document. Initially, the
+  // document's empty, so no CSS rules match.  The renderer will send
+  // an ExtensionHostMsg_OnWatchedPageChange later if any CSS rules
+  // match.
+  std::vector<std::string> no_css_selectors;
+  Apply(contents, no_css_selectors);
+}
+
+std::set<ContentRule*>
+ContentRulesRegistry::GetMatches(
+    const RendererContentMatchData& renderer_data) const {
+  std::set<ContentRule*> result;
+
+  // Then we need to check for each of these, whether the other
+  // attributes are also fulfilled.
+  for (std::set<URLMatcherConditionSet::ID>::iterator
+           url_match = renderer_data.page_url_matches.begin();
+       url_match != renderer_data.page_url_matches.end(); ++url_match) {
+    URLMatcherIdToRule::const_iterator rule_iter =
+        match_id_to_rule_.find(*url_match);
+    CHECK(rule_iter != match_id_to_rule_.end());
+
+    ContentRule* rule = rule_iter->second;
+    if (rule->conditions().IsFulfilled(*url_match, renderer_data))
+      result.insert(rule);
+  }
+  return result;
+}
+
+std::string ContentRulesRegistry::AddRulesImpl(
+    const std::string& extension_id,
+    const std::vector<linked_ptr<RulesRegistry::Rule> >& rules) {
+  base::Time extension_installation_time =
+      GetExtensionInstallationTime(extension_id);
+
+  std::string error;
+  RulesMap new_content_rules;
+
+  for (std::vector<linked_ptr<RulesRegistry::Rule> >::const_iterator rule =
+       rules.begin(); rule != rules.end(); ++rule) {
+    ContentRule::GlobalRuleId rule_id(extension_id, *(*rule)->id);
+    DCHECK(content_rules_.find(rule_id) == content_rules_.end());
+
+    scoped_ptr<ContentRule> content_rule(
+        ContentRule::Create(url_matcher_.condition_factory(), extension_id,
+                            extension_installation_time, *rule, NULL, &error));
+    if (!error.empty()) {
+      // Clean up temporary condition sets created during rule creation.
+      url_matcher_.ClearUnusedConditionSets();
+      return error;
+    }
+    DCHECK(content_rule);
+
+    new_content_rules[rule_id] = make_linked_ptr(content_rule.release());
+  }
+
+  // Wohoo, everything worked fine.
+  content_rules_.insert(new_content_rules.begin(), new_content_rules.end());
+
+  // Create the triggers.
+  for (RulesMap::iterator i = new_content_rules.begin();
+       i != new_content_rules.end(); ++i) {
+    URLMatcherConditionSet::Vector url_condition_sets;
+    const ContentConditionSet& conditions = i->second->conditions();
+    conditions.GetURLMatcherConditionSets(&url_condition_sets);
+    for (URLMatcherConditionSet::Vector::iterator j =
+         url_condition_sets.begin(); j != url_condition_sets.end(); ++j) {
+      match_id_to_rule_[(*j)->id()] = i->second.get();
+    }
+  }
+
+  // Register url patterns in url_matcher_.
+  URLMatcherConditionSet::Vector all_new_condition_sets;
+  for (RulesMap::iterator i = new_content_rules.begin();
+       i != new_content_rules.end(); ++i) {
+    i->second->conditions().GetURLMatcherConditionSets(&all_new_condition_sets);
+  }
+  url_matcher_.AddConditionSets(all_new_condition_sets);
+
+  UpdateConditionCache();
+
+  return "";
+}
+
+std::string ContentRulesRegistry::RemoveRulesImpl(
+    const std::string& extension_id,
+    const std::vector<std::string>& rule_identifiers) {
+  // URLMatcherConditionSet IDs that can be removed from URLMatcher.
+  std::vector<URLMatcherConditionSet::ID> remove_from_url_matcher;
+
+  for (std::vector<std::string>::const_iterator i = rule_identifiers.begin();
+       i != rule_identifiers.end(); ++i) {
+    ContentRule::GlobalRuleId rule_id(extension_id, *i);
+
+    // Skip unknown rules.
+    RulesMap::iterator content_rules_entry = content_rules_.find(rule_id);
+    if (content_rules_entry == content_rules_.end())
+      continue;
+
+    // Remove all triggers but collect their IDs.
+    URLMatcherConditionSet::Vector condition_sets;
+    ContentRule* rule = content_rules_entry->second.get();
+    rule->conditions().GetURLMatcherConditionSets(&condition_sets);
+    for (URLMatcherConditionSet::Vector::iterator j = condition_sets.begin();
+         j != condition_sets.end(); ++j) {
+      remove_from_url_matcher.push_back((*j)->id());
+      match_id_to_rule_.erase((*j)->id());
+    }
+
+    // Remove the ContentRule from active_rules_.
+    for (std::map<int, std::set<ContentRule*> >::iterator
+             it = active_rules_.begin();
+         it != active_rules_.end(); ++it) {
+      // Has no effect if the rule wasn't present.
+      it->second.erase(rule);
+    }
+
+    // Remove reference to actual rule.
+    content_rules_.erase(content_rules_entry);
+  }
+
+  // Clear URLMatcher based on condition_set_ids that are not needed any more.
+  url_matcher_.RemoveConditionSets(remove_from_url_matcher);
+
+  UpdateConditionCache();
+
+  return "";
+}
+
+std::string ContentRulesRegistry::RemoveAllRulesImpl(
+    const std::string& extension_id) {
+  // Search all identifiers of rules that belong to extension |extension_id|.
+  std::vector<std::string> rule_identifiers;
+  for (RulesMap::iterator i = content_rules_.begin();
+       i != content_rules_.end(); ++i) {
+    const ContentRule::GlobalRuleId& global_rule_id = i->first;
+    if (global_rule_id.first == extension_id)
+      rule_identifiers.push_back(global_rule_id.second);
+  }
+
+  return RemoveRulesImpl(extension_id, rule_identifiers);
+}
+
+void ContentRulesRegistry::UpdateConditionCache() {
+  std::set<std::string> css_selectors;  // We rely on this being sorted.
+  for (RulesMap::const_iterator i = content_rules_.begin();
+       i != content_rules_.end(); ++i) {
+    ContentRule& rule = *i->second;
+    for (ContentConditionSet::const_iterator
+             condition = rule.conditions().begin();
+         condition != rule.conditions().end(); ++condition) {
+      const std::vector<std::string>& condition_css_selectors =
+          (*condition)->css_selectors();
+      css_selectors.insert(condition_css_selectors.begin(),
+                           condition_css_selectors.end());
+    }
+  }
+
+  if (css_selectors.size() != watched_css_selectors_.size() ||
+      !std::equal(css_selectors.begin(), css_selectors.end(),
+                  watched_css_selectors_.begin())) {
+    watched_css_selectors_.assign(css_selectors.begin(), css_selectors.end());
+
+    for (content::RenderProcessHost::iterator it(
+             content::RenderProcessHost::AllHostsIterator());
+         !it.IsAtEnd(); it.Advance()) {
+      content::RenderProcessHost* process = it.GetCurrentValue();
+      if (process->GetBrowserContext() == profile_)
+        InstructRenderProcess(process);
+    }
+  }
+}
+
+void ContentRulesRegistry::InstructRenderProcess(
+    content::RenderProcessHost* process) {
+  process->Send(new ExtensionMsg_WatchPages(watched_css_selectors_));
+}
+
+content::BrowserThread::ID ContentRulesRegistry::GetOwnerThread() const {
+  return content::BrowserThread::UI;
+}
+
+bool ContentRulesRegistry::IsEmpty() const {
+  return match_id_to_rule_.empty() && content_rules_.empty() &&
+      url_matcher_.IsEmpty();
+}
+
+ContentRulesRegistry::~ContentRulesRegistry() {}
+
+base::Time ContentRulesRegistry::GetExtensionInstallationTime(
+    const std::string& extension_id) const {
+  if (!extension_info_map_.get())  // May be NULL during testing.
+    return base::Time();
+
+  return extension_info_map_->GetInstallTime(extension_id);
+}
+
+}  // namespace extensions
diff --git a/chrome/browser/extensions/api/declarative_content/content_rules_registry.h b/chrome/browser/extensions/api/declarative_content/content_rules_registry.h
new file mode 100644
index 0000000..16e06f7
--- /dev/null
+++ b/chrome/browser/extensions/api/declarative_content/content_rules_registry.h
@@ -0,0 +1,145 @@
+// Copyright (c) 2012 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_API_DECLARATIVE_CONTENT_CONTENT_RULES_REGISTRY_H_
+#define CHROME_BROWSER_EXTENSIONS_API_DECLARATIVE_CONTENT_CONTENT_RULES_REGISTRY_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/memory/linked_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "base/time.h"
+#include "chrome/browser/extensions/api/declarative/declarative_rule.h"
+#include "chrome/browser/extensions/api/declarative/rules_registry_with_cache.h"
+#include "chrome/browser/extensions/api/declarative_content/content_action.h"
+#include "chrome/browser/extensions/api/declarative_content/content_condition.h"
+#include "chrome/browser/extensions/extension_info_map.h"
+#include "chrome/common/extensions/matcher/url_matcher.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+
+class Profile;
+class ContentPermissions;
+
+namespace content {
+class RenderProcessHost;
+class WebContents;
+struct FrameNavigateParams;
+struct LoadCommittedDetails;
+}
+
+namespace extension_web_request_api_helpers {
+struct EventResponseDelta;
+}
+
+namespace net {
+class URLRequest;
+}
+
+namespace extensions {
+
+class RulesRegistryService;
+
+typedef DeclarativeRule<ContentCondition, ContentAction> ContentRule;
+
+// The ContentRulesRegistry is responsible for managing
+// the internal representation of rules for the Declarative Content API.
+//
+// Here is the high level overview of this functionality:
+//
+// RulesRegistry::Rule consists of Conditions and Actions, these are
+// represented as a ContentRule with ContentConditions and
+// ContentRuleActions.
+//
+// The evaluation of URL related condition attributes (host_suffix, path_prefix)
+// is delegated to a URLMatcher, because this is capable of evaluating many
+// of such URL related condition attributes in parallel.
+class ContentRulesRegistry : public RulesRegistryWithCache,
+                             public content::NotificationObserver {
+ public:
+  ContentRulesRegistry(Profile* profile, Delegate* delegate);
+
+  // Applies all content rules given an update (CSS match change or
+  // page navigation, for now) from the renderer.
+  void Apply(content::WebContents* contents,
+             const std::vector<std::string>& matching_css_selectors);
+
+  // Applies all content rules given that a tab was just navigated.
+  void DidNavigateMainFrame(content::WebContents* tab,
+                            const content::LoadCommittedDetails& details,
+                            const content::FrameNavigateParams& params);
+
+  // Implementation of RulesRegistryWithCache:
+  virtual std::string AddRulesImpl(
+      const std::string& extension_id,
+      const std::vector<linked_ptr<RulesRegistry::Rule> >& rules) OVERRIDE;
+  virtual std::string RemoveRulesImpl(
+      const std::string& extension_id,
+      const std::vector<std::string>& rule_identifiers) OVERRIDE;
+  virtual std::string RemoveAllRulesImpl(
+      const std::string& extension_id) OVERRIDE;
+  virtual content::BrowserThread::ID GetOwnerThread() const OVERRIDE;
+
+  // content::NotificationObserver implementation.
+  virtual void Observe(int type,
+                       const content::NotificationSource& source,
+                       const content::NotificationDetails& details) OVERRIDE;
+
+  // Returns true if this object retains no allocated data. Only for debugging.
+  bool IsEmpty() const;
+
+ protected:
+  virtual ~ContentRulesRegistry();
+
+  // Virtual for testing:
+  virtual base::Time GetExtensionInstallationTime(
+      const std::string& extension_id) const;
+
+ private:
+  std::set<ContentRule*>
+  GetMatches(const RendererContentMatchData& renderer_data) const;
+
+  // Scans the rules for the set of conditions they're watching.  If the set has
+  // changed, calls InstructRenderProcess() for each RenderProcessHost in the
+  // current profile.
+  void UpdateConditionCache();
+
+  // Tells a renderer what page attributes to watch for using an
+  // ExtensionMsg_WatchPages.
+  void InstructRenderProcess(content::RenderProcessHost* process);
+
+  typedef std::map<URLMatcherConditionSet::ID, ContentRule*> URLMatcherIdToRule;
+  typedef std::map<ContentRule::GlobalRuleId, linked_ptr<ContentRule> >
+      RulesMap;
+
+  Profile* const profile_;
+
+  // Map that tells us which ContentRules may match under the condition that
+  // the URLMatcherConditionSet::ID was returned by the |url_matcher_|.
+  URLMatcherIdToRule match_id_to_rule_;
+
+  RulesMap content_rules_;
+
+  // Maps tab_id to the set of rules that match on that tab.  This
+  // lets us call Revert as appropriate.
+  std::map<int, std::set<ContentRule*> > active_rules_;
+
+  // Matches URLs for the page_url condition.
+  URLMatcher url_matcher_;
+
+  // All CSS selectors any rule's conditions watch for.
+  std::vector<std::string> watched_css_selectors_;
+
+  // Manages our notification registrations.
+  content::NotificationRegistrar registrar_;
+
+  scoped_refptr<ExtensionInfoMap> extension_info_map_;
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_BROWSER_EXTENSIONS_API_DECLARATIVE_CONTENT_CONTENT_RULES_REGISTRY_H_
diff --git a/chrome/browser/extensions/api/declarative_content/declarative_content_apitest.cc b/chrome/browser/extensions/api/declarative_content/declarative_content_apitest.cc
new file mode 100644
index 0000000..813b6b9
--- /dev/null
+++ b/chrome/browser/extensions/api/declarative_content/declarative_content_apitest.cc
@@ -0,0 +1,72 @@
+// Copyright (c) 2012 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_action.h"
+#include "chrome/browser/extensions/extension_action_manager.h"
+#include "chrome/browser/extensions/extension_apitest.h"
+#include "chrome/browser/extensions/extension_tab_util.h"
+#include "chrome/browser/extensions/extension_test_message_listener.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/common/extensions/features/feature.h"
+#include "content/public/test/browser_test_utils.h"
+
+namespace extensions {
+namespace {
+
+class DeclarativeContentApiTest : public ExtensionApiTest {
+ public:
+  DeclarativeContentApiTest()
+      // Set the channel to "trunk" since declarativeContent is restricted
+      // to trunk.
+      : current_channel_(chrome::VersionInfo::CHANNEL_UNKNOWN) {
+  }
+  virtual ~DeclarativeContentApiTest() {}
+
+  extensions::Feature::ScopedCurrentChannel current_channel_;
+};
+
+IN_PROC_BROWSER_TEST_F(DeclarativeContentApiTest, Overview) {
+  ExtensionTestMessageListener ready("ready", true);
+  const Extension* extension =
+      LoadExtension(test_data_dir_.AppendASCII("declarative_content/overview"));
+  ASSERT_TRUE(extension);
+  const ExtensionAction* page_action =
+      ExtensionActionManager::Get(browser()->profile())->
+      GetPageAction(*extension);
+  ASSERT_TRUE(page_action);
+
+  ASSERT_TRUE(ready.WaitUntilSatisfied());
+  content::WebContents* const tab =
+      browser()->tab_strip_model()->GetWebContentsAt(0);
+  const int tab_id = ExtensionTabUtil::GetTabId(tab);
+
+  NavigateInRenderer(tab, GURL("http://test1/"));
+
+  // The declarative API should show the page action instantly, rather
+  // than waiting for the extension to run.
+  EXPECT_TRUE(page_action->GetIsVisible(tab_id));
+
+  // Make sure leaving a matching page unshows the page action.
+  NavigateInRenderer(tab, GURL("http://not_checked/"));
+  EXPECT_FALSE(page_action->GetIsVisible(tab_id));
+
+  // Insert a password field to make sure that's noticed.
+  ASSERT_TRUE(content::ExecuteScript(
+      tab, "document.body.innerHTML = '<input type=\"password\">';"));
+  // Give the mutation observer a chance to run and send back the
+  // matching-selector update.
+  ASSERT_TRUE(content::ExecuteScript(tab, ""));
+  EXPECT_TRUE(page_action->GetIsVisible(tab_id));
+
+  // Remove it again to make sure that reverts the action.
+  ASSERT_TRUE(content::ExecuteScript(
+      tab, "document.body.innerHTML = 'Hello world';"));
+  // Give the mutation observer a chance to run and send back the
+  // matching-selector update.
+  ASSERT_TRUE(content::ExecuteScript(tab, ""));
+  EXPECT_FALSE(page_action->GetIsVisible(tab_id));
+}
+
+}  // namespace
+}  // namespace extensions
diff --git a/chrome/browser/extensions/extension_action.cc b/chrome/browser/extensions/extension_action.cc
index c193f4c..1d7648a 100644
--- a/chrome/browser/extensions/extension_action.cc
+++ b/chrome/browser/extensions/extension_action.cc
@@ -272,6 +272,18 @@
   return true;
 }
 
+void ExtensionAction::DeclarativeShow(int tab_id) {
+  DCHECK_NE(tab_id, kDefaultTabId);
+  ++declarative_show_count_[tab_id];  // Use default initialization to 0.
+}
+
+void ExtensionAction::UndoDeclarativeShow(int tab_id) {
+  int& show_count = declarative_show_count_[tab_id];
+  DCHECK_GT(show_count, 0);
+  if (--show_count == 0)
+    declarative_show_count_.erase(tab_id);
+}
+
 void ExtensionAction::ClearAllValuesForTab(int tab_id) {
   popup_url_.erase(tab_id);
   title_.erase(tab_id);
@@ -280,6 +292,10 @@
   badge_text_color_.erase(tab_id);
   badge_background_color_.erase(tab_id);
   appearance_.erase(tab_id);
+  // TODO(jyasskin): Erase the element from declarative_show_count_
+  // when the tab's closed.  There's a race between the
+  // PageActionController and the ContentRulesRegistry on navigation,
+  // which prevents me from cleaning everything up now.
   icon_animation_.erase(tab_id);
 }
 
diff --git a/chrome/browser/extensions/extension_action.h b/chrome/browser/extensions/extension_action.h
index dacd961e..943a3eb 100644
--- a/chrome/browser/extensions/extension_action.h
+++ b/chrome/browser/extensions/extension_action.h
@@ -220,15 +220,20 @@
   // care of any appropriate transition animations.  Returns true if
   // the appearance has changed.
   bool SetAppearance(int tab_id, Appearance value);
+  // The declarative appearance overrides a default appearance but is overridden
+  // by an appearance set directly on the tab.
+  void DeclarativeShow(int tab_id);
+  void UndoDeclarativeShow(int tab_id);
+
   // Get the badge visibility for a tab, or the default badge visibility
   // if none was set.
   bool GetIsVisible(int tab_id) const {
-    return GetValue(&appearance_, tab_id) != INVISIBLE;
+    return GetAppearance(tab_id) != INVISIBLE;
   }
 
   // True if the tab's action wants the user's attention.
   bool WantsAttention(int tab_id) const {
-    return GetValue(&appearance_, tab_id) == WANTS_ATTENTION;
+    return GetAppearance(tab_id) == WANTS_ATTENTION;
   }
 
   // Remove all tab-specific state.
@@ -272,17 +277,47 @@
     (*map)[tab_id] = val;
   }
 
+  template<class Map>
+  static const typename Map::mapped_type* FindOrNull(
+      const Map* map,
+      const typename Map::key_type& key) {
+    typename Map::const_iterator iter = map->find(key);
+    if (iter == map->end())
+      return NULL;
+    return &iter->second;
+  }
+
   template<class T>
   T GetValue(const std::map<int, T>* map, int tab_id) const {
-    typename std::map<int, T>::const_iterator iter = map->find(tab_id);
-    if (iter != map->end()) {
-      return iter->second;
+    if (const T* tab_value = FindOrNull(map, tab_id)) {
+      return *tab_value;
+    } else if (const T* default_value = FindOrNull(map, kDefaultTabId)) {
+      return *default_value;
     } else {
-      iter = map->find(kDefaultTabId);
-      return iter != map->end() ? iter->second : ValueTraits<T>::CreateEmpty();
+      return ValueTraits<T>::CreateEmpty();
     }
   }
 
+  // Gets the appearance of |tab_id|.  Returns the first of: a specific
+  // appearance set on the tab; a declarative appearance set on the tab; the
+  // default appearance set for all tabs; or INVISIBLE.  Don't return this
+  // result to an extension's background page because the declarative state can
+  // leak information about hosts the extension doesn't have permission to
+  // access.
+  Appearance GetAppearance(int tab_id) const {
+    if (const Appearance* tab_appearance = FindOrNull(&appearance_, tab_id))
+      return *tab_appearance;
+
+    if (ContainsKey(declarative_show_count_, tab_id))
+      return ACTIVE;
+
+    if (const Appearance* default_appearance =
+        FindOrNull(&appearance_, kDefaultTabId))
+      return *default_appearance;
+
+    return INVISIBLE;
+  }
+
   // The id for the extension this action belongs to (as defined in the
   // extension manifest).
   const std::string extension_id_;
@@ -299,6 +334,17 @@
   std::map<int, SkColor> badge_text_color_;
   std::map<int, Appearance> appearance_;
 
+  // Declarative state exists for two reasons: First, we need to hide it from
+  // the extension's background/event page to avoid leaking data from hosts the
+  // extension doesn't have permission to access.  Second, the action's state
+  // gets both reset and given its declarative values in response to a
+  // WebContentsObserver::DidNavigateMainFrame event, and there's no way to set
+  // those up to be called in the right order.
+
+  // Maps tab_id to the number of active (applied-but-not-reverted)
+  // declarativeContent.ShowPageAction actions.
+  std::map<int, int> declarative_show_count_;
+
   // IconAnimations are destroyed by a delayed task on the UI message loop so
   // that even if the Extension and ExtensionAction are destroyed on a non-UI
   // thread, the animation will still only be touched from the UI thread.  This
diff --git a/chrome/browser/extensions/tab_helper.cc b/chrome/browser/extensions/tab_helper.cc
index 8fe31dd..7763f1a 100644
--- a/chrome/browser/extensions/tab_helper.cc
+++ b/chrome/browser/extensions/tab_helper.cc
@@ -5,11 +5,14 @@
 #include "chrome/browser/extensions/tab_helper.h"
 
 #include "chrome/browser/extensions/activity_log.h"
+#include "chrome/browser/extensions/api/declarative/rules_registry_service.h"
+#include "chrome/browser/extensions/api/declarative_content/content_rules_registry.h"
 #include "chrome/browser/extensions/app_notify_channel_ui.h"
 #include "chrome/browser/extensions/crx_installer.h"
 #include "chrome/browser/extensions/extension_action.h"
 #include "chrome/browser/extensions/extension_action_manager.h"
 #include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/extension_system.h"
 #include "chrome/browser/extensions/extension_tab_util.h"
 #include "chrome/browser/extensions/image_loader.h"
 #include "chrome/browser/extensions/page_action_controller.h"
@@ -84,6 +87,10 @@
       pending_web_app_action_(NONE),
       script_executor_(new ScriptExecutor(web_contents,
                                           &script_execution_observers_)),
+      rules_registry_service_(
+          ExtensionSystem::Get(
+              Profile::FromBrowserContext(web_contents->GetBrowserContext()))->
+          rules_registry_service()),
       ALLOW_THIS_IN_INITIALIZER_LIST(image_loader_ptr_factory_(this)) {
   // The ActiveTabPermissionManager requires a session ID; ensure this
   // WebContents has one.
@@ -188,6 +195,13 @@
 void TabHelper::DidNavigateMainFrame(
     const content::LoadCommittedDetails& details,
     const content::FrameNavigateParams& params) {
+#if defined(ENABLE_EXTENSIONS)
+  if (rules_registry_service_) {
+    rules_registry_service_->content_rules_registry()->DidNavigateMainFrame(
+        web_contents(), details, params);
+  }
+#endif  // defined(ENABLE_EXTENSIONS)
+
   if (details.is_in_page)
     return;
 
@@ -229,6 +243,8 @@
     IPC_MESSAGE_HANDLER(ExtensionHostMsg_Request, OnRequest)
     IPC_MESSAGE_HANDLER(ExtensionHostMsg_ContentScriptsExecuting,
                         OnContentScriptsExecuting)
+    IPC_MESSAGE_HANDLER(ExtensionHostMsg_OnWatchedPageChange,
+                        OnWatchedPageChange)
     IPC_MESSAGE_UNHANDLED(handled = false)
   IPC_END_MESSAGE_MAP()
   return handled;
@@ -417,6 +433,16 @@
                                       on_url));
 }
 
+void TabHelper::OnWatchedPageChange(
+    const std::vector<std::string>& css_selectors) {
+#if defined(ENABLE_EXTENSIONS)
+  if (rules_registry_service_) {
+    rules_registry_service_->content_rules_registry()->Apply(
+        web_contents(), css_selectors);
+  }
+#endif  // defined(ENABLE_EXTENSIONS)
+}
+
 const Extension* TabHelper::GetExtension(const std::string& extension_app_id) {
   if (extension_app_id.empty())
     return NULL;
diff --git a/chrome/browser/extensions/tab_helper.h b/chrome/browser/extensions/tab_helper.h
index 8e3e00e..539871f 100644
--- a/chrome/browser/extensions/tab_helper.h
+++ b/chrome/browser/extensions/tab_helper.h
@@ -7,6 +7,8 @@
 
 #include <map>
 #include <string>
+#include <vector>
+
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
@@ -30,6 +32,7 @@
 namespace extensions {
 class Extension;
 class LocationBarController;
+class RulesRegistryService;
 class ScriptBadgeController;
 class ScriptBubbleController;
 class ScriptExecutor;
@@ -192,6 +195,7 @@
       const ScriptExecutionObserver::ExecutingScriptsMap& extension_ids,
       int32 page_id,
       const GURL& on_url);
+  void OnWatchedPageChange(const std::vector<std::string>& css_selectors);
 
   // App extensions related methods:
 
@@ -259,6 +263,8 @@
 
   scoped_ptr<ScriptBubbleController> script_bubble_controller_;
 
+  RulesRegistryService* rules_registry_service_;
+
   Profile* profile_;
 
   // Vend weak pointers that can be invalidated to stop in-progress loads.
diff --git a/chrome/chrome_browser_extensions.gypi b/chrome/chrome_browser_extensions.gypi
index 9304ecd..64df276 100644
--- a/chrome/chrome_browser_extensions.gypi
+++ b/chrome/chrome_browser_extensions.gypi
@@ -150,6 +150,14 @@
         'browser/extensions/api/declarative/rules_registry_with_cache.h',
         'browser/extensions/api/declarative/test_rules_registry.cc',
         'browser/extensions/api/declarative/test_rules_registry.h',
+        'browser/extensions/api/declarative_content/content_action.cc',
+        'browser/extensions/api/declarative_content/content_action.h',
+        'browser/extensions/api/declarative_content/content_condition.cc',
+        'browser/extensions/api/declarative_content/content_condition.h',
+        'browser/extensions/api/declarative_content/content_constants.cc',
+        'browser/extensions/api/declarative_content/content_constants.h',
+        'browser/extensions/api/declarative_content/content_rules_registry.cc',
+        'browser/extensions/api/declarative_content/content_rules_registry.h',
         'browser/extensions/api/declarative_webrequest/request_stage.cc',
         'browser/extensions/api/declarative_webrequest/request_stage.h',
         'browser/extensions/api/declarative_webrequest/webrequest_action.cc',
diff --git a/chrome/chrome_renderer.gypi b/chrome/chrome_renderer.gypi
index 8be2427..fbd8fc5 100644
--- a/chrome/chrome_renderer.gypi
+++ b/chrome/chrome_renderer.gypi
@@ -71,6 +71,8 @@
         'renderer/extensions/chrome_v8_extension.h',
         'renderer/extensions/chrome_v8_extension_handler.cc',
         'renderer/extensions/chrome_v8_extension_handler.h',
+        'renderer/extensions/content_watcher.cc',
+        'renderer/extensions/content_watcher.h',
         'renderer/extensions/context_menus_custom_bindings.cc',
         'renderer/extensions/context_menus_custom_bindings.h',
         'renderer/extensions/dispatcher.cc',
@@ -145,7 +147,9 @@
         'renderer/resources/extensions/apitest.js',
         'renderer/resources/extensions/app_custom_bindings.js',
         'renderer/resources/extensions/browser_action_custom_bindings.js',
+        'renderer/resources/extensions/content_watcher.js',
         'renderer/resources/extensions/context_menus_custom_bindings.js',
+        'renderer/resources/extensions/declarative_content_custom_bindings.js',
         'renderer/resources/extensions/declarative_webrequest_custom_bindings.js',
         'renderer/resources/extensions/event.js',
         'renderer/resources/extensions/experimental.offscreenTabs_custom_bindings.js',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index 2f7a2f0..ec1146f 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -1040,6 +1040,7 @@
         'browser/extensions/api/cookies/cookies_apitest.cc',
         'browser/extensions/api/debugger/debugger_apitest.cc',
         'browser/extensions/api/declarative/declarative_apitest.cc',
+        'browser/extensions/api/declarative_content/declarative_content_apitest.cc',
         'browser/extensions/api/developer_private/developer_private_apitest.cc',
         'browser/extensions/api/dial/dial_apitest.cc',
         'browser/extensions/api/dns/dns_apitest.cc',
diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi
index 62ce4593..d5add6a 100644
--- a/chrome/chrome_tests_unit.gypi
+++ b/chrome/chrome_tests_unit.gypi
@@ -660,6 +660,8 @@
         'browser/extensions/api/declarative/initializing_rules_registry_unittest.cc',
         'browser/extensions/api/declarative/rules_registry_service_unittest.cc',
         'browser/extensions/api/declarative/rules_registry_with_cache_unittest.cc',
+        'browser/extensions/api/declarative_content/content_action_unittest.cc',
+        'browser/extensions/api/declarative_content/content_condition_unittest.cc',
         'browser/extensions/api/declarative_webrequest/webrequest_action_unittest.cc',
         'browser/extensions/api/declarative_webrequest/webrequest_condition_attribute_unittest.cc',
         'browser/extensions/api/declarative_webrequest/webrequest_condition_unittest.cc',
diff --git a/chrome/common/extensions/api/_permission_features.json b/chrome/common/extensions/api/_permission_features.json
index 270a959..427c883c 100644
--- a/chrome/common/extensions/api/_permission_features.json
+++ b/chrome/common/extensions/api/_permission_features.json
@@ -119,6 +119,10 @@
     "channel": "stable",
     "extension_types": ["extension", "packaged_app"]
   },
+  "declarativeContent": {
+    "channel": "trunk",
+    "extension_types": ["extension"]
+  },
   "declarativeWebRequest": {
     "channel": "beta",
     "extension_types": ["extension", "packaged_app"]
diff --git a/chrome/common/extensions/api/declarative_content.json b/chrome/common/extensions/api/declarative_content.json
new file mode 100644
index 0000000..13696f1
--- /dev/null
+++ b/chrome/common/extensions/api/declarative_content.json
@@ -0,0 +1,73 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "declarativeContent",
+    "documentation_permissions_required": ["declarative", "declarativeContent"],
+    "types": [
+      {
+        "id": "PageStateMatcher",
+        "type": "object",
+        "description": "Matches the state of a web page by various criteria.",
+        "properties": {
+          "pageUrl": {
+            "$ref": "events.UrlFilter",
+            "description": "Matches if the condition of the UrlFilter are fulfilled for the top-level URL of the page.",
+            "optional": true
+          },
+          "css": {
+            "type": "array",
+            "optional": true,
+            "description": "Matches if all of the CSS selectors in the array match in a frame with the same origin as the page's main frame.  Note that listing hundreds of CSS selectors here can slow down web sites.",
+            // TODO(jyasskin): Figure out if we want to require all
+            // the selectors to match in the same frame, or allow several
+            // frames to contribute to a match.
+            "items": { "type": "string" }
+            // TODO(jyasskin): Validate that the selectors in this
+            // array are valid. Otherwise, we can get exceptions from
+            // content_watcher.js:FindMatchingSelectors() long after the
+            // rule is registered.
+//          },
+//          TODO: "text": {
+//            "type": "array",
+//            "optional": true,
+//            "description": "Matches if all of the regular expressions in the array match text in the page. The regular expressions use the <a href=\"http://code.google.com/p/re2/wiki/Syntax\">RE2 syntax</a>.",
+//            "items": { "type": "string" }
+          },
+          "instanceType": {
+            "type": "string", "enum": ["declarativeContent.PageStateMatcher"],
+            "nodoc": true
+          }
+        }
+      },
+      {
+        "id": "ShowPageAction",
+        "description": "Declarative event action that shows the extension's page action while the corresponding conditions are met.",
+        "type": "object",
+        "properties": {
+          "instanceType": {
+            "type": "string", "enum": ["declarativeContent.ShowPageAction"],
+            "nodoc": true
+          }
+        }
+      }
+    ],
+    "functions": [
+    ],
+    "events": [
+      {
+        "name": "onPageChanged",
+        "options": {
+          "supportsListeners": false,
+          "supportsRules": true,
+          "conditions": ["declarativeContent.PageStateMatcher"],
+          "actions": [
+            "declarativeContent.ShowPageAction"
+          ]
+        }
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/extension_api.cc b/chrome/common/extensions/api/extension_api.cc
index 85207e7..a156608 100644
--- a/chrome/common/extensions/api/extension_api.cc
+++ b/chrome/common/extensions/api/extension_api.cc
@@ -354,6 +354,8 @@
       IDR_EXTENSION_API_JSON_CHROMEOSINFOPRIVATE));
   RegisterSchema("commands", ReadFromResource(
       IDR_EXTENSION_API_JSON_COMMANDS));
+  RegisterSchema("declarativeContent", ReadFromResource(
+      IDR_EXTENSION_API_JSON_DECLARATIVE_CONTENT));
   RegisterSchema("declarativeWebRequest", ReadFromResource(
       IDR_EXTENSION_API_JSON_DECLARATIVE_WEBREQUEST));
   RegisterSchema("devtools", ReadFromResource(
diff --git a/chrome/common/extensions/extension_messages.h b/chrome/common/extensions/extension_messages.h
index 7927ba56..6f084c47 100644
--- a/chrome/common/extensions/extension_messages.h
+++ b/chrome/common/extensions/extension_messages.h
@@ -5,6 +5,9 @@
 // IPC messages for extensions.
 // Multiply-included message file, hence no include guard.
 
+#include <string>
+#include <vector>
+
 #include "base/shared_memory.h"
 #include "base/values.h"
 #include "chrome/common/extensions/draggable_region.h"
@@ -397,6 +400,13 @@
 // Notify the renderer that its window has closed.
 IPC_MESSAGE_ROUTED0(ExtensionMsg_AppWindowClosed)
 
+// Notify the renderer that an extension wants notifications when certain
+// searches match the active page.  This message replaces the old set of
+// searches, and triggers ExtensionHostMsg_OnWatchedPageChange messages from
+// each tab to keep the browser updated about changes.
+IPC_MESSAGE_CONTROL1(ExtensionMsg_WatchPages,
+                     std::vector<std::string> /* CSS selectors */)
+
 // Messages sent from the renderer to the browser.
 
 // A renderer sends this message when an extension process starts an API
@@ -577,3 +587,14 @@
 // Sent by the renderer when the draggable regions are updated.
 IPC_MESSAGE_ROUTED1(ExtensionHostMsg_UpdateDraggableRegions,
                     std::vector<extensions::DraggableRegion> /* regions */)
+
+// Notifies the browser process that a tab has started or stopped matching
+// certain conditions.  This message is sent in response to several events:
+//
+// * ExtensionMsg_WatchPages was received, updating the set of conditions.
+// * A new page is loaded.  This will be sent after ViewHostMsg_FrameNavigate.
+//   Currently this only fires for the main frame.
+// * Something changed on an existing frame causing the set of matching searches
+//   to change.
+IPC_MESSAGE_ROUTED1(ExtensionHostMsg_OnWatchedPageChange,
+                    std::vector<std::string> /* Matching CSS selectors */)
diff --git a/chrome/common/extensions/permissions/api_permission.cc b/chrome/common/extensions/permissions/api_permission.cc
index c3dc35d8..30c9eea5 100644
--- a/chrome/common/extensions/permissions/api_permission.cc
+++ b/chrome/common/extensions/permissions/api_permission.cc
@@ -176,6 +176,7 @@
       IDS_EXTENSION_PROMPT_WARNING_CLIPBOARD,
       PermissionMessage::kClipboard },
     { APIPermission::kClipboardWrite, "clipboardWrite" },
+    { APIPermission::kDeclarativeContent, "declarativeContent" },
     { APIPermission::kDeclarativeWebRequest, "declarativeWebRequest" },
     { APIPermission::kDownloads, "downloads", kFlagNone,
       IDS_EXTENSION_PROMPT_WARNING_DOWNLOADS,
diff --git a/chrome/common/extensions/permissions/api_permission.h b/chrome/common/extensions/permissions/api_permission.h
index b7d1414..6d1d963 100644
--- a/chrome/common/extensions/permissions/api_permission.h
+++ b/chrome/common/extensions/permissions/api_permission.h
@@ -62,6 +62,7 @@
     kDial,
     kDebugger,
     kDeclarative,
+    kDeclarativeContent,
     kDeclarativeWebRequest,
     kDeveloperPrivate,
     kDevtools,
diff --git a/chrome/common/extensions/permissions/permission_set_unittest.cc b/chrome/common/extensions/permissions/permission_set_unittest.cc
index 46da7e0..b5a8ded 100644
--- a/chrome/common/extensions/permissions/permission_set_unittest.cc
+++ b/chrome/common/extensions/permissions/permission_set_unittest.cc
@@ -684,6 +684,7 @@
   skip.insert(APIPermission::kCookie);
 
   // These are warned as part of host permission checks.
+  skip.insert(APIPermission::kDeclarativeContent);
   skip.insert(APIPermission::kPageCapture);
   skip.insert(APIPermission::kProxy);
   skip.insert(APIPermission::kWebRequest);
diff --git a/chrome/common/extensions_api_resources.grd b/chrome/common/extensions_api_resources.grd
index 915a1c8..3c36b6b 100644
--- a/chrome/common/extensions_api_resources.grd
+++ b/chrome/common/extensions_api_resources.grd
@@ -15,6 +15,7 @@
       <include name="IDR_EXTENSION_API_JSON_BROWSINGDATA" file="extensions\api\browsing_data.json" type="BINDATA" />
       <include name="IDR_EXTENSION_API_JSON_CHROMEOSINFOPRIVATE" file="extensions\api\chromeos_info_private.json" type="BINDATA" />
       <include name="IDR_EXTENSION_API_JSON_COMMANDS" file="extensions\api\commands.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_DECLARATIVE_CONTENT" file="extensions\api\declarative_content.json" type="BINDATA" />
       <include name="IDR_EXTENSION_API_JSON_DECLARATIVE_WEBREQUEST" file="extensions\api\declarative_web_request.json" type="BINDATA" />
       <include name="IDR_EXTENSION_API_JSON_DEVTOOLS" file="extensions\api\devtools.json" type="BINDATA" />
       <include name="IDR_EXTENSION_API_JSON_ECHOPRIVATE" file="extensions\api\echo_private.json" type="BINDATA" />
diff --git a/chrome/renderer/extensions/content_watcher.cc b/chrome/renderer/extensions/content_watcher.cc
new file mode 100644
index 0000000..684787a
--- /dev/null
+++ b/chrome/renderer/extensions/content_watcher.cc
@@ -0,0 +1,209 @@
+// Copyright (c) 2012 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/common/extensions/extension_messages.h"
+#include "chrome/renderer/extensions/chrome_v8_context.h"
+#include "chrome/renderer/extensions/chrome_v8_extension.h"
+#include "chrome/renderer/extensions/content_watcher.h"
+#include "chrome/renderer/extensions/dispatcher.h"
+#include "content/public/renderer/render_view.h"
+#include "content/public/renderer/render_view_visitor.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h"
+
+namespace extensions {
+
+namespace {
+class MutationHandler : public ChromeV8Extension {
+ public:
+  explicit MutationHandler(Dispatcher* dispatcher,
+                           base::WeakPtr<ContentWatcher> content_watcher)
+      : ChromeV8Extension(dispatcher),
+        content_watcher_(content_watcher) {
+    RouteFunction("FrameMutated",
+                  base::Bind(&MutationHandler::FrameMutated,
+                             base::Unretained(this)));
+  }
+
+ private:
+  v8::Handle<v8::Value> FrameMutated(const v8::Arguments& args) {
+    if (content_watcher_) {
+      content_watcher_->ScanAndNotify(
+          WebKit::WebFrame::frameForCurrentContext());
+    }
+    return v8::Undefined();
+  }
+
+  base::WeakPtr<ContentWatcher> content_watcher_;
+};
+
+}  // namespace
+
+ContentWatcher::ContentWatcher(Dispatcher* dispatcher)
+    : weak_ptr_factory_(this),
+      dispatcher_(dispatcher) {}
+ContentWatcher::~ContentWatcher() {}
+
+scoped_ptr<NativeHandler> ContentWatcher::MakeNatives() {
+  return scoped_ptr<NativeHandler>(
+      new MutationHandler(dispatcher_, weak_ptr_factory_.GetWeakPtr()));
+}
+
+void ContentWatcher::OnWatchPages(
+    const std::vector<std::string>& new_css_selectors) {
+  if (new_css_selectors == css_selectors_)
+    return;
+
+  css_selectors_ = new_css_selectors;
+
+  for (std::map<WebKit::WebFrame*,
+                std::vector<base::StringPiece> >::iterator
+           it = matching_selectors_.begin();
+       it != matching_selectors_.end(); ++it) {
+    WebKit::WebFrame* frame = it->first;
+    if (!css_selectors_.empty())
+      EnsureWatchingMutations(frame);
+
+    // Make sure to replace the contents of it->second because it contains
+    // dangling StringPieces that referred into the old css_selectors_ content.
+    it->second = FindMatchingSelectors(frame);
+  }
+
+  // For each top-level frame, inform the browser about its new matching set of
+  // selectors.
+  struct NotifyVisitor : public content::RenderViewVisitor {
+    explicit NotifyVisitor(ContentWatcher* watcher) : watcher_(watcher) {}
+    virtual bool Visit(content::RenderView* view) {
+      watcher_->NotifyBrowserOfChange(view->GetWebView()->mainFrame());
+      return true;  // Continue visiting.
+    }
+    ContentWatcher* watcher_;
+  };
+  NotifyVisitor visitor(this);
+  content::RenderView::ForEach(&visitor);
+}
+
+void ContentWatcher::DidCreateDocumentElement(WebKit::WebFrame* frame) {
+  // Make sure the frame is represented in the matching_selectors_ map.
+  matching_selectors_[frame];
+
+  if (!css_selectors_.empty()) {
+    EnsureWatchingMutations(frame);
+  }
+}
+
+void ContentWatcher::EnsureWatchingMutations(WebKit::WebFrame* frame) {
+  v8::HandleScope scope;
+  v8::Context::Scope context_scope(frame->mainWorldScriptContext());
+  if (ModuleSystem* module_system = GetModuleSystem(frame)) {
+    ModuleSystem::NativesEnabledScope scope(module_system);
+    module_system->Require("contentWatcher");
+  }
+}
+
+ModuleSystem* ContentWatcher::GetModuleSystem(WebKit::WebFrame* frame) const {
+  ChromeV8Context* v8_context =
+      dispatcher_->v8_context_set().GetByV8Context(
+          frame->mainWorldScriptContext());
+
+  if (!v8_context)
+    return NULL;
+  return v8_context->module_system();
+}
+
+void ContentWatcher::ScanAndNotify(WebKit::WebFrame* frame) {
+  std::vector<base::StringPiece> new_matches = FindMatchingSelectors(frame);
+  std::vector<base::StringPiece>& old_matches = matching_selectors_[frame];
+  if (new_matches == old_matches)
+    return;
+
+  using std::swap;
+  swap(old_matches, new_matches);
+  NotifyBrowserOfChange(frame);
+}
+
+std::vector<base::StringPiece> ContentWatcher::FindMatchingSelectors(
+    WebKit::WebFrame* frame) const {
+  std::vector<base::StringPiece> result;
+  v8::HandleScope scope;
+
+  // Get the indices within |css_selectors_| that match elements in
+  // |frame|, as a JS Array.
+  v8::Local<v8::Value> selector_indices;
+  if (ModuleSystem* module_system = GetModuleSystem(frame)) {
+    v8::Context::Scope context_scope(frame->mainWorldScriptContext());
+    v8::Local<v8::Array> js_css_selectors =
+        v8::Array::New(css_selectors_.size());
+    for (size_t i = 0; i < css_selectors_.size(); ++i) {
+      js_css_selectors->Set(i, v8::String::New(css_selectors_[i].data(),
+                                               css_selectors_[i].size()));
+    }
+    std::vector<v8::Handle<v8::Value> > find_selectors_args;
+    find_selectors_args.push_back(js_css_selectors);
+    selector_indices = module_system->CallModuleMethod("contentWatcher",
+                                                       "FindMatchingSelectors",
+                                                       &find_selectors_args);
+  }
+  if (selector_indices.IsEmpty() || !selector_indices->IsArray())
+    return result;
+
+  // Iterate over the array, and extract the indices, laboriously
+  // converting them back to integers.
+  v8::Local<v8::Array> index_array = selector_indices.As<v8::Array>();
+  const size_t length = index_array->Length();
+  result.reserve(length);
+  for (size_t i = 0; i < length; ++i) {
+    v8::Local<v8::Value> index_value = index_array->Get(i);
+    if (!index_value->IsNumber())
+      continue;
+    double index = index_value->NumberValue();
+    // Make sure the index is within bounds.
+    if (index < 0 || css_selectors_.size() <= index)
+      continue;
+    // Push a StringPiece referring to the CSS selector onto the result.
+    result.push_back(
+        base::StringPiece(css_selectors_[static_cast<size_t>(index)]));
+  }
+  return result;
+}
+
+void ContentWatcher::NotifyBrowserOfChange(
+    WebKit::WebFrame* changed_frame) const {
+  WebKit::WebFrame* const top_frame = changed_frame->top();
+  const WebKit::WebSecurityOrigin top_origin =
+      top_frame->document().securityOrigin();
+  // Want to aggregate matched selectors from all frames where an
+  // extension with access to top_origin could run on the frame.
+  if (!top_origin.canAccess(changed_frame->document().securityOrigin())) {
+    // If the changed frame can't be accessed by the top frame, then
+    // no change in it could affect the set of selectors we'd send back.
+    return;
+  }
+
+  std::set<base::StringPiece> transitive_selectors;
+  for (WebKit::WebFrame* frame = top_frame; frame;
+       frame = frame->traverseNext(/*wrap=*/false)) {
+    if (top_origin.canAccess(frame->document().securityOrigin())) {
+      std::map<WebKit::WebFrame*,
+               std::vector<base::StringPiece> >::const_iterator
+          frame_selectors = matching_selectors_.find(frame);
+      if (frame_selectors != matching_selectors_.end()) {
+        transitive_selectors.insert(frame_selectors->second.begin(),
+                                    frame_selectors->second.end());
+      }
+    }
+  }
+  std::vector<std::string> selector_strings;
+  for (std::set<base::StringPiece>::const_iterator
+           it = transitive_selectors.begin();
+       it != transitive_selectors.end(); ++it)
+    selector_strings.push_back(it->as_string());
+  content::RenderView* view =
+      content::RenderView::FromWebView(top_frame->view());
+  view->Send(new ExtensionHostMsg_OnWatchedPageChange(
+      view->GetRoutingID(), selector_strings));
+}
+
+}  // namespace extensions
diff --git a/chrome/renderer/extensions/content_watcher.h b/chrome/renderer/extensions/content_watcher.h
new file mode 100644
index 0000000..908dad3
--- /dev/null
+++ b/chrome/renderer/extensions/content_watcher.h
@@ -0,0 +1,86 @@
+// Copyright (c) 2012 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_RENDERER_EXTENSIONS_CONTENT_WATCHER_H_
+#define CHROME_RENDERER_EXTENSIONS_CONTENT_WATCHER_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/string_piece.h"
+#include "v8/include/v8.h"
+
+namespace WebKit {
+class WebFrame;
+}
+
+namespace extensions {
+class Dispatcher;
+class Extension;
+class NativeHandler;
+
+// Watches the content of WebFrames to notify extensions when they match various
+// patterns.  This class tracks the set of relevant patterns (set by
+// ExtensionMsg_WatchPages) and the set that match on each WebFrame, and sends a
+// ExtensionHostMsg_OnWatchedPageChange whenever a RenderView's set changes.
+//
+// There's one ContentWatcher per Dispatcher rather than per RenderView because
+// WebFrames can move between RenderViews through adoptNode.
+class ContentWatcher {
+ public:
+  explicit ContentWatcher(Dispatcher* dispatcher);
+  ~ContentWatcher();
+
+  // Returns the callback to call on a frame change.
+  scoped_ptr<NativeHandler> MakeNatives();
+
+  // Handler for ExtensionMsg_WatchPages.
+  void OnWatchPages(const std::vector<std::string>& css_selectors);
+
+  // Registers the MutationObserver to call back into this object whenever the
+  // content of |frame| changes.
+  void DidCreateDocumentElement(WebKit::WebFrame* frame);
+
+  // Scans *frame for the current set of interesting CSS selectors, and if
+  // they've changed sends ExtensionHostMsg_OnWatchedPageChange back to the
+  // RenderViewHost that owns the frame.
+  void ScanAndNotify(WebKit::WebFrame* frame);
+
+ private:
+  void EnsureWatchingMutations(WebKit::WebFrame* frame);
+
+  ModuleSystem* GetModuleSystem(WebKit::WebFrame* frame) const;
+  std::vector<base::StringPiece> FindMatchingSelectors(
+      WebKit::WebFrame* frame) const;
+
+  // Given that we saw a change in the CSS selectors that |changed_frame|
+  // matched, tell the browser about the new set of matching selectors in its
+  // top-level page.  We filter this so that if an extension were to be granted
+  // activeTab permission on that top-level page, we only send CSS selectors for
+  // frames that it could run on.
+  void NotifyBrowserOfChange(WebKit::WebFrame* changed_frame) const;
+
+  base::WeakPtrFactory<ContentWatcher> weak_ptr_factory_;
+  Dispatcher* dispatcher_;
+
+  // If any of these selectors match on a page, we need to send an
+  // ExtensionHostMsg_OnWatchedPageChange back to the browser.
+  std::vector<std::string> css_selectors_;
+
+  // Maps live WebFrames to the set of CSS selectors they match.  This lets us
+  // traverse a top-level frame's sub-frames without rescanning them all each
+  // time any one changes.
+  //
+  // The StringPieces point into css_selectors_ above, so when it changes, they
+  // all need to be regenerated.
+  std::map<WebKit::WebFrame*,
+           std::vector<base::StringPiece> > matching_selectors_;
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_RENDERER_EXTENSIONS_CONTENT_WATCHER_H_
diff --git a/chrome/renderer/extensions/dispatcher.cc b/chrome/renderer/extensions/dispatcher.cc
index f63ecf7..09ec7d3 100644
--- a/chrome/renderer/extensions/dispatcher.cc
+++ b/chrome/renderer/extensions/dispatcher.cc
@@ -24,6 +24,7 @@
 #include "chrome/renderer/extensions/app_window_custom_bindings.h"
 #include "chrome/renderer/extensions/chrome_v8_context.h"
 #include "chrome/renderer/extensions/chrome_v8_extension.h"
+#include "chrome/renderer/extensions/content_watcher.h"
 #include "chrome/renderer/extensions/context_menus_custom_bindings.h"
 #include "chrome/renderer/extensions/event_bindings.h"
 #include "chrome/renderer/extensions/extension_custom_bindings.h"
@@ -314,7 +315,8 @@
 }  // namespace
 
 Dispatcher::Dispatcher()
-    : is_webkit_initialized_(false),
+    : content_watcher_(new ContentWatcher(this)),
+      is_webkit_initialized_(false),
       webrequest_adblock_(false),
       webrequest_adblock_plus_(false),
       webrequest_other_(false),
@@ -363,6 +365,8 @@
     IPC_MESSAGE_HANDLER(ExtensionMsg_ShouldUnload, OnShouldUnload)
     IPC_MESSAGE_HANDLER(ExtensionMsg_Unload, OnUnload)
     IPC_MESSAGE_HANDLER(ExtensionMsg_CancelUnload, OnCancelUnload)
+    IPC_MESSAGE_FORWARD(ExtensionMsg_WatchPages,
+                        content_watcher_.get(), ContentWatcher::OnWatchPages)
     IPC_MESSAGE_UNHANDLED(handled = false)
   IPC_END_MESSAGE_MAP()
 
@@ -566,6 +570,8 @@
   module_system->RegisterNativeHandler("setIcon",
       scoped_ptr<NativeHandler>(
           new SetIconNatives(this, request_sender_.get())));
+  module_system->RegisterNativeHandler("contentWatcherNative",
+                                       content_watcher_->MakeNatives());
 
   // Natives used by multiple APIs.
   module_system->RegisterNativeHandler("file_system_natives",
@@ -620,6 +626,7 @@
   source_map_.RegisterSource("apitest", IDR_EXTENSION_APITEST_JS);
 
   // Libraries.
+  source_map_.RegisterSource("contentWatcher", IDR_CONTENT_WATCHER_JS);
   source_map_.RegisterSource("lastError", IDR_LAST_ERROR_JS);
   source_map_.RegisterSource("schemaUtils", IDR_SCHEMA_UTILS_JS);
   source_map_.RegisterSource("sendRequest", IDR_SEND_REQUEST_JS);
@@ -638,6 +645,8 @@
                              IDR_CONTENT_SETTINGS_CUSTOM_BINDINGS_JS);
   source_map_.RegisterSource("contextMenus",
                              IDR_CONTEXT_MENUS_CUSTOM_BINDINGS_JS);
+  source_map_.RegisterSource("declarativeContent",
+                             IDR_DECLARATIVE_CONTENT_CUSTOM_BINDINGS_JS);
   source_map_.RegisterSource("declarativeWebRequest",
                              IDR_DECLARATIVE_WEBREQUEST_CUSTOM_BINDINGS_JS);
   source_map_.RegisterSource(
@@ -871,6 +880,8 @@
             GetRawDataResource(IDR_PLATFORM_APP_CSS)),
         WebDocument::UserStyleUserLevel);
   }
+
+  content_watcher_->DidCreateDocumentElement(frame);
 }
 
 void Dispatcher::OnActivateExtension(const std::string& extension_id) {
diff --git a/chrome/renderer/extensions/dispatcher.h b/chrome/renderer/extensions/dispatcher.h
index 2676b3b..d36234c 100644
--- a/chrome/renderer/extensions/dispatcher.h
+++ b/chrome/renderer/extensions/dispatcher.h
@@ -39,6 +39,7 @@
 }
 
 namespace extensions {
+class ContentWatcher;
 class Extension;
 class FilteredEventRouter;
 class RequestSender;
@@ -66,6 +67,9 @@
   V8SchemaRegistry* v8_schema_registry() {
     return &v8_schema_registry_;
   }
+  ContentWatcher* content_watcher() {
+    return content_watcher_.get();
+  }
 
   bool IsExtensionActive(const std::string& extension_id) const;
 
@@ -221,6 +225,8 @@
 
   scoped_ptr<UserScriptSlave> user_script_slave_;
 
+  scoped_ptr<ContentWatcher> content_watcher_;
+
   // Same as above, but on a longer timer and will run even if the process is
   // not idle, to ensure that IdleHandle gets called eventually.
   base::RepeatingTimer<content::RenderThread> forced_idle_timer_;
diff --git a/chrome/renderer/extensions/module_system.cc b/chrome/renderer/extensions/module_system.cc
index 69b2c78..aa33bbc 100644
--- a/chrome/renderer/extensions/module_system.cc
+++ b/chrome/renderer/extensions/module_system.cc
@@ -5,6 +5,7 @@
 #include "chrome/renderer/extensions/module_system.h"
 
 #include "base/bind.h"
+#include "base/stl_util.h"
 #include "third_party/WebKit/Source/WebKit/chromium/public/WebScopedMicrotaskSuppression.h"
 
 namespace {
@@ -159,29 +160,39 @@
 
 void ModuleSystem::CallModuleMethod(const std::string& module_name,
                                     const std::string& method_name) {
+  std::vector<v8::Handle<v8::Value> > args;
+  CallModuleMethod(module_name, method_name, &args);
+}
+
+v8::Local<v8::Value> ModuleSystem::CallModuleMethod(
+    const std::string& module_name,
+    const std::string& method_name,
+    std::vector<v8::Handle<v8::Value> >* args) {
   v8::HandleScope handle_scope;
   v8::Local<v8::Value> module =
       v8::Local<v8::Value>::New(
           RequireForJsInner(v8::String::New(module_name.c_str())));
   if (module.IsEmpty() || !module->IsObject())
-    return;
+    return v8::Local<v8::Value>();
   v8::Local<v8::Value> value =
       v8::Handle<v8::Object>::Cast(module)->Get(
           v8::String::New(method_name.c_str()));
   if (value.IsEmpty() || !value->IsFunction())
-    return;
+    return v8::Local<v8::Value>();
   v8::Handle<v8::Function> func =
       v8::Handle<v8::Function>::Cast(value);
   // TODO(jeremya/koz): refer to context_ here, not the current context.
   v8::Handle<v8::Object> global(v8::Context::GetCurrent()->Global());
+  v8::Local<v8::Value> result;
   {
     WebKit::WebScopedMicrotaskSuppression suppression;
     v8::TryCatch try_catch;
     try_catch.SetCaptureMessage(true);
-    func->Call(global, 0, NULL);
+    result = func->Call(global, args->size(), vector_as_array(args));
     if (try_catch.HasCaught())
       HandleException(try_catch);
   }
+  return handle_scope.Close(result);
 }
 
 void ModuleSystem::RegisterNativeHandler(const std::string& name,
diff --git a/chrome/renderer/extensions/module_system.h b/chrome/renderer/extensions/module_system.h
index 4f790842..be03f2ad 100644
--- a/chrome/renderer/extensions/module_system.h
+++ b/chrome/renderer/extensions/module_system.h
@@ -14,6 +14,7 @@
 #include <map>
 #include <set>
 #include <string>
+#include <vector>
 
 namespace extensions {
 
@@ -81,6 +82,13 @@
   void CallModuleMethod(const std::string& module_name,
                         const std::string& method_name);
 
+  // Calls the specified method exported by the specified module. This is
+  // equivalent to calling require('module_name').method_name(args) from JS.
+  v8::Local<v8::Value> CallModuleMethod(
+      const std::string& module_name,
+      const std::string& method_name,
+      std::vector<v8::Handle<v8::Value> >* args);
+
   // Register |native_handler| as a potential target for requireNative(), so
   // calls to requireNative(|name|) from JS will return a new object created by
   // |native_handler|.
diff --git a/chrome/renderer/resources/extensions/content_watcher.js b/chrome/renderer/resources/extensions/content_watcher.js
new file mode 100644
index 0000000..5a3ea61a
--- /dev/null
+++ b/chrome/renderer/resources/extensions/content_watcher.js
@@ -0,0 +1,32 @@
+// Copyright (c) 2012 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 contentWatcherNative = requireNative("contentWatcherNative");
+
+// Returns the indices in |css_selectors| that match any element on the page.
+exports.FindMatchingSelectors = function(css_selectors) {
+  result = []
+  css_selectors.forEach(function(selector, index) {
+    try {
+      if (document.querySelector(selector) != null)
+        result.push(index);
+    } catch (exception) {
+      throw new Error("query Selector failed on '" + selector + "': " +
+                      exception.stack);
+    }
+  });
+  return result;
+};
+
+// Watches the page for all changes and calls FrameMutated (a C++ callback) in
+// response.
+var mutation_observer = new WebKitMutationObserver(
+  contentWatcherNative.FrameMutated);
+
+// This runs once per frame, when the module is 'require'd.
+mutation_observer.observe(document, {
+  childList: true,
+  attributes: true,
+  characterData: true,
+  subtree: true});
diff --git a/chrome/renderer/resources/extensions/declarative_content_custom_bindings.js b/chrome/renderer/resources/extensions/declarative_content_custom_bindings.js
new file mode 100644
index 0000000..3655c46
--- /dev/null
+++ b/chrome/renderer/resources/extensions/declarative_content_custom_bindings.js
@@ -0,0 +1,43 @@
+// Copyright (c) 2012 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.
+
+// Custom bindings for the declarativeContent API.
+
+var chromeHidden = requireNative('chrome_hidden').GetChromeHidden();
+var utils = require('utils');
+var validate = require('schemaUtils').validate;
+
+chromeHidden.registerCustomHook('declarativeContent', function(api) {
+  // Returns the schema definition of type |typeId| defined in |namespace|.
+  function getSchema(namespace, typeId) {
+    var apiSchema = utils.lookup(api.apiDefinitions, 'namespace', namespace);
+    var resultSchema = utils.lookup(
+        apiSchema.types, 'id', namespace + '.' + typeId);
+    return resultSchema;
+  }
+
+  // Helper function for the constructor of concrete datatypes of the
+  // declarative content API.
+  // Makes sure that |this| contains the union of parameters and
+  // {'instanceType': 'declarativeContent.' + typeId} and validates the
+  // generated union dictionary against the schema for |typeId|.
+  function setupInstance(instance, parameters, typeId) {
+    for (var key in parameters) {
+      if (parameters.hasOwnProperty(key)) {
+        instance[key] = parameters[key];
+      }
+    }
+    instance.instanceType = 'declarativeContent.' + typeId;
+    var schema = getSchema('declarativeContent', typeId);
+    validate([instance], [schema]);
+  }
+
+  // Setup all data types for the declarative content API.
+  chrome.declarativeContent.PageStateMatcher = function(parameters) {
+    setupInstance(this, parameters, 'PageStateMatcher');
+  };
+  chrome.declarativeContent.ShowPageAction = function(parameters) {
+    setupInstance(this, parameters, 'ShowPageAction');
+  };
+});
diff --git a/chrome/renderer/resources/renderer_resources.grd b/chrome/renderer/resources/renderer_resources.grd
index e73b214..9a75f4f 100644
--- a/chrome/renderer/resources/renderer_resources.grd
+++ b/chrome/renderer/resources/renderer_resources.grd
@@ -22,6 +22,7 @@
       </if>
       <include name="IDR_BLOCKED_PLUGIN_HTML" file="blocked_plugin.html" flattenhtml="true" type="BINDATA" />
       <include name="IDR_CLICK_TO_PLAY_PLUGIN_HTML" file="click_to_play_plugin.html" flattenhtml="true" type="BINDATA" />
+      <include name="IDR_CONTENT_WATCHER_JS" file="extensions\content_watcher.js" type="BINDATA" />
       <include name="IDR_DISABLED_PLUGIN_HTML" file="disabled_plugin.html" flattenhtml="true" type="BINDATA" />
       <include name="IDR_ERROR_APP_HTML" file="error_app.html" flattenhtml="true" type="BINDATA" />
       <include name="IDR_EVENT_BINDINGS_JS" file="extensions\event.js" type="BINDATA" />
@@ -50,6 +51,7 @@
         <include name="IDR_BROWSER_ACTION_CUSTOM_BINDINGS_JS" file="extensions\browser_action_custom_bindings.js" type="BINDATA" />
         <include name="IDR_CONTENT_SETTINGS_CUSTOM_BINDINGS_JS" file="extensions\content_settings_custom_bindings.js" type="BINDATA" />
         <include name="IDR_CONTEXT_MENUS_CUSTOM_BINDINGS_JS" file="extensions\context_menus_custom_bindings.js" type="BINDATA" />
+        <include name="IDR_DECLARATIVE_CONTENT_CUSTOM_BINDINGS_JS" file="extensions\declarative_content_custom_bindings.js" type="BINDATA" />
         <include name="IDR_DECLARATIVE_WEBREQUEST_CUSTOM_BINDINGS_JS" file="extensions\declarative_webrequest_custom_bindings.js" type="BINDATA" />
         <include name="IDR_BLUETOOTH_CUSTOM_BINDINGS_JS" file="extensions\bluetooth_custom_bindings.js" type="BINDATA" />
         <include name="IDR_PERMISSIONS_CUSTOM_BINDINGS_JS" file="extensions\permissions_custom_bindings.js" type="BINDATA" />
diff --git a/chrome/test/data/extensions/api_test/declarative_content/overview/background.js b/chrome/test/data/extensions/api_test/declarative_content/overview/background.js
new file mode 100644
index 0000000..a23b546
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/declarative_content/overview/background.js
@@ -0,0 +1,23 @@
+// Copyright (c) 2012 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 declarative = chrome.declarative;
+
+var PageStateMatcher = chrome.declarativeContent.PageStateMatcher;
+var ShowPageAction = chrome.declarativeContent.ShowPageAction;
+
+var rule0 = {
+  conditions: [new PageStateMatcher({pageUrl: {hostPrefix: "test1"}}),
+               new PageStateMatcher({css: ["input[type='password']"]})],
+  actions: [new ShowPageAction()]
+}
+
+var testEvent = chrome.declarativeContent.onPageChanged;
+
+testEvent.removeRules(undefined, function() {
+  testEvent.addRules([rule0], function() {
+    chrome.test.sendMessage("ready", function(reply) {
+    })
+  });
+});
diff --git a/chrome/test/data/extensions/api_test/declarative_content/overview/manifest.json b/chrome/test/data/extensions/api_test/declarative_content/overview/manifest.json
new file mode 100644
index 0000000..08451ae
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/declarative_content/overview/manifest.json
@@ -0,0 +1,13 @@
+{
+  "name": "Declarative Content apitest",
+  "version": "0.1",
+  "manifest_version": 2,
+  "description": "end-to-end browser test for the declarative Content API",
+  "background": {
+    "scripts": ["background.js"]
+  },
+  "permissions": [
+    "declarativeContent"
+  ],
+  "page_action": {}
+}