summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
Diffstat (limited to 'chrome')
-rw-r--r--chrome/browser/extensions/api/declarative/rules_registry_service.cc17
-rw-r--r--chrome/browser/extensions/api/declarative/rules_registry_service.h10
-rw-r--r--chrome/browser/extensions/api/declarative/rules_registry_storage_delegate.cc7
-rw-r--r--chrome/browser/extensions/api/declarative_content/OWNERS2
-rw-r--r--chrome/browser/extensions/api/declarative_content/content_action.cc148
-rw-r--r--chrome/browser/extensions/api/declarative_content/content_action.h70
-rw-r--r--chrome/browser/extensions/api/declarative_content/content_action_unittest.cc155
-rw-r--r--chrome/browser/extensions/api/declarative_content/content_condition.cc140
-rw-r--r--chrome/browser/extensions/api/declarative_content/content_condition.h110
-rw-r--r--chrome/browser/extensions/api/declarative_content/content_condition_unittest.cc97
-rw-r--r--chrome/browser/extensions/api/declarative_content/content_constants.cc23
-rw-r--r--chrome/browser/extensions/api/declarative_content/content_constants.h28
-rw-r--r--chrome/browser/extensions/api/declarative_content/content_rules_registry.cc276
-rw-r--r--chrome/browser/extensions/api/declarative_content/content_rules_registry.h145
-rw-r--r--chrome/browser/extensions/api/declarative_content/declarative_content_apitest.cc72
-rw-r--r--chrome/browser/extensions/extension_action.cc16
-rw-r--r--chrome/browser/extensions/extension_action.h60
-rw-r--r--chrome/browser/extensions/tab_helper.cc26
-rw-r--r--chrome/browser/extensions/tab_helper.h6
-rw-r--r--chrome/chrome_browser_extensions.gypi8
-rw-r--r--chrome/chrome_renderer.gypi4
-rw-r--r--chrome/chrome_tests.gypi1
-rw-r--r--chrome/chrome_tests_unit.gypi2
-rw-r--r--chrome/common/extensions/api/_permission_features.json4
-rw-r--r--chrome/common/extensions/api/declarative_content.json73
-rw-r--r--chrome/common/extensions/api/extension_api.cc2
-rw-r--r--chrome/common/extensions/extension_messages.h21
-rw-r--r--chrome/common/extensions/permissions/api_permission.cc1
-rw-r--r--chrome/common/extensions/permissions/api_permission.h1
-rw-r--r--chrome/common/extensions/permissions/permission_set_unittest.cc1
-rw-r--r--chrome/common/extensions_api_resources.grd1
-rw-r--r--chrome/renderer/extensions/content_watcher.cc209
-rw-r--r--chrome/renderer/extensions/content_watcher.h86
-rw-r--r--chrome/renderer/extensions/dispatcher.cc13
-rw-r--r--chrome/renderer/extensions/dispatcher.h6
-rw-r--r--chrome/renderer/extensions/module_system.cc17
-rw-r--r--chrome/renderer/extensions/module_system.h8
-rw-r--r--chrome/renderer/resources/extensions/content_watcher.js32
-rw-r--r--chrome/renderer/resources/extensions/declarative_content_custom_bindings.js43
-rw-r--r--chrome/renderer/resources/renderer_resources.grd2
-rw-r--r--chrome/test/data/extensions/api_test/declarative_content/overview/background.js23
-rw-r--r--chrome/test/data/extensions/api_test/declarative_content/overview/manifest.json13
42 files changed, 1965 insertions, 14 deletions
diff --git a/chrome/browser/extensions/api/declarative/rules_registry_service.cc b/chrome/browser/extensions/api/declarative/rules_registry_service.cc
index 24e7f32..58a849d 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 @@ void RulesRegistryService::RegisterDefaultRulesRegistries() {
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 db0485b..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 @@ class NotificationSource;
}
namespace extensions {
+class ContentRulesRegistry;
class RulesRegistry;
class RulesRegistryStorageDelegate;
}
@@ -50,6 +51,11 @@ class RulesRegistryService : public content::NotificationObserver {
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 @@ class RulesRegistryService : public content::NotificationObserver {
// 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..ce9fb2c 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 @@ void RulesRegistryStorageDelegate::Inner::InitForOTRProfile() {
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 @@ void RulesRegistryStorageDelegate::Inner::Observe(
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..5ee8175
--- /dev/null
+++ b/chrome/browser/extensions/api/declarative_content/OWNERS
@@ -0,0 +1,2 @@
+battre@chromium.org
+jyasskin@chromium.org
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..24661dd
--- /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..099bebf
--- /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..c07afdb
--- /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..dda4f93
--- /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 @@ bool ExtensionAction::SetAppearance(int tab_id, Appearance new_appearance) {
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 @@ void ExtensionAction::ClearAllValuesForTab(int tab_id) {
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 dacd961..943a3eb 100644
--- a/chrome/browser/extensions/extension_action.h
+++ b/chrome/browser/extensions/extension_action.h
@@ -220,15 +220,20 @@ class ExtensionAction {
// 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 @@ class ExtensionAction {
(*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 @@ class ExtensionAction {
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 @@ TabHelper::TabHelper(content::WebContents* web_contents)
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::RenderViewCreated(RenderViewHost* render_view_host) {
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 @@ bool TabHelper::OnMessageReceived(const IPC::Message& message) {
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 @@ void TabHelper::OnContentScriptsExecuting(
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 @@ struct LoadCommittedDetails;
namespace extensions {
class Extension;
class LocationBarController;
+class RulesRegistryService;
class ScriptBadgeController;
class ScriptBubbleController;
class ScriptExecutor;
@@ -192,6 +195,7 @@ class TabHelper : public content::WebContentsObserver,
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 @@ class TabHelper : public content::WebContentsObserver,
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 62ce459..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..427c883 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 @@ void ExtensionAPI::InitDefaultConfiguration() {
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 7927ba5..6f084c4 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 @@ IPC_MESSAGE_ROUTED2(ExtensionMsg_AddMessageToConsole,
// 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 @@ IPC_MESSAGE_CONTROL1(ExtensionHostMsg_ResumeRequests, int /* route_id */)
// 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 c3dc35d..30c9eea 100644
--- a/chrome/common/extensions/permissions/api_permission.cc
+++ b/chrome/common/extensions/permissions/api_permission.cc
@@ -176,6 +176,7 @@ void APIPermissionInfo::RegisterAllPermissions(
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 @@ class APIPermission {
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 @@ TEST(PermissionsTest, PermissionMessages) {
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 @@ static v8::Handle<v8::Object> GetOrCreateChrome(
} // 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 @@ bool Dispatcher::OnControlMessageReceived(const IPC::Message& message) {
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 @@ void Dispatcher::RegisterNativeHandlers(ModuleSystem* module_system,
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 @@ void Dispatcher::PopulateSourceMap() {
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 @@ void Dispatcher::PopulateSourceMap() {
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 @@ void Dispatcher::DidCreateDocumentElement(WebKit::WebFrame* frame) {
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 @@ class RenderThread;
}
namespace extensions {
+class ContentWatcher;
class Extension;
class FilteredEventRouter;
class RequestSender;
@@ -66,6 +67,9 @@ class Dispatcher : public content::RenderProcessObserver {
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 @@ class Dispatcher : public content::RenderProcessObserver {
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 @@ v8::Handle<v8::Value> ModuleSystem::RequireForJsInner(
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 4f79084..be03f2a 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 @@ class ModuleSystem : public NativeHandler {
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..5a3ea61
--- /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 @@ without changes to the corresponding grd file. fb9 -->
</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 @@ without changes to the corresponding grd file. fb9 -->
<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": {}
+}