diff options
author | wittman <wittman@chromium.org> | 2015-06-24 10:47:40 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-06-24 17:48:08 +0000 |
commit | b3ee048a81b8a101c0f96ceb612d148fa3bde44f (patch) | |
tree | 66c665ca7202faef276e523e731a2e017bd27837 | |
parent | 6d26d5737cdc4a4c223163eeb83881c11aade838 (diff) | |
download | chromium_src-b3ee048a81b8a101c0f96ceb612d148fa3bde44f.zip chromium_src-b3ee048a81b8a101c0f96ceb612d148fa3bde44f.tar.gz chromium_src-b3ee048a81b8a101c0f96ceb612d148fa3bde44f.tar.bz2 |
Add declarativeContent support for bookmarked state
Allow extensions to test the bookmarked state of URLs using the
declarativeContent API.
BUG=485169
Review URL: https://codereview.chromium.org/1183983003
Cr-Commit-Position: refs/heads/master@{#335945}
32 files changed, 1068 insertions, 64 deletions
diff --git a/chrome/browser/extensions/api/declarative_content/chrome_content_rules_registry.cc b/chrome/browser/extensions/api/declarative_content/chrome_content_rules_registry.cc index 53be4bc..f03c9b1 100644 --- a/chrome/browser/extensions/api/declarative_content/chrome_content_rules_registry.cc +++ b/chrome/browser/extensions/api/declarative_content/chrome_content_rules_registry.cc @@ -85,6 +85,7 @@ ChromeContentRulesRegistry::ChromeContentRulesRegistry( RulesRegistryService::kDefaultRulesRegistryID), page_url_condition_tracker_(browser_context, this), css_condition_tracker_(browser_context, this), + is_bookmarked_condition_tracker_(browser_context, this), evaluation_disposition_(EVALUATE_REQUESTS) { extension_info_map_ = ExtensionSystem::Get(browser_context)->info_map(); @@ -137,6 +138,7 @@ void ChromeContentRulesRegistry::MonitorWebContentsForRuleEvaluation( EvaluationScope evaluation_scope(this); page_url_condition_tracker_.TrackForWebContents(contents); css_condition_tracker_.TrackForWebContents(contents); + is_bookmarked_condition_tracker_.TrackForWebContents(contents); } void ChromeContentRulesRegistry::DidNavigateMainFrame( @@ -148,6 +150,8 @@ void ChromeContentRulesRegistry::DidNavigateMainFrame( page_url_condition_tracker_.OnWebContentsNavigation(contents, details, params); css_condition_tracker_.OnWebContentsNavigation(contents, details, params); + is_bookmarked_condition_tracker_.OnWebContentsNavigation(contents, details, + params); } } @@ -362,6 +366,8 @@ void ChromeContentRulesRegistry::EvaluateConditionsForTab( page_url_condition_tracker_.GetMatches(tab, &renderer_data.page_url_matches); css_condition_tracker_.GetMatchingCssSelectors(tab, &renderer_data.css_selectors); + renderer_data.is_bookmarked = + is_bookmarked_condition_tracker_.IsUrlBookmarked(tab); std::set<const ContentRule*> matching_rules = GetMatches(renderer_data, tab->GetBrowserContext()->IsOffTheRecord()); if (matching_rules.empty() && !ContainsKey(active_rules_, tab)) diff --git a/chrome/browser/extensions/api/declarative_content/chrome_content_rules_registry.h b/chrome/browser/extensions/api/declarative_content/chrome_content_rules_registry.h index 9ce1e90..0732a6d 100644 --- a/chrome/browser/extensions/api/declarative_content/chrome_content_rules_registry.h +++ b/chrome/browser/extensions/api/declarative_content/chrome_content_rules_registry.h @@ -18,6 +18,7 @@ #include "chrome/browser/extensions/api/declarative_content/content_condition.h" #include "chrome/browser/extensions/api/declarative_content/declarative_content_condition_tracker_delegate.h" #include "chrome/browser/extensions/api/declarative_content/declarative_content_css_condition_tracker.h" +#include "chrome/browser/extensions/api/declarative_content/declarative_content_is_bookmarked_condition_tracker.h" #include "chrome/browser/extensions/api/declarative_content/declarative_content_page_url_condition_tracker.h" #include "content/public/browser/notification_observer.h" #include "content/public/browser/notification_registrar.h" @@ -181,6 +182,10 @@ class ChromeContentRulesRegistry // Responsible for tracking declarative content CSS condition state. DeclarativeContentCssConditionTracker css_condition_tracker_; + // Responsible for tracking declarative content bookmarked condition state. + DeclarativeContentIsBookmarkedConditionTracker + is_bookmarked_condition_tracker_; + // Specifies what to do with evaluation requests. EvaluationDisposition evaluation_disposition_; diff --git a/chrome/browser/extensions/api/declarative_content/chrome_content_rules_registry_unittest.cc b/chrome/browser/extensions/api/declarative_content/chrome_content_rules_registry_unittest.cc index 0b3e232..dafff4e 100644 --- a/chrome/browser/extensions/api/declarative_content/chrome_content_rules_registry_unittest.cc +++ b/chrome/browser/extensions/api/declarative_content/chrome_content_rules_registry_unittest.cc @@ -7,8 +7,11 @@ #include <string> #include "base/test/values_test_util.h" +#include "chrome/browser/bookmarks/bookmark_model_factory.h" #include "chrome/browser/extensions/test_extension_environment.h" #include "chrome/test/base/testing_profile.h" +#include "components/bookmarks/browser/bookmark_model.h" +#include "components/bookmarks/test/bookmark_test_helpers.h" #include "content/public/browser/navigation_details.h" #include "content/public/browser/web_contents.h" #include "content/public/common/frame_navigate_params.h" @@ -17,15 +20,30 @@ namespace extensions { -TEST(DeclarativeChromeContentRulesRegistryTest, ActiveRulesDoesntGrow) { - TestExtensionEnvironment env; +class DeclarativeChromeContentRulesRegistryTest : public testing::Test { + public: + DeclarativeChromeContentRulesRegistryTest() { + env_.profile()->CreateBookmarkModel(true); + bookmarks::test::WaitForBookmarkModelToLoad( + BookmarkModelFactory::GetForProfile(env_.profile())); + } + protected: + TestExtensionEnvironment* env() { return &env_; } + + private: + TestExtensionEnvironment env_; + + DISALLOW_COPY_AND_ASSIGN(DeclarativeChromeContentRulesRegistryTest); +}; + +TEST_F(DeclarativeChromeContentRulesRegistryTest, ActiveRulesDoesntGrow) { scoped_refptr<ChromeContentRulesRegistry> registry( - new ChromeContentRulesRegistry(env.profile(), NULL)); + new ChromeContentRulesRegistry(env()->profile(), NULL)); EXPECT_EQ(0u, registry->GetActiveRulesCountForTesting()); - scoped_ptr<content::WebContents> tab = env.MakeTab(); + scoped_ptr<content::WebContents> tab = env()->MakeTab(); registry->MonitorWebContentsForRuleEvaluation(tab.get()); registry->DidNavigateMainFrame(tab.get(), content::LoadCommittedDetails(), content::FrameNavigateParams()); @@ -51,7 +69,7 @@ TEST(DeclarativeChromeContentRulesRegistryTest, ActiveRulesDoesntGrow) { std::vector<linked_ptr<RulesRegistry::Rule> > rules; rules.push_back(rule); - const Extension* extension = env.MakeExtension(*base::test::ParseJson( + const Extension* extension = env()->MakeExtension(*base::test::ParseJson( "{\"page_action\": {}}")); registry->AddRulesImpl(extension->id(), rules); @@ -68,7 +86,7 @@ TEST(DeclarativeChromeContentRulesRegistryTest, ActiveRulesDoesntGrow) { tab.reset(); EXPECT_EQ(0u, registry->GetActiveRulesCountForTesting()); - tab = env.MakeTab(); + tab = env()->MakeTab(); registry->MonitorWebContentsForRuleEvaluation(tab.get()); registry->UpdateMatchingCssSelectorsForTesting(tab.get(), css_selectors); EXPECT_EQ(1u, registry->GetActiveRulesCountForTesting()); diff --git a/chrome/browser/extensions/api/declarative_content/content_condition.cc b/chrome/browser/extensions/api/declarative_content/content_condition.cc index cc12e6a3..1822cf0 100644 --- a/chrome/browser/extensions/api/declarative_content/content_condition.cc +++ b/chrome/browser/extensions/api/declarative_content/content_condition.cc @@ -8,12 +8,13 @@ #include "base/values.h" #include "chrome/browser/extensions/api/declarative_content/content_constants.h" #include "components/url_matcher/url_matcher_factory.h" +#include "extensions/common/permissions/permissions_data.h" using url_matcher::URLMatcherConditionFactory; using url_matcher::URLMatcherConditionSet; using url_matcher::URLMatcherFactory; -namespace keys = extensions::declarative_content_constants; +namespace extensions { namespace { static URLMatcherConditionSet::ID g_next_id = 0; @@ -26,14 +27,20 @@ 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 +const char kInvalidTypeOfParameter[] = "Attribute '%s' has an invalid type"; +const char kIsBookmarkedRequiresBookmarkPermission[] = + "Property 'isBookmarked' requires 'bookmarks' permission"; -namespace extensions { +bool HasBookmarkAPIPermission(const Extension* extension) { + return extension->permissions_data()->HasAPIPermission( + APIPermission::kBookmark); +} + +} // namespace namespace keys = declarative_content_constants; -RendererContentMatchData::RendererContentMatchData() {} +RendererContentMatchData::RendererContentMatchData() : is_bookmarked(false) {} RendererContentMatchData::~RendererContentMatchData() {} // @@ -41,10 +48,14 @@ RendererContentMatchData::~RendererContentMatchData() {} // ContentCondition::ContentCondition( + scoped_refptr<const Extension> extension, scoped_refptr<URLMatcherConditionSet> url_matcher_conditions, - const std::vector<std::string>& css_selectors) - : url_matcher_conditions_(url_matcher_conditions), - css_selectors_(css_selectors) { + const std::vector<std::string>& css_selectors, + BookmarkedStateMatch bookmarked_state) + : extension_(extension.Pass()), + url_matcher_conditions_(url_matcher_conditions), + css_selectors_(css_selectors), + bookmarked_state_(bookmarked_state) { CHECK(url_matcher_conditions.get()); } @@ -62,12 +73,19 @@ bool ContentCondition::IsFulfilled( if (!ContainsKey(renderer_data.css_selectors, *i)) return false; } + + if (HasBookmarkAPIPermission(extension_.get()) && + bookmarked_state_ != DONT_CARE) { + return (bookmarked_state_ == BOOKMARKED && renderer_data.is_bookmarked) || + (bookmarked_state_ == NOT_BOOKMARKED && !renderer_data.is_bookmarked); + } + return true; } // static scoped_ptr<ContentCondition> ContentCondition::Create( - const Extension* extension, + scoped_refptr<const Extension> extension, URLMatcherConditionFactory* url_matcher_condition_factory, const base::Value& condition, std::string* error) { @@ -90,6 +108,7 @@ scoped_ptr<ContentCondition> ContentCondition::Create( scoped_refptr<URLMatcherConditionSet> url_matcher_condition_set; std::vector<std::string> css_rules; + BookmarkedStateMatch bookmarked_state = DONT_CARE; for (base::DictionaryValue::Iterator iter(*condition_dict); !iter.IsAtEnd(); iter.Advance()) { @@ -100,7 +119,7 @@ scoped_ptr<ContentCondition> ContentCondition::Create( } else if (condition_attribute_name == keys::kPageUrl) { const base::DictionaryValue* dict = NULL; if (!condition_attribute_value.GetAsDictionary(&dict)) { - *error = base::StringPrintf(kInvalidTypeOfParamter, + *error = base::StringPrintf(kInvalidTypeOfParameter, condition_attribute_name.c_str()); } else { url_matcher_condition_set = @@ -113,14 +132,25 @@ scoped_ptr<ContentCondition> ContentCondition::Create( 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, + *error = base::StringPrintf(kInvalidTypeOfParameter, condition_attribute_name.c_str()); break; } css_rules.push_back(css_rule); } } else { - *error = base::StringPrintf(kInvalidTypeOfParamter, + *error = base::StringPrintf(kInvalidTypeOfParameter, + condition_attribute_name.c_str()); + } + } else if (condition_attribute_name == keys::kIsBookmarked){ + bool value; + if (condition_attribute_value.GetAsBoolean(&value)) { + if (!HasBookmarkAPIPermission(extension.get())) + *error = kIsBookmarkedRequiresBookmarkPermission; + else + bookmarked_state = value ? BOOKMARKED : NOT_BOOKMARKED; + } else { + *error = base::StringPrintf(kInvalidTypeOfParameter, condition_attribute_name.c_str()); } } else { @@ -139,8 +169,9 @@ scoped_ptr<ContentCondition> ContentCondition::Create( url_matcher_condition_set = new URLMatcherConditionSet(++g_next_id, url_matcher_conditions); } - return scoped_ptr<ContentCondition>( - new ContentCondition(url_matcher_condition_set, css_rules)); + return make_scoped_ptr( + new ContentCondition(extension.Pass(), url_matcher_condition_set, + css_rules, bookmarked_state)); } } // namespace extensions diff --git a/chrome/browser/extensions/api/declarative_content/content_condition.h b/chrome/browser/extensions/api/declarative_content/content_condition.h index b8c92f2..cf9c9df 100644 --- a/chrome/browser/extensions/api/declarative_content/content_condition.h +++ b/chrome/browser/extensions/api/declarative_content/content_condition.h @@ -32,6 +32,8 @@ struct RendererContentMatchData { // 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; + // True if the URL is bookmarked. + bool is_bookmarked; }; // Representation of a condition in the Declarative Content API. A condition @@ -58,16 +60,21 @@ class ContentCondition { // DeclarativeConditionSet<ContentCondition>::IsFulfilled. typedef RendererContentMatchData MatchData; + // Possible states for matching bookmarked state. + enum BookmarkedStateMatch { NOT_BOOKMARKED, BOOKMARKED, DONT_CARE }; + ContentCondition( + scoped_refptr<const Extension> extension, scoped_refptr<url_matcher::URLMatcherConditionSet> url_matcher_conditions, - const std::vector<std::string>& css_selectors); + const std::vector<std::string>& css_selectors, + BookmarkedStateMatch bookmarked_state); ~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( - const Extension* extension, + scoped_refptr<const Extension> extension, url_matcher::URLMatcherConditionFactory* url_matcher_condition_factory, const base::Value& condition, std::string* error); @@ -102,9 +109,13 @@ class ContentCondition { return css_selectors_; } + BookmarkedStateMatch bookmarked_state() const { return bookmarked_state_; } + private: + const scoped_refptr<const Extension> extension_; scoped_refptr<url_matcher::URLMatcherConditionSet> url_matcher_conditions_; std::vector<std::string> css_selectors_; + BookmarkedStateMatch bookmarked_state_; DISALLOW_COPY_AND_ASSIGN(ContentCondition); }; diff --git a/chrome/browser/extensions/api/declarative_content/content_condition_unittest.cc b/chrome/browser/extensions/api/declarative_content/content_condition_unittest.cc index 9d6f24a..2500211 100644 --- a/chrome/browser/extensions/api/declarative_content/content_condition_unittest.cc +++ b/chrome/browser/extensions/api/declarative_content/content_condition_unittest.cc @@ -11,6 +11,8 @@ #include "base/values.h" #include "chrome/browser/extensions/api/declarative_content/content_constants.h" #include "components/url_matcher/url_matcher.h" +#include "extensions/common/extension_builder.h" +#include "extensions/common/value_builder.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" @@ -21,8 +23,26 @@ using url_matcher::URLMatcher; using url_matcher::URLMatcherConditionSet; namespace extensions { + namespace { +scoped_refptr<Extension> CreateExtensionWithBookmarksPermission( + bool include_bookmarks) { + ListBuilder permissions; + permissions.Append("declarativeContent"); + if (include_bookmarks) + permissions.Append("bookmarks"); + return ExtensionBuilder() + .SetManifest(DictionaryBuilder() + .Set("name", "Test extension") + .Set("version", "1.0") + .Set("manifest_version", 2) + .Set("permissions", permissions)) + .Build(); +} + +} // namespace + TEST(DeclarativeContentConditionTest, UnknownConditionName) { URLMatcher matcher; std::string error; @@ -79,10 +99,12 @@ TEST(DeclarativeContentConditionTest, WrongCssDatatype) { TEST(DeclarativeContentConditionTest, ConditionWithUrlAndCss) { URLMatcher matcher; + scoped_refptr<Extension> extension = + CreateExtensionWithBookmarksPermission(false); std::string error; scoped_ptr<ContentCondition> result = ContentCondition::Create( - NULL, + extension.get(), matcher.condition_factory(), *base::test::ParseJson( "{\n" @@ -116,5 +138,116 @@ TEST(DeclarativeContentConditionTest, ConditionWithUrlAndCss) { EXPECT_FALSE(result->IsFulfilled(match_data)); } -} // namespace +// Tests that condition with isBookmarked requires "bookmarks" permission. +TEST(DeclarativeContentConditionTest, IsBookmarkedRequiresBookmarkPermission) { + URLMatcher matcher; + scoped_refptr<Extension> extension = + CreateExtensionWithBookmarksPermission(false); + + std::string error; + scoped_ptr<ContentCondition> result = ContentCondition::Create( + extension.get(), + matcher.condition_factory(), + *base::test::ParseJson( + "{\n" + " \"isBookmarked\": true,\n" + " \"instanceType\": \"declarativeContent.PageStateMatcher\",\n" + "}"), + &error); + EXPECT_THAT(error, HasSubstr("requires 'bookmarks' permission")); + ASSERT_FALSE(result); +} + +// Tests an invalid isBookmarked value type. +TEST(DeclarativeContentConditionTest, WrongIsBookmarkedDatatype) { + URLMatcher matcher; + scoped_refptr<Extension> extension = + CreateExtensionWithBookmarksPermission(true); + + std::string error; + scoped_ptr<ContentCondition> result = ContentCondition::Create( + extension.get(), + matcher.condition_factory(), + *base::test::ParseJson( + "{\n" + " \"isBookmarked\": [],\n" + " \"instanceType\": \"declarativeContent.PageStateMatcher\",\n" + "}"), + &error); + EXPECT_THAT(error, HasSubstr("invalid type")); + EXPECT_FALSE(result); +} + +// Tests isBookmark: true. +TEST(DeclarativeContentConditionTest, IsBookmarkedTrue) { + URLMatcher matcher; + scoped_refptr<Extension> extension = + CreateExtensionWithBookmarksPermission(true); + + std::string error; + scoped_ptr<ContentCondition> result = ContentCondition::Create( + extension.get(), + matcher.condition_factory(), + *base::test::ParseJson( + "{\n" + " \"isBookmarked\": true,\n" + " \"instanceType\": \"declarativeContent.PageStateMatcher\",\n" + "}"), + &error); + EXPECT_EQ("", error); + ASSERT_TRUE(result); + + // TODO(wittman): Getting/adding URL matcher condition sets should not be + // necessary when we haven't specified any pageUrl conditions. Remove this and + // the MatchURL call below, and refactor ContentCondition and + // ChromeContentRulesRegistry to not depend on a ContentCondition always + // having a URL matcher condition. + URLMatcherConditionSet::Vector all_new_condition_sets; + result->GetURLMatcherConditionSets(&all_new_condition_sets); + matcher.AddConditionSets(all_new_condition_sets); + + RendererContentMatchData data; + data.page_url_matches = matcher.MatchURL(GURL("http://test/")); + data.is_bookmarked = true; + EXPECT_TRUE(result->IsFulfilled(data)); + data.is_bookmarked = false; + EXPECT_FALSE(result->IsFulfilled(data)); +} + +// Tests isBookmark: false. +TEST(DeclarativeContentConditionTest, IsBookmarkedFalse) { + URLMatcher matcher; + scoped_refptr<Extension> extension = + CreateExtensionWithBookmarksPermission(true); + + std::string error; + scoped_ptr<ContentCondition> result = ContentCondition::Create( + extension.get(), + matcher.condition_factory(), + *base::test::ParseJson( + "{\n" + " \"isBookmarked\": false,\n" + " \"instanceType\": \"declarativeContent.PageStateMatcher\",\n" + "}"), + &error); + EXPECT_EQ("", error); + ASSERT_TRUE(result); + + // TODO(wittman): Getting/adding URL matcher condition sets should not be + // necessary when we haven't specified any pageUrl conditions. Remove this and + // the MatchURL call below, and refactor ContentCondition and + // ChromeContentRulesRegistry to not depend on a ContentCondition always + // having a URL matcher condition. + URLMatcherConditionSet::Vector all_new_condition_sets; + result->GetURLMatcherConditionSets(&all_new_condition_sets); + matcher.AddConditionSets(all_new_condition_sets); + + RendererContentMatchData data; + data.page_url_matches = matcher.MatchURL(GURL("http://test/")); + data.is_bookmarked = true; + EXPECT_FALSE(result->IsFulfilled(data)); + data.is_bookmarked = false; + EXPECT_TRUE(result->IsFulfilled(data)); +} + } // namespace extensions diff --git a/chrome/browser/extensions/api/declarative_content/content_constants.cc b/chrome/browser/extensions/api/declarative_content/content_constants.cc index b4a6177..2d41866 100644 --- a/chrome/browser/extensions/api/declarative_content/content_constants.cc +++ b/chrome/browser/extensions/api/declarative_content/content_constants.cc @@ -14,6 +14,7 @@ const char kOnPageChanged[] = "declarativeContent.onPageChanged"; const char kAllFrames[] = "allFrames"; const char kCss[] = "css"; const char kInstanceType[] = "instanceType"; +const char kIsBookmarked[] = "isBookmarked"; const char kJs[] = "js"; const char kMatchAboutBlank[] = "matchAboutBlank"; const char kPageUrl[] = "pageUrl"; diff --git a/chrome/browser/extensions/api/declarative_content/content_constants.h b/chrome/browser/extensions/api/declarative_content/content_constants.h index 7177dfd..4286c6e 100644 --- a/chrome/browser/extensions/api/declarative_content/content_constants.h +++ b/chrome/browser/extensions/api/declarative_content/content_constants.h @@ -17,6 +17,7 @@ extern const char kOnPageChanged[]; extern const char kAllFrames[]; extern const char kCss[]; extern const char kInstanceType[]; +extern const char kIsBookmarked[]; extern const char kJs[]; extern const char kMatchAboutBlank[]; extern const char kPageUrl[]; diff --git a/chrome/browser/extensions/api/declarative_content/declarative_content_apitest.cc b/chrome/browser/extensions/api/declarative_content/declarative_content_apitest.cc index adc530a..810f5c8 100644 --- a/chrome/browser/extensions/api/declarative_content/declarative_content_apitest.cc +++ b/chrome/browser/extensions/api/declarative_content/declarative_content_apitest.cc @@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "chrome/browser/bookmarks/bookmark_model_factory.h" #include "chrome/browser/extensions/extension_action.h" #include "chrome/browser/extensions/extension_action_manager.h" #include "chrome/browser/extensions/extension_action_test_util.h" @@ -11,6 +14,7 @@ #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_window.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" +#include "components/bookmarks/browser/bookmark_model.h" #include "content/public/test/browser_test_utils.h" #include "extensions/browser/extension_registry.h" #include "extensions/test/extension_test_message_listener.h" @@ -31,7 +35,7 @@ const char kDeclarativeContentManifest[] = " },\n" " \"page_action\": {},\n" " \"permissions\": [\n" - " \"declarativeContent\"\n" + " \"declarativeContent\", \"bookmarks\"\n" " ],\n" " \"incognito\": \"spanning\"\n" "}\n"; @@ -108,6 +112,10 @@ class DeclarativeContentApiTest : public ExtensionApiTest { // |is_enabled_in_incognito|. void CheckIncognito(IncognitoMode mode, bool is_enabled_in_incognito); + // Checks that the rules matching a bookmarked state of |is_bookmarked| are + // correctly evaluated on bookmark events. + void CheckBookmarkEvents(bool is_bookmarked); + TestExtensionDir ext_dir_; private: @@ -183,6 +191,60 @@ void DeclarativeContentApiTest::CheckIncognito(IncognitoMode mode, EXPECT_FALSE(page_action->GetIsVisible(tab_id)); } +void DeclarativeContentApiTest::CheckBookmarkEvents(bool match_is_bookmarked) { + ext_dir_.WriteManifest(kDeclarativeContentManifest); + ext_dir_.WriteFile(FILE_PATH_LITERAL("background.js"), kBackgroundHelpers); + + content::WebContents* const tab = + browser()->tab_strip_model()->GetWebContentsAt(0); + const int tab_id = ExtensionTabUtil::GetTabId(tab); + + const Extension* extension = LoadExtension(ext_dir_.unpacked_path()); + ASSERT_TRUE(extension); + const ExtensionAction* page_action = ExtensionActionManager::Get( + browser()->profile())->GetPageAction(*extension); + ASSERT_TRUE(page_action); + + NavigateInRenderer(tab, GURL("http://test1/")); + EXPECT_FALSE(page_action->GetIsVisible(tab_id)); + + static const char kSetIsBookmarkedRule[] = + "setRules([{\n" + " conditions: [new PageStateMatcher({isBookmarked: %s})],\n" + " actions: [new ShowPageAction()]\n" + "}], 'test_rule');\n"; + + EXPECT_EQ("test_rule", ExecuteScriptInBackgroundPage( + extension->id(), + base::StringPrintf(kSetIsBookmarkedRule, + match_is_bookmarked ? "true" : "false"))); + EXPECT_EQ(!match_is_bookmarked, page_action->GetIsVisible(tab_id)); + + // Check rule evaluation on add/remove bookmark. + bookmarks::BookmarkModel* bookmark_model = + BookmarkModelFactory::GetForProfile(browser()->profile()); + const bookmarks::BookmarkNode* node = + bookmark_model->AddURL(bookmark_model->other_node(), 0, + base::ASCIIToUTF16("title"), + GURL("http://test1/")); + EXPECT_EQ(match_is_bookmarked, page_action->GetIsVisible(tab_id)); + + bookmark_model->Remove(node); + EXPECT_EQ(!match_is_bookmarked, page_action->GetIsVisible(tab_id)); + + // Check rule evaluation on navigate to bookmarked and non-bookmarked URL. + bookmark_model->AddURL(bookmark_model->other_node(), 0, + base::ASCIIToUTF16("title"), + GURL("http://test2/")); + + NavigateInRenderer(tab, GURL("http://test2/")); + EXPECT_EQ(match_is_bookmarked, page_action->GetIsVisible(tab_id)); + + NavigateInRenderer(tab, GURL("http://test3/")); + EXPECT_EQ(!match_is_bookmarked, page_action->GetIsVisible(tab_id)); +} + + IN_PROC_BROWSER_TEST_F(DeclarativeContentApiTest, Overview) { ext_dir_.WriteManifest(kDeclarativeContentManifest); ext_dir_.WriteFile( @@ -657,6 +719,20 @@ IN_PROC_BROWSER_TEST_F(DeclarativeContentApiTest, testing::ContainsRegex("compound selector.*: div input$")); } +// Tests that the rules with isBookmarked: true are evaluated when handling +// bookmarking events. +IN_PROC_BROWSER_TEST_F(DeclarativeContentApiTest, + IsBookmarkedRulesEvaluatedOnBookmarkEvents) { + CheckBookmarkEvents(true); +} + +// Tests that the rules with isBookmarked: false are evaluated when handling +// bookmarking events. +IN_PROC_BROWSER_TEST_F(DeclarativeContentApiTest, + NotBookmarkedRulesEvaluatedOnBookmarkEvents) { + CheckBookmarkEvents(false); +} + // https://crbug.com/497586 IN_PROC_BROWSER_TEST_F(DeclarativeContentApiTest, WebContentsWithoutTabAddedNotificationAtOnLoaded) { diff --git a/chrome/browser/extensions/api/declarative_content/declarative_content_condition_tracker_delegate.h b/chrome/browser/extensions/api/declarative_content/declarative_content_condition_tracker_delegate.h index 94cbb74..8279127 100644 --- a/chrome/browser/extensions/api/declarative_content/declarative_content_condition_tracker_delegate.h +++ b/chrome/browser/extensions/api/declarative_content/declarative_content_condition_tracker_delegate.h @@ -19,8 +19,8 @@ namespace extensions { class DeclarativeContentConditionTrackerDelegate { public: // Requests re-evaluation of conditions for |contents|. This must be called - // whenever the state monitored by the condition changes, even if the value of - // the condition itself doesn't change. + // whenever the URL or page state changes, even if the value of the condition + // itself doesn't change. virtual void RequestEvaluation(content::WebContents* contents) = 0; // Returns true if the evaluator should manage condition state for |context|. diff --git a/chrome/browser/extensions/api/declarative_content/declarative_content_condition_tracker_test.cc b/chrome/browser/extensions/api/declarative_content/declarative_content_condition_tracker_test.cc index 3fd5321..26cad63 100644 --- a/chrome/browser/extensions/api/declarative_content/declarative_content_condition_tracker_test.cc +++ b/chrome/browser/extensions/api/declarative_content/declarative_content_condition_tracker_test.cc @@ -14,7 +14,7 @@ namespace extensions { DeclarativeContentConditionTrackerTest::DeclarativeContentConditionTrackerTest() - : browser_context_(new TestingProfile) {} + : profile_(new TestingProfile) {} DeclarativeContentConditionTrackerTest:: ~DeclarativeContentConditionTrackerTest() { @@ -27,7 +27,7 @@ DeclarativeContentConditionTrackerTest:: scoped_ptr<content::WebContents> DeclarativeContentConditionTrackerTest::MakeTab() { return make_scoped_ptr(content::WebContentsTester::CreateTestWebContents( - browser_context_.get(), + profile_.get(), nullptr)); } diff --git a/chrome/browser/extensions/api/declarative_content/declarative_content_condition_tracker_test.h b/chrome/browser/extensions/api/declarative_content/declarative_content_condition_tracker_test.h index dd88a27..5f79c83 100644 --- a/chrome/browser/extensions/api/declarative_content/declarative_content_condition_tracker_test.h +++ b/chrome/browser/extensions/api/declarative_content/declarative_content_condition_tracker_test.h @@ -7,6 +7,7 @@ #include "base/memory/scoped_ptr.h" #include "chrome/browser/extensions/api/declarative_content/declarative_content_condition_tracker_delegate.h" +#include "chrome/test/base/testing_profile.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/render_view_host.h" #include "content/public/test/browser_test_utils.h" @@ -38,7 +39,7 @@ class DeclarativeContentConditionTrackerTest : public testing::Test { content::MockRenderProcessHost* GetMockRenderProcessHost( content::WebContents* contents); - content::BrowserContext* browser_context() { return browser_context_.get(); } + TestingProfile* profile() { return profile_.get(); } private: content::TestBrowserThreadBundle thread_bundle_; @@ -46,7 +47,7 @@ class DeclarativeContentConditionTrackerTest : public testing::Test { // Enables MockRenderProcessHosts. content::RenderViewHostTestEnabler render_view_host_test_enabler_; - const scoped_ptr<content::BrowserContext> browser_context_; + const scoped_ptr<TestingProfile> profile_; DISALLOW_COPY_AND_ASSIGN(DeclarativeContentConditionTrackerTest); }; diff --git a/chrome/browser/extensions/api/declarative_content/declarative_content_css_condition_tracker_unittest.cc b/chrome/browser/extensions/api/declarative_content/declarative_content_css_condition_tracker_unittest.cc index c6698bf..8a42a8e 100644 --- a/chrome/browser/extensions/api/declarative_content/declarative_content_css_condition_tracker_unittest.cc +++ b/chrome/browser/extensions/api/declarative_content/declarative_content_css_condition_tracker_unittest.cc @@ -80,8 +80,7 @@ class DeclarativeContentCssConditionTrackerTest // Tests the basic flow of operations on the // DeclarativeContentCssConditionTracker. TEST_F(DeclarativeContentCssConditionTrackerTest, Basic) { - DeclarativeContentCssConditionTracker tracker(browser_context(), - &delegate_); + DeclarativeContentCssConditionTracker tracker(profile(), &delegate_); int expected_evaluation_requests = 0; const scoped_ptr<content::WebContents> tab = MakeTab(); @@ -146,8 +145,7 @@ TEST_F(DeclarativeContentCssConditionTrackerTest, WebContentsOutlivesTracker) { const scoped_ptr<content::WebContents> tab = MakeTab(); { - DeclarativeContentCssConditionTracker tracker(browser_context(), - &delegate_); + DeclarativeContentCssConditionTracker tracker(profile(), &delegate_); tracker.TrackForWebContents(tab.get()); } } diff --git a/chrome/browser/extensions/api/declarative_content/declarative_content_is_bookmarked_condition_tracker.cc b/chrome/browser/extensions/api/declarative_content/declarative_content_is_bookmarked_condition_tracker.cc new file mode 100644 index 0000000..a678f02 --- /dev/null +++ b/chrome/browser/extensions/api/declarative_content/declarative_content_is_bookmarked_condition_tracker.cc @@ -0,0 +1,193 @@ +// Copyright 2015 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/declarative_content_is_bookmarked_condition_tracker.h" + +#include "base/bind.h" +#include "base/stl_util.h" +#include "chrome/browser/bookmarks/bookmark_model_factory.h" +#include "chrome/browser/profiles/profile.h" +#include "components/bookmarks/browser/bookmark_model.h" +#include "content/public/browser/web_contents.h" + +namespace extensions { + +// +// PerWebContentsTracker +// + +DeclarativeContentIsBookmarkedConditionTracker::PerWebContentsTracker:: +PerWebContentsTracker( + content::WebContents* contents, + const RequestEvaluationCallback& request_evaluation, + const WebContentsDestroyedCallback& web_contents_destroyed) + : WebContentsObserver(contents), + request_evaluation_(request_evaluation), + web_contents_destroyed_(web_contents_destroyed) { + is_url_bookmarked_ = IsCurrentUrlBookmarked(); + request_evaluation_.Run(web_contents()); +} + +DeclarativeContentIsBookmarkedConditionTracker::PerWebContentsTracker:: +~PerWebContentsTracker() { +} + +void DeclarativeContentIsBookmarkedConditionTracker::PerWebContentsTracker:: +BookmarkAddedForUrl(const GURL& url) { + if (web_contents()->GetVisibleURL() == url) { + is_url_bookmarked_ = true; + request_evaluation_.Run(web_contents()); + } +} + +void DeclarativeContentIsBookmarkedConditionTracker::PerWebContentsTracker:: +BookmarkRemovedForUrls(const std::set<GURL>& urls) { + if (ContainsKey(urls, web_contents()->GetVisibleURL())) { + is_url_bookmarked_ = false; + request_evaluation_.Run(web_contents()); + } +} + +void DeclarativeContentIsBookmarkedConditionTracker::PerWebContentsTracker:: +UpdateState(bool request_evaluation_if_unchanged) { + bool state_changed = + IsCurrentUrlBookmarked() != is_url_bookmarked_; + if (state_changed) + is_url_bookmarked_ = !is_url_bookmarked_; + if (state_changed || request_evaluation_if_unchanged) + request_evaluation_.Run(web_contents()); +} + +bool DeclarativeContentIsBookmarkedConditionTracker::PerWebContentsTracker:: +IsCurrentUrlBookmarked() { + bookmarks::BookmarkModel* bookmark_model = + BookmarkModelFactory::GetForProfile( + Profile::FromBrowserContext(web_contents()->GetBrowserContext())); + // BookmarkModel can be null during unit test execution. + return bookmark_model && + bookmark_model->IsBookmarked(web_contents()->GetVisibleURL()); +} + +void DeclarativeContentIsBookmarkedConditionTracker::PerWebContentsTracker:: +WebContentsDestroyed() { + web_contents_destroyed_.Run(web_contents()); +} + +// +// DeclarativeContentIsBookmarkedConditionTracker +// + +DeclarativeContentIsBookmarkedConditionTracker:: +DeclarativeContentIsBookmarkedConditionTracker( + content::BrowserContext* context, + DeclarativeContentConditionTrackerDelegate* delegate) + : extensive_bookmark_changes_in_progress_(0), + delegate_(delegate), + scoped_bookmarks_observer_(this) { + bookmarks::BookmarkModel* bookmark_model = + BookmarkModelFactory::GetForProfile(Profile::FromBrowserContext(context)); + // Can be null during unit test execution. + if (bookmark_model) + scoped_bookmarks_observer_.Add(bookmark_model); +} + +DeclarativeContentIsBookmarkedConditionTracker:: +~DeclarativeContentIsBookmarkedConditionTracker() { +} + +void DeclarativeContentIsBookmarkedConditionTracker::TrackForWebContents( + content::WebContents* contents) { + per_web_contents_tracker_[contents] = + make_linked_ptr(new PerWebContentsTracker( + contents, + base::Bind(&DeclarativeContentConditionTrackerDelegate:: + RequestEvaluation, + base::Unretained(delegate_)), + base::Bind(&DeclarativeContentIsBookmarkedConditionTracker:: + DeletePerWebContentsTracker, + base::Unretained(this)))); +} + +void DeclarativeContentIsBookmarkedConditionTracker::OnWebContentsNavigation( + content::WebContents* contents, + const content::LoadCommittedDetails& details, + const content::FrameNavigateParams& params) { + DCHECK(ContainsKey(per_web_contents_tracker_, contents)); + per_web_contents_tracker_[contents]->UpdateState(true); +} + +bool DeclarativeContentIsBookmarkedConditionTracker::IsUrlBookmarked( + content::WebContents* contents) { + DCHECK(ContainsKey(per_web_contents_tracker_, contents)); + return per_web_contents_tracker_[contents]->is_url_bookmarked(); +} + +void DeclarativeContentIsBookmarkedConditionTracker::BookmarkModelChanged() {} + +void DeclarativeContentIsBookmarkedConditionTracker::BookmarkNodeAdded( + bookmarks::BookmarkModel* model, + const bookmarks::BookmarkNode* parent, + int index) { + if (!extensive_bookmark_changes_in_progress_) { + for (const auto& web_contents_tracker_pair : per_web_contents_tracker_) { + web_contents_tracker_pair.second->BookmarkAddedForUrl( + parent->GetChild(index)->url()); + } + } +} + +void DeclarativeContentIsBookmarkedConditionTracker::BookmarkNodeRemoved( + bookmarks::BookmarkModel* model, + const bookmarks::BookmarkNode* parent, + int old_index, + const bookmarks::BookmarkNode* node, + const std::set<GURL>& no_longer_bookmarked) { + if (!extensive_bookmark_changes_in_progress_) { + for (const auto& web_contents_tracker_pair : per_web_contents_tracker_) { + web_contents_tracker_pair.second->BookmarkRemovedForUrls( + no_longer_bookmarked); + } + } +} + +void DeclarativeContentIsBookmarkedConditionTracker:: +ExtensiveBookmarkChangesBeginning( + bookmarks::BookmarkModel* model) { + ++extensive_bookmark_changes_in_progress_; +} + +void +DeclarativeContentIsBookmarkedConditionTracker::ExtensiveBookmarkChangesEnded( + bookmarks::BookmarkModel* model) { + if (--extensive_bookmark_changes_in_progress_ == 0) + UpdateAllPerWebContentsTrackers(); +} + +void +DeclarativeContentIsBookmarkedConditionTracker::GroupedBookmarkChangesBeginning( + bookmarks::BookmarkModel* model) { + ++extensive_bookmark_changes_in_progress_; +} + +void +DeclarativeContentIsBookmarkedConditionTracker::GroupedBookmarkChangesEnded( + bookmarks::BookmarkModel* model) { + if (--extensive_bookmark_changes_in_progress_ == 0) + UpdateAllPerWebContentsTrackers(); +} + +void +DeclarativeContentIsBookmarkedConditionTracker::DeletePerWebContentsTracker( + content::WebContents* contents) { + DCHECK(ContainsKey(per_web_contents_tracker_, contents)); + per_web_contents_tracker_.erase(contents); +} + +void DeclarativeContentIsBookmarkedConditionTracker:: +UpdateAllPerWebContentsTrackers() { + for (const auto& web_contents_tracker_pair : per_web_contents_tracker_) + web_contents_tracker_pair.second->UpdateState(false); +} + +} // namespace extensions diff --git a/chrome/browser/extensions/api/declarative_content/declarative_content_is_bookmarked_condition_tracker.h b/chrome/browser/extensions/api/declarative_content/declarative_content_is_bookmarked_condition_tracker.h new file mode 100644 index 0000000..ac0a4ef --- /dev/null +++ b/chrome/browser/extensions/api/declarative_content/declarative_content_is_bookmarked_condition_tracker.h @@ -0,0 +1,133 @@ +// Copyright 2015 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_DECLARATIVE_CONTENT_IS_BOOKMARKED_CONDITION_TRACKER_H_ +#define CHROME_BROWSER_EXTENSIONS_API_DECLARATIVE_CONTENT_DECLARATIVE_CONTENT_IS_BOOKMARKED_CONDITION_TRACKER_H_ + +#include <map> + +#include "base/callback.h" +#include "base/memory/linked_ptr.h" +#include "base/scoped_observer.h" +#include "chrome/browser/extensions/api/declarative_content/declarative_content_condition_tracker_delegate.h" +#include "components/bookmarks/browser/base_bookmark_model_observer.h" +#include "content/public/browser/web_contents_observer.h" + +namespace content { +class BrowserContext; +struct FrameNavigateParams; +struct LoadCommittedDetails; +class WebContents; +} + +namespace extensions { + +// Supports tracking of URL matches across tab contents in a browser context, +// and querying for the matching condition sets. +class DeclarativeContentIsBookmarkedConditionTracker + : public bookmarks::BaseBookmarkModelObserver { + public: + DeclarativeContentIsBookmarkedConditionTracker( + content::BrowserContext* context, + DeclarativeContentConditionTrackerDelegate* delegate); + ~DeclarativeContentIsBookmarkedConditionTracker() override; + + // Requests that URL matches be tracked for |contents|. + void TrackForWebContents(content::WebContents* contents); + + // Handles navigation of |contents|. We depend on the caller to notify us of + // this event rather than listening for it ourselves, so that the caller can + // coordinate evaluation with all the trackers that respond to it. If we + // listened ourselves and requested rule evaluation before another tracker + // received the notification, our conditions would be evaluated based on the + // new URL while the other tracker's conditions would still be evaluated based + // on the previous URL. + void OnWebContentsNavigation(content::WebContents* contents, + const content::LoadCommittedDetails& details, + const content::FrameNavigateParams& params); + + // Returns true if |contents| current URL is bookmarked. + bool IsUrlBookmarked(content::WebContents* contents); + + private: + class PerWebContentsTracker : public content::WebContentsObserver { + public: + using RequestEvaluationCallback = + base::Callback<void(content::WebContents*)>; + using WebContentsDestroyedCallback = + base::Callback<void(content::WebContents*)>; + + PerWebContentsTracker( + content::WebContents* contents, + const RequestEvaluationCallback& request_evaluation, + const WebContentsDestroyedCallback& web_contents_destroyed); + ~PerWebContentsTracker() override; + + void BookmarkAddedForUrl(const GURL& url); + void BookmarkRemovedForUrls(const std::set<GURL>& urls); + void UpdateState(bool request_evaluation_if_unchanged); + + bool is_url_bookmarked() { + return is_url_bookmarked_; + } + + private: + bool IsCurrentUrlBookmarked(); + + // content::WebContentsObserver + void WebContentsDestroyed() override; + + bool is_url_bookmarked_; + const RequestEvaluationCallback request_evaluation_; + const WebContentsDestroyedCallback web_contents_destroyed_; + + DISALLOW_COPY_AND_ASSIGN(PerWebContentsTracker); + }; + + // bookmarks::BookmarkModelObserver implementation. + void BookmarkModelChanged() override; + void BookmarkNodeAdded(bookmarks::BookmarkModel* model, + const bookmarks::BookmarkNode* parent, + int index) override; + void BookmarkNodeRemoved(bookmarks::BookmarkModel* model, + const bookmarks::BookmarkNode* parent, + int old_index, + const bookmarks::BookmarkNode* node, + const std::set<GURL>& no_longer_bookmarked) override; + void ExtensiveBookmarkChangesBeginning( + bookmarks::BookmarkModel* model) override; + void ExtensiveBookmarkChangesEnded( + bookmarks::BookmarkModel* model) override; + void GroupedBookmarkChangesBeginning( + bookmarks::BookmarkModel* model) override; + void GroupedBookmarkChangesEnded( + bookmarks::BookmarkModel* model) override; + + // Called by PerWebContentsTracker on web contents destruction. + void DeletePerWebContentsTracker(content::WebContents* tracker); + + // Updates the bookmark state of all per-WebContents trackers. + void UpdateAllPerWebContentsTrackers(); + + // Maps WebContents to the tracker for that WebContents state. + std::map<content::WebContents*, + linked_ptr<PerWebContentsTracker>> per_web_contents_tracker_; + + // Count of the number of extensive bookmarks changes in progress (e.g. due to + // sync). The rules need only be evaluated once after the extensive changes + // are complete. + int extensive_bookmark_changes_in_progress_; + + // Weak. + DeclarativeContentConditionTrackerDelegate* delegate_; + + ScopedObserver<bookmarks::BookmarkModel, bookmarks::BookmarkModelObserver> + scoped_bookmarks_observer_; + + DISALLOW_COPY_AND_ASSIGN(DeclarativeContentIsBookmarkedConditionTracker); +}; + +} // namespace extensions + +#endif // CHROME_BROWSER_EXTENSIONS_API_DECLARATIVE_CONTENT_DECLARATIVE_CONTENT_IS_BOOKMARKED_CONDITION_TRACKER_H_ diff --git a/chrome/browser/extensions/api/declarative_content/declarative_content_is_bookmarked_condition_tracker_unittest.cc b/chrome/browser/extensions/api/declarative_content/declarative_content_is_bookmarked_condition_tracker_unittest.cc new file mode 100644 index 0000000..a09a53a --- /dev/null +++ b/chrome/browser/extensions/api/declarative_content/declarative_content_is_bookmarked_condition_tracker_unittest.cc @@ -0,0 +1,296 @@ +// Copyright 2015 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/declarative_content_is_bookmarked_condition_tracker.h" + +#include <set> +#include <utility> + +#include "base/memory/scoped_vector.h" +#include "base/stl_util.h" +#include "base/strings/utf_string_conversions.h" +#include "chrome/browser/bookmarks/bookmark_model_factory.h" +#include "chrome/browser/extensions/api/declarative_content/declarative_content_condition_tracker_test.h" +#include "chrome/test/base/testing_profile.h" +#include "components/bookmarks/browser/bookmark_model.h" +#include "components/bookmarks/browser/scoped_group_bookmark_actions.h" +#include "components/bookmarks/test/bookmark_test_helpers.h" +#include "content/public/browser/navigation_controller.h" +#include "content/public/browser/navigation_details.h" +#include "content/public/browser/web_contents.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace extensions { + +using testing::UnorderedElementsAre; +using testing::UnorderedElementsAreArray; + +class DeclarativeContentIsBookmarkedConditionTrackerTest + : public DeclarativeContentConditionTrackerTest { + protected: + class Delegate : public DeclarativeContentConditionTrackerDelegate { + public: + Delegate() {} + + std::set<content::WebContents*>& evaluation_requests() { + return evaluation_requests_; + } + + // DeclarativeContentConditionTrackerDelegate: + void RequestEvaluation(content::WebContents* contents) override { + EXPECT_FALSE(ContainsKey(evaluation_requests_, contents)); + evaluation_requests_.insert(contents); + } + + bool ShouldManageConditionsForBrowserContext( + content::BrowserContext* context) override { + return true; + } + + private: + std::set<content::WebContents*> evaluation_requests_; + + DISALLOW_COPY_AND_ASSIGN(Delegate); + }; + + DeclarativeContentIsBookmarkedConditionTrackerTest() { + profile()->CreateBookmarkModel(true); + bookmarks::test::WaitForBookmarkModelToLoad( + BookmarkModelFactory::GetForProfile(profile())); + bookmark_model_ = BookmarkModelFactory::GetForProfile(profile()); + } + + void LoadURL(content::WebContents* tab, const GURL& url) { + tab->GetController().LoadURL(url, content::Referrer(), + ui::PAGE_TRANSITION_LINK, std::string()); + } + + Delegate delegate_; + bookmarks::BookmarkModel* bookmark_model_; + + private: + DISALLOW_COPY_AND_ASSIGN(DeclarativeContentIsBookmarkedConditionTrackerTest); +}; + + +// Tests that starting tracking for a WebContents that has a bookmarked URL +// results in the proper IsUrlBookmarked state. +TEST_F(DeclarativeContentIsBookmarkedConditionTrackerTest, + BookmarkedAtStartOfTracking) { + DeclarativeContentIsBookmarkedConditionTracker tracker(profile(), &delegate_); + + scoped_ptr<content::WebContents> tab = MakeTab(); + LoadURL(tab.get(), GURL("http://bookmarked/")); + EXPECT_TRUE(delegate_.evaluation_requests().empty()); + + bookmark_model_->AddURL(bookmark_model_->other_node(), 0, + base::ASCIIToUTF16("title"), + GURL("http://bookmarked/")); + + tracker.TrackForWebContents(tab.get()); + EXPECT_THAT(delegate_.evaluation_requests(), UnorderedElementsAre(tab.get())); + EXPECT_TRUE(tracker.IsUrlBookmarked(tab.get())); +} + +// Tests that adding and removing bookmarks triggers evaluation requests for the +// matching WebContents. +TEST_F(DeclarativeContentIsBookmarkedConditionTrackerTest, + AddAndRemoveBookmark) { + DeclarativeContentIsBookmarkedConditionTracker tracker(profile(), &delegate_); + + // Create two tabs. + ScopedVector<content::WebContents> tabs; + for (int i = 0; i < 2; ++i) { + tabs.push_back(MakeTab()); + delegate_.evaluation_requests().clear(); + tracker.TrackForWebContents(tabs.back()); + EXPECT_THAT(delegate_.evaluation_requests(), + UnorderedElementsAre(tabs.back())); + EXPECT_FALSE(tracker.IsUrlBookmarked(tabs.back())); + } + + // Navigate the first tab to a URL that we will bookmark. + delegate_.evaluation_requests().clear(); + LoadURL(tabs[0], GURL("http://bookmarked/")); + tracker.OnWebContentsNavigation(tabs[0], content::LoadCommittedDetails(), + content::FrameNavigateParams()); + EXPECT_THAT(delegate_.evaluation_requests(), UnorderedElementsAre(tabs[0])); + EXPECT_FALSE(tracker.IsUrlBookmarked(tabs[0])); + EXPECT_FALSE(tracker.IsUrlBookmarked(tabs[1])); + + // Bookmark the first tab's URL. + delegate_.evaluation_requests().clear(); + const bookmarks::BookmarkNode* node = + bookmark_model_->AddURL(bookmark_model_->other_node(), 0, + base::ASCIIToUTF16("title"), + GURL("http://bookmarked/")); + EXPECT_THAT(delegate_.evaluation_requests(), UnorderedElementsAre(tabs[0])); + EXPECT_TRUE(tracker.IsUrlBookmarked(tabs[0])); + EXPECT_FALSE(tracker.IsUrlBookmarked(tabs[1])); + + // Remove the bookmark. + delegate_.evaluation_requests().clear(); + bookmark_model_->Remove(node); + EXPECT_THAT(delegate_.evaluation_requests(), UnorderedElementsAre(tabs[0])); + EXPECT_FALSE(tracker.IsUrlBookmarked(tabs[0])); + EXPECT_FALSE(tracker.IsUrlBookmarked(tabs[1])); +} + +// Tests that adding and removing bookmarks triggers evaluation requests for the +// matching WebContents. +TEST_F(DeclarativeContentIsBookmarkedConditionTrackerTest, ExtensiveChanges) { + DeclarativeContentIsBookmarkedConditionTracker tracker(profile(), &delegate_); + + // Create two tabs. + ScopedVector<content::WebContents> tabs; + for (int i = 0; i < 2; ++i) { + tabs.push_back(MakeTab()); + delegate_.evaluation_requests().clear(); + tracker.TrackForWebContents(tabs.back()); + EXPECT_THAT(delegate_.evaluation_requests(), + UnorderedElementsAre(tabs.back())); + EXPECT_FALSE(tracker.IsUrlBookmarked(tabs.back())); + } + + // Navigate the first tab to a URL that we will bookmark. + delegate_.evaluation_requests().clear(); + LoadURL(tabs[0], GURL("http://bookmarked/")); + tracker.OnWebContentsNavigation(tabs[0], content::LoadCommittedDetails(), + content::FrameNavigateParams()); + EXPECT_THAT(delegate_.evaluation_requests(), UnorderedElementsAre(tabs[0])); + EXPECT_FALSE(tracker.IsUrlBookmarked(tabs[0])); + EXPECT_FALSE(tracker.IsUrlBookmarked(tabs[1])); + + { + // Check that evaluation requests occur outside ExtensiveBookmarkChanges for + // added nodes. + delegate_.evaluation_requests().clear(); + bookmark_model_->BeginExtensiveChanges(); + const bookmarks::BookmarkNode* node = + bookmark_model_->AddURL(bookmark_model_->other_node(), 0, + base::ASCIIToUTF16("title"), + GURL("http://bookmarked/")); + EXPECT_TRUE(delegate_.evaluation_requests().empty()); + EXPECT_FALSE(tracker.IsUrlBookmarked(tabs[0])); + EXPECT_FALSE(tracker.IsUrlBookmarked(tabs[1])); + bookmark_model_->EndExtensiveChanges(); + EXPECT_THAT(delegate_.evaluation_requests(), UnorderedElementsAre(tabs[0])); + EXPECT_TRUE(tracker.IsUrlBookmarked(tabs[0])); + EXPECT_FALSE(tracker.IsUrlBookmarked(tabs[1])); + + // Check that evaluation requests occur outside ExtensiveBookmarkChanges for + // removed nodes. + delegate_.evaluation_requests().clear(); + bookmark_model_->BeginExtensiveChanges(); + bookmark_model_->Remove(node); + EXPECT_TRUE(delegate_.evaluation_requests().empty()); + EXPECT_TRUE(tracker.IsUrlBookmarked(tabs[0])); + EXPECT_FALSE(tracker.IsUrlBookmarked(tabs[1])); + bookmark_model_->EndExtensiveChanges(); + EXPECT_THAT(delegate_.evaluation_requests(), UnorderedElementsAre(tabs[0])); + EXPECT_FALSE(tracker.IsUrlBookmarked(tabs[0])); + EXPECT_FALSE(tracker.IsUrlBookmarked(tabs[1])); + } + + { + // Check that evaluation requests occur outside ScopedGroupBookmarkActions + // for added nodes. + delegate_.evaluation_requests().clear(); + const bookmarks::BookmarkNode* node = nullptr; + { + bookmarks::ScopedGroupBookmarkActions scoped_group(bookmark_model_); + node = bookmark_model_->AddURL(bookmark_model_->other_node(), 0, + base::ASCIIToUTF16("title"), + GURL("http://bookmarked/")); + EXPECT_TRUE(delegate_.evaluation_requests().empty()); + EXPECT_FALSE(tracker.IsUrlBookmarked(tabs[0])); + EXPECT_FALSE(tracker.IsUrlBookmarked(tabs[1])); + } + EXPECT_THAT(delegate_.evaluation_requests(), UnorderedElementsAre(tabs[0])); + EXPECT_TRUE(tracker.IsUrlBookmarked(tabs[0])); + EXPECT_FALSE(tracker.IsUrlBookmarked(tabs[1])); + + // Check that evaluation requests occur outside ScopedGroupBookmarkActions + // for removed nodes. + delegate_.evaluation_requests().clear(); + { + bookmarks::ScopedGroupBookmarkActions scoped_group(bookmark_model_); + bookmark_model_->Remove(node); + EXPECT_TRUE(delegate_.evaluation_requests().empty()); + EXPECT_TRUE(tracker.IsUrlBookmarked(tabs[0])); + EXPECT_FALSE(tracker.IsUrlBookmarked(tabs[1])); + } + EXPECT_THAT(delegate_.evaluation_requests(), UnorderedElementsAre(tabs[0])); + EXPECT_FALSE(tracker.IsUrlBookmarked(tabs[0])); + EXPECT_FALSE(tracker.IsUrlBookmarked(tabs[1])); + } +} + +// Tests that navigation to bookmarked and non-bookmarked URLs triggers +// evaluation requests for the relevant WebContents. +TEST_F(DeclarativeContentIsBookmarkedConditionTrackerTest, Navigation) { + DeclarativeContentIsBookmarkedConditionTracker tracker(profile(), &delegate_); + + // Bookmark two URLs. + delegate_.evaluation_requests().clear(); + bookmark_model_->AddURL(bookmark_model_->other_node(), 0, + base::ASCIIToUTF16("title"), + GURL("http://bookmarked1/")); + bookmark_model_->AddURL(bookmark_model_->other_node(), 0, + base::ASCIIToUTF16("title"), + GURL("http://bookmarked2/")); + + // Create two tabs. + ScopedVector<content::WebContents> tabs; + for (int i = 0; i < 2; ++i) { + tabs.push_back(MakeTab()); + delegate_.evaluation_requests().clear(); + tracker.TrackForWebContents(tabs.back()); + EXPECT_THAT(delegate_.evaluation_requests(), + UnorderedElementsAre(tabs.back())); + EXPECT_FALSE(tracker.IsUrlBookmarked(tabs.back())); + } + + // Navigate the first tab to one bookmarked URL. + delegate_.evaluation_requests().clear(); + LoadURL(tabs[0], GURL("http://bookmarked1/")); + tracker.OnWebContentsNavigation(tabs[0], content::LoadCommittedDetails(), + content::FrameNavigateParams()); + EXPECT_THAT(delegate_.evaluation_requests(), UnorderedElementsAre(tabs[0])); + EXPECT_TRUE(tracker.IsUrlBookmarked(tabs[0])); + EXPECT_FALSE(tracker.IsUrlBookmarked(tabs[1])); + + // Navigate the first tab to another bookmarked URL. The contents have + // changed, so we should receive a new evaluation request even though the + // bookmarked state hasn't. + delegate_.evaluation_requests().clear(); + LoadURL(tabs[0], GURL("http://bookmarked2/")); + tracker.OnWebContentsNavigation(tabs[0], content::LoadCommittedDetails(), + content::FrameNavigateParams()); + EXPECT_THAT(delegate_.evaluation_requests(), UnorderedElementsAre(tabs[0])); + EXPECT_TRUE(tracker.IsUrlBookmarked(tabs[0])); + EXPECT_FALSE(tracker.IsUrlBookmarked(tabs[1])); + + // Navigate the first tab to a non-bookmarked URL. + delegate_.evaluation_requests().clear(); + LoadURL(tabs[0], GURL("http://not-bookmarked1/")); + tracker.OnWebContentsNavigation(tabs[0], content::LoadCommittedDetails(), + content::FrameNavigateParams()); + EXPECT_THAT(delegate_.evaluation_requests(), UnorderedElementsAre(tabs[0])); + EXPECT_FALSE(tracker.IsUrlBookmarked(tabs[0])); + EXPECT_FALSE(tracker.IsUrlBookmarked(tabs[1])); + + // Navigate the first tab to another non-bookmarked URL. The contents have + // changed, so we should receive a new evaluation request even though the + // bookmarked state hasn't. + delegate_.evaluation_requests().clear(); + LoadURL(tabs[0], GURL("http://not-bookmarked2/")); + tracker.OnWebContentsNavigation(tabs[0], content::LoadCommittedDetails(), + content::FrameNavigateParams()); + EXPECT_THAT(delegate_.evaluation_requests(), UnorderedElementsAre(tabs[0])); + EXPECT_FALSE(tracker.IsUrlBookmarked(tabs[0])); + EXPECT_FALSE(tracker.IsUrlBookmarked(tabs[1])); +} + +} // namespace extensions diff --git a/chrome/browser/extensions/api/declarative_content/declarative_content_page_url_condition_tracker.cc b/chrome/browser/extensions/api/declarative_content/declarative_content_page_url_condition_tracker.cc index a1aa645..0199300 100644 --- a/chrome/browser/extensions/api/declarative_content/declarative_content_page_url_condition_tracker.cc +++ b/chrome/browser/extensions/api/declarative_content/declarative_content_page_url_condition_tracker.cc @@ -33,7 +33,7 @@ DeclarativeContentPageUrlConditionTracker::PerWebContentsTracker:: void DeclarativeContentPageUrlConditionTracker::PerWebContentsTracker:: UpdateMatchesForCurrentUrl(bool request_evaluation_if_unchanged) { std::set<url_matcher::URLMatcherConditionSet::ID> new_matches = - url_matcher_->MatchURL(web_contents()->GetURL()); + url_matcher_->MatchURL(web_contents()->GetVisibleURL()); matches_.swap(new_matches); if (matches_ != new_matches || request_evaluation_if_unchanged) request_evaluation_.Run(web_contents()); @@ -107,16 +107,16 @@ void DeclarativeContentPageUrlConditionTracker::GetMatches( *matches = per_web_contents_tracker_[contents]->matches(); } +bool DeclarativeContentPageUrlConditionTracker::IsEmpty() const { + return url_matcher_.IsEmpty(); +} + void DeclarativeContentPageUrlConditionTracker::DeletePerWebContentsTracker( content::WebContents* contents) { DCHECK(ContainsKey(per_web_contents_tracker_, contents)); per_web_contents_tracker_.erase(contents); } -bool DeclarativeContentPageUrlConditionTracker::IsEmpty() const { - return url_matcher_.IsEmpty(); -} - void DeclarativeContentPageUrlConditionTracker::UpdateMatchesForAllTrackers() { for (const auto& web_contents_tracker_pair : per_web_contents_tracker_) web_contents_tracker_pair.second->UpdateMatchesForCurrentUrl(false); diff --git a/chrome/browser/extensions/api/declarative_content/declarative_content_page_url_condition_tracker.h b/chrome/browser/extensions/api/declarative_content/declarative_content_page_url_condition_tracker.h index b5bb686..7535978 100644 --- a/chrome/browser/extensions/api/declarative_content/declarative_content_page_url_condition_tracker.h +++ b/chrome/browser/extensions/api/declarative_content/declarative_content_page_url_condition_tracker.h @@ -69,9 +69,6 @@ class DeclarativeContentPageUrlConditionTracker { void GetMatches(content::WebContents* contents, std::set<url_matcher::URLMatcherConditionSet::ID>* matches); - // Called by PerWebContentsTracker on web contents destruction. - void DeletePerWebContentsTracker(content::WebContents* tracker); - // Returns true if this object retains no allocated data. Only for debugging. bool IsEmpty() const; @@ -109,6 +106,9 @@ class DeclarativeContentPageUrlConditionTracker { DISALLOW_COPY_AND_ASSIGN(PerWebContentsTracker); }; + // Called by PerWebContentsTracker on web contents destruction. + void DeletePerWebContentsTracker(content::WebContents* tracker); + void UpdateMatchesForAllTrackers(); // Matches URLs for all WebContents. diff --git a/chrome/browser/extensions/api/declarative_content/declarative_content_page_url_condition_tracker_unittest.cc b/chrome/browser/extensions/api/declarative_content/declarative_content_page_url_condition_tracker_unittest.cc index 491fef9..7db53d5 100644 --- a/chrome/browser/extensions/api/declarative_content/declarative_content_page_url_condition_tracker_unittest.cc +++ b/chrome/browser/extensions/api/declarative_content/declarative_content_page_url_condition_tracker_unittest.cc @@ -64,8 +64,7 @@ class DeclarativeContentPageUrlConditionTrackerTest // the matching WebContents. TEST_F(DeclarativeContentPageUrlConditionTrackerTest, AddAndRemoveConditionSets) { - DeclarativeContentPageUrlConditionTracker tracker(browser_context(), - &delegate_); + DeclarativeContentPageUrlConditionTracker tracker(profile(), &delegate_); // Create two tabs. ScopedVector<content::WebContents> tabs; @@ -108,8 +107,7 @@ TEST_F(DeclarativeContentPageUrlConditionTrackerTest, // Tests that tracking WebContents triggers evaluation requests for matching // rules. TEST_F(DeclarativeContentPageUrlConditionTrackerTest, TrackWebContents) { - DeclarativeContentPageUrlConditionTracker tracker(browser_context(), - &delegate_); + DeclarativeContentPageUrlConditionTracker tracker(profile(), &delegate_); const int condition_set_id = 100; std::set<url_matcher::URLMatcherCondition> conditions; @@ -140,8 +138,7 @@ TEST_F(DeclarativeContentPageUrlConditionTrackerTest, TrackWebContents) { // matching rules. TEST_F(DeclarativeContentPageUrlConditionTrackerTest, NotifyWebContentsNavigation) { - DeclarativeContentPageUrlConditionTracker tracker(browser_context(), - &delegate_); + DeclarativeContentPageUrlConditionTracker tracker(profile(), &delegate_); const int condition_set_id = 100; std::set<url_matcher::URLMatcherCondition> conditions; diff --git a/chrome/browser/extensions/api/developer_private/extension_info_generator.cc b/chrome/browser/extensions/api/developer_private/extension_info_generator.cc index 7c0cec8..ed101d2 100644 --- a/chrome/browser/extensions/api/developer_private/extension_info_generator.cc +++ b/chrome/browser/extensions/api/developer_private/extension_info_generator.cc @@ -456,6 +456,10 @@ void ExtensionInfoGenerator::CreateExtensionInfoHelper( info->runtime_errors.push_back(ConstructRuntimeError( static_cast<const RuntimeError&>(*error))); break; + case ExtensionError::INTERNAL_ERROR: + // TODO(wittman): Support InternalError in developer tools: + // https://crbug.com/503427. + break; case ExtensionError::NUM_ERROR_TYPES: NOTREACHED(); break; diff --git a/chrome/browser/extensions/chrome_extensions_browser_client.cc b/chrome/browser/extensions/chrome_extensions_browser_client.cc index a1f2547..8859364 100644 --- a/chrome/browser/extensions/chrome_extensions_browser_client.cc +++ b/chrome/browser/extensions/chrome_extensions_browser_client.cc @@ -22,6 +22,7 @@ #include "chrome/browser/extensions/chrome_mojo_service_registration.h" #include "chrome/browser/extensions/chrome_process_manager_delegate.h" #include "chrome/browser/extensions/chrome_url_request_util.h" +#include "chrome/browser/extensions/error_console/error_console.h" #include "chrome/browser/extensions/event_router_forwarder.h" #include "chrome/browser/extensions/extension_system_factory.h" #include "chrome/browser/extensions/extension_util.h" @@ -324,4 +325,10 @@ ChromeExtensionsBrowserClient::GetExtensionWebContentsObserver( return ChromeExtensionWebContentsObserver::FromWebContents(web_contents); } +void ChromeExtensionsBrowserClient::ReportError( + content::BrowserContext* context, + scoped_ptr<ExtensionError> error) { + extensions::ErrorConsole::Get(context)->ReportError(error.Pass()); +} + } // namespace extensions diff --git a/chrome/browser/extensions/chrome_extensions_browser_client.h b/chrome/browser/extensions/chrome_extensions_browser_client.h index 10e0d71..afc2511 100644 --- a/chrome/browser/extensions/chrome_extensions_browser_client.h +++ b/chrome/browser/extensions/chrome_extensions_browser_client.h @@ -103,6 +103,8 @@ class ChromeExtensionsBrowserClient : public ExtensionsBrowserClient { bool IsMinBrowserVersionSupported(const std::string& min_version) override; ExtensionWebContentsObserver* GetExtensionWebContentsObserver( content::WebContents* web_contents) override; + void ReportError(content::BrowserContext* context, + scoped_ptr<ExtensionError> error) override; private: friend struct base::DefaultLazyInstanceTraits<ChromeExtensionsBrowserClient>; diff --git a/chrome/chrome_browser_extensions.gypi b/chrome/chrome_browser_extensions.gypi index c473555..b79348e 100644 --- a/chrome/chrome_browser_extensions.gypi +++ b/chrome/chrome_browser_extensions.gypi @@ -219,6 +219,8 @@ 'browser/extensions/api/declarative_content/declarative_content_condition_tracker_delegate.h', 'browser/extensions/api/declarative_content/declarative_content_css_condition_tracker.cc', 'browser/extensions/api/declarative_content/declarative_content_css_condition_tracker.h', + 'browser/extensions/api/declarative_content/declarative_content_is_bookmarked_condition_tracker.cc', + 'browser/extensions/api/declarative_content/declarative_content_is_bookmarked_condition_tracker.h', 'browser/extensions/api/declarative_content/declarative_content_page_url_condition_tracker.cc', 'browser/extensions/api/declarative_content/declarative_content_page_url_condition_tracker.h', 'browser/extensions/api/desktop_capture/desktop_capture_api.cc', diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi index 2ae5409..f0dc3ea 100644 --- a/chrome/chrome_tests_unit.gypi +++ b/chrome/chrome_tests_unit.gypi @@ -664,6 +664,7 @@ 'browser/extensions/api/declarative_content/declarative_content_condition_tracker_test.cc', 'browser/extensions/api/declarative_content/declarative_content_condition_tracker_test.h', 'browser/extensions/api/declarative_content/declarative_content_css_condition_tracker_unittest.cc', + 'browser/extensions/api/declarative_content/declarative_content_is_bookmarked_condition_tracker_unittest.cc', 'browser/extensions/api/declarative_content/declarative_content_page_url_condition_tracker_unittest.cc', 'browser/extensions/api/declarative_webrequest/webrequest_action_unittest.cc', 'browser/extensions/api/declarative_webrequest/webrequest_rules_registry_unittest.cc', diff --git a/chrome/common/extensions/api/declarative_content.json b/chrome/common/extensions/api/declarative_content.json index b90c319..adb05fe 100644 --- a/chrome/common/extensions/api/declarative_content.json +++ b/chrome/common/extensions/api/declarative_content.json @@ -40,6 +40,11 @@ // "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" } }, + "isBookmarked": { + "type": "boolean", + "description": "Matches if the bookmarked state of the page is equal to the specified value. Requres the <a href='declare_permissions'>bookmarks permission</a>.", + "optional": true + }, "instanceType": { "type": "string", "enum": ["declarativeContent.PageStateMatcher"], "nodoc": true diff --git a/chrome/common/extensions/docs/templates/intros/declarativeContent.html b/chrome/common/extensions/docs/templates/intros/declarativeContent.html index 187e7a8..f360e18 100644 --- a/chrome/common/extensions/docs/templates/intros/declarativeContent.html +++ b/chrome/common/extensions/docs/templates/intros/declarativeContent.html @@ -20,8 +20,8 @@ user clicks on it, you'll have to keep using the $(ref:pageAction) API. <p>As a <a href="events#declarative">declarative API</a>, this API lets you register rules on the -<code>$(ref:onPageChanged)</code> $(ref:events.Event event) object which -take an action (currently just <code>$(ref:ShowPageAction)</code>) when +<code>$(ref:onPageChanged)</code> $(ref:events.Event event) object which take an +action (<code>$(ref:ShowPageAction)</code> and <code>$(ref:SetIcon)</code>) when a set of conditions, represented as a <code>$(ref:PageStateMatcher)</code>, are met. </p> @@ -119,3 +119,11 @@ selector is <code>display:none</code> or one of its parent elements is <code>display:none</code>, it doesn't cause the condition to match. Elements styled with <code>visibility:hidden</code>, positioned off-screen, or hidden by other elements can still make your condition match.</p> + + +<h2 id="css-matching">Bookmarked State Matching</h2> + +<p>The $(ref:PageStateMatcher.isBookmarked) condition allows matching of the +bookmarked state of the current URL in the user's profile. To make use of this +condition the "bookmarks" permission must be declared in +the <a href="manifest">extension manifest</a></p> diff --git a/extensions/browser/api/declarative/rules_registry.cc b/extensions/browser/api/declarative/rules_registry.cc index ac18b08..16a5ab0 100644 --- a/extensions/browser/api/declarative/rules_registry.cc +++ b/extensions/browser/api/declarative/rules_registry.cc @@ -12,17 +12,21 @@ #include "base/metrics/histogram.h" #include "base/stl_util.h" #include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" #include "base/time/time.h" #include "base/values.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/notification_details.h" #include "content/public/browser/notification_source.h" #include "extensions/browser/api/declarative/rules_cache_delegate.h" +#include "extensions/browser/extension_error.h" #include "extensions/browser/extension_prefs.h" #include "extensions/browser/extension_system.h" +#include "extensions/browser/extensions_browser_client.h" #include "extensions/browser/state_store.h" #include "extensions/common/api/declarative/declarative_manifest_data.h" #include "extensions/common/extension.h" +#include "extensions/common/extensions_client.h" #include "extensions/common/manifest_constants.h" namespace extensions { @@ -247,14 +251,14 @@ void RulesRegistry::OnExtensionUnloaded(const Extension* extension) { DCHECK_CURRENTLY_ON(owner_thread()); std::string error = RemoveAllRulesImpl(extension->id()); if (!error.empty()) - LOG(ERROR) << error; + ReportInternalError(extension->id(), error); } void RulesRegistry::OnExtensionUninstalled(const Extension* extension) { DCHECK_CURRENTLY_ON(owner_thread()); std::string error = RemoveAllRulesNoStoreUpdate(extension->id(), true); if (!error.empty()) - LOG(ERROR) << error; + ReportInternalError(extension->id(), error); } void RulesRegistry::OnExtensionLoaded(const Extension* extension) { @@ -270,12 +274,12 @@ void RulesRegistry::OnExtensionLoaded(const Extension* extension) { std::string error = AddRulesInternal(extension->id(), manifest_rules, &manifest_rules_); if (!error.empty()) - LOG(ERROR) << error; + ReportInternalError(extension->id(), error); } } std::string error = AddRulesImpl(extension->id(), rules); if (!error.empty()) - LOG(ERROR) << error; + ReportInternalError(extension->id(), error); } size_t RulesRegistry::GetNumberOfUsedRuleIdentifiersForTesting() const { @@ -296,7 +300,18 @@ void RulesRegistry::DeserializeAndAddRules( scoped_ptr<base::Value> rules) { DCHECK_CURRENTLY_ON(owner_thread()); - AddRulesNoFill(extension_id, RulesFromValue(rules.get()), &rules_); + std::string error = + AddRulesNoFill(extension_id, RulesFromValue(rules.get()), &rules_); + if (!error.empty()) + ReportInternalError(extension_id, error); +} + +void RulesRegistry::ReportInternalError(const std::string& extension_id, + const std::string& error) { + scoped_ptr<ExtensionError> error_instance(new InternalError( + extension_id, base::ASCIIToUTF16(error), logging::LOG_ERROR)); + ExtensionsBrowserClient::Get()->ReportError(browser_context_, + error_instance.Pass()); } RulesRegistry::~RulesRegistry() { diff --git a/extensions/browser/api/declarative/rules_registry.h b/extensions/browser/api/declarative/rules_registry.h index 9421362..f8c9492 100644 --- a/extensions/browser/api/declarative/rules_registry.h +++ b/extensions/browser/api/declarative/rules_registry.h @@ -226,6 +226,11 @@ class RulesRegistry : public base::RefCountedThreadSafe<RulesRegistry> { void DeserializeAndAddRules(const std::string& extension_id, scoped_ptr<base::Value> rules); + // Reports an internal error with the specified params to the extensions + // client. + void ReportInternalError(const std::string& extension_id, + const std::string& error); + // The context to which this rules registry belongs. content::BrowserContext* browser_context_; diff --git a/extensions/browser/extension_error.cc b/extensions/browser/extension_error.cc index 605efbb..96ebdaa 100644 --- a/extensions/browser/extension_error.cc +++ b/extensions/browser/extension_error.cc @@ -58,7 +58,7 @@ scoped_ptr<DictionaryValue> ExtensionError::ToValue() const { return value.Pass(); } -std::string ExtensionError::PrintForTest() const { +std::string ExtensionError::GetDebugString() const { return std::string("Extension Error:") + "\n OTR: " + std::string(from_incognito_ ? "true" : "false") + "\n Level: " + base::IntToString(static_cast<int>(level_)) + @@ -109,8 +109,8 @@ scoped_ptr<DictionaryValue> ManifestError::ToValue() const { return value.Pass(); } -std::string ManifestError::PrintForTest() const { - return ExtensionError::PrintForTest() + +std::string ManifestError::GetDebugString() const { + return ExtensionError::GetDebugString() + "\n Type: ManifestError"; } @@ -184,8 +184,8 @@ scoped_ptr<DictionaryValue> RuntimeError::ToValue() const { return value.Pass(); } -std::string RuntimeError::PrintForTest() const { - std::string result = ExtensionError::PrintForTest() + +std::string RuntimeError::GetDebugString() const { + std::string result = ExtensionError::GetDebugString() + "\n Type: RuntimeError" "\n Context: " + context_url_.spec() + "\n Stack Trace: "; @@ -235,4 +235,31 @@ void RuntimeError::CleanUpInit() { source_ = stack_trace_[0].source; } +//////////////////////////////////////////////////////////////////////////////// +// InternalError + +InternalError::InternalError(const std::string& extension_id, + const base::string16& message, + logging::LogSeverity level) + : ExtensionError(ExtensionError::INTERNAL_ERROR, + extension_id, + false, // not incognito. + level, + base::string16(), + message) { +} + +InternalError::~InternalError() { +} + +std::string InternalError::GetDebugString() const { + return ExtensionError::GetDebugString() + + "\n Type: InternalError"; +} + +bool InternalError::IsEqualImpl(const ExtensionError* rhs) const { + // ExtensionError logic is sufficient for comparison. + return true; +} + } // namespace extensions diff --git a/extensions/browser/extension_error.h b/extensions/browser/extension_error.h index 7628f7e..5e0b4e1 100644 --- a/extensions/browser/extension_error.h +++ b/extensions/browser/extension_error.h @@ -26,6 +26,7 @@ class ExtensionError { enum Type { MANIFEST_ERROR = 0, RUNTIME_ERROR, + INTERNAL_ERROR, NUM_ERROR_TYPES, // Put new values above this. }; @@ -34,7 +35,7 @@ class ExtensionError { // Serializes the ExtensionError into JSON format. virtual scoped_ptr<base::DictionaryValue> ToValue() const; - virtual std::string PrintForTest() const; + virtual std::string GetDebugString() const; // Return true if this error and |rhs| are considered equal, and should be // grouped together. @@ -102,7 +103,7 @@ class ManifestError : public ExtensionError { scoped_ptr<base::DictionaryValue> ToValue() const override; - std::string PrintForTest() const override; + std::string GetDebugString() const override; const base::string16& manifest_key() const { return manifest_key_; } const base::string16& manifest_specific() const { return manifest_specific_; } @@ -139,7 +140,7 @@ class RuntimeError : public ExtensionError { scoped_ptr<base::DictionaryValue> ToValue() const override; - std::string PrintForTest() const override; + std::string GetDebugString() const override; const GURL& context_url() const { return context_url_; } const StackTrace& stack_trace() const { return stack_trace_; } @@ -175,6 +176,21 @@ class RuntimeError : public ExtensionError { DISALLOW_COPY_AND_ASSIGN(RuntimeError); }; +class InternalError : public ExtensionError { + public: + InternalError(const std::string& extension_id, + const base::string16& message, + logging::LogSeverity level); + ~InternalError() override; + + std::string GetDebugString() const override; + + private: + bool IsEqualImpl(const ExtensionError* rhs) const override; + + DISALLOW_COPY_AND_ASSIGN(InternalError); +}; + } // namespace extensions #endif // EXTENSIONS_BROWSER_EXTENSION_ERROR_H_ diff --git a/extensions/browser/extensions_browser_client.cc b/extensions/browser/extensions_browser_client.cc index ce89b9c..495ac50 100644 --- a/extensions/browser/extensions_browser_client.cc +++ b/extensions/browser/extensions_browser_client.cc @@ -5,6 +5,8 @@ #include "extensions/browser/extensions_browser_client.h" #include "base/basictypes.h" +#include "base/logging.h" +#include "extensions/browser/extension_error.h" namespace extensions { @@ -14,6 +16,11 @@ ExtensionsBrowserClient* g_client = NULL; } // namespace +void ExtensionsBrowserClient::ReportError(content::BrowserContext* context, + scoped_ptr<ExtensionError> error) { + LOG(ERROR) << error->GetDebugString(); +} + ExtensionsBrowserClient* ExtensionsBrowserClient::Get() { return g_client; } diff --git a/extensions/browser/extensions_browser_client.h b/extensions/browser/extensions_browser_client.h index 6e99f7f..269dc8e 100644 --- a/extensions/browser/extensions_browser_client.h +++ b/extensions/browser/extensions_browser_client.h @@ -40,6 +40,7 @@ class AppSorting; class ComponentExtensionResourceManager; class Extension; class ExtensionCache; +class ExtensionError; class ExtensionHostDelegate; class ExtensionPrefsObserver; class ExtensionSystem; @@ -213,6 +214,10 @@ class ExtensionsBrowserClient { // is left up to the embedder. virtual bool IsMinBrowserVersionSupported(const std::string& min_version) = 0; + // Embedders can override this function to handle extension errors. + virtual void ReportError(content::BrowserContext* context, + scoped_ptr<ExtensionError> error); + // Returns the ExtensionWebContentsObserver for the given |web_contents|. virtual ExtensionWebContentsObserver* GetExtensionWebContentsObserver( content::WebContents* web_contents) = 0; |