summaryrefslogtreecommitdiffstats
path: root/chrome/browser/extensions
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/extensions')
-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
19 files changed, 1398 insertions, 10 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.