diff options
Diffstat (limited to 'chrome')
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": {} +} |