summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwittman <wittman@chromium.org>2015-06-24 10:47:40 -0700
committerCommit bot <commit-bot@chromium.org>2015-06-24 17:48:08 +0000
commitb3ee048a81b8a101c0f96ceb612d148fa3bde44f (patch)
tree66c665ca7202faef276e523e731a2e017bd27837
parent6d26d5737cdc4a4c223163eeb83881c11aade838 (diff)
downloadchromium_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}
-rw-r--r--chrome/browser/extensions/api/declarative_content/chrome_content_rules_registry.cc6
-rw-r--r--chrome/browser/extensions/api/declarative_content/chrome_content_rules_registry.h5
-rw-r--r--chrome/browser/extensions/api/declarative_content/chrome_content_rules_registry_unittest.cc30
-rw-r--r--chrome/browser/extensions/api/declarative_content/content_condition.cc59
-rw-r--r--chrome/browser/extensions/api/declarative_content/content_condition.h15
-rw-r--r--chrome/browser/extensions/api/declarative_content/content_condition_unittest.cc137
-rw-r--r--chrome/browser/extensions/api/declarative_content/content_constants.cc1
-rw-r--r--chrome/browser/extensions/api/declarative_content/content_constants.h1
-rw-r--r--chrome/browser/extensions/api/declarative_content/declarative_content_apitest.cc78
-rw-r--r--chrome/browser/extensions/api/declarative_content/declarative_content_condition_tracker_delegate.h4
-rw-r--r--chrome/browser/extensions/api/declarative_content/declarative_content_condition_tracker_test.cc4
-rw-r--r--chrome/browser/extensions/api/declarative_content/declarative_content_condition_tracker_test.h5
-rw-r--r--chrome/browser/extensions/api/declarative_content/declarative_content_css_condition_tracker_unittest.cc6
-rw-r--r--chrome/browser/extensions/api/declarative_content/declarative_content_is_bookmarked_condition_tracker.cc193
-rw-r--r--chrome/browser/extensions/api/declarative_content/declarative_content_is_bookmarked_condition_tracker.h133
-rw-r--r--chrome/browser/extensions/api/declarative_content/declarative_content_is_bookmarked_condition_tracker_unittest.cc296
-rw-r--r--chrome/browser/extensions/api/declarative_content/declarative_content_page_url_condition_tracker.cc10
-rw-r--r--chrome/browser/extensions/api/declarative_content/declarative_content_page_url_condition_tracker.h6
-rw-r--r--chrome/browser/extensions/api/declarative_content/declarative_content_page_url_condition_tracker_unittest.cc9
-rw-r--r--chrome/browser/extensions/api/developer_private/extension_info_generator.cc4
-rw-r--r--chrome/browser/extensions/chrome_extensions_browser_client.cc7
-rw-r--r--chrome/browser/extensions/chrome_extensions_browser_client.h2
-rw-r--r--chrome/chrome_browser_extensions.gypi2
-rw-r--r--chrome/chrome_tests_unit.gypi1
-rw-r--r--chrome/common/extensions/api/declarative_content.json5
-rw-r--r--chrome/common/extensions/docs/templates/intros/declarativeContent.html12
-rw-r--r--extensions/browser/api/declarative/rules_registry.cc25
-rw-r--r--extensions/browser/api/declarative/rules_registry.h5
-rw-r--r--extensions/browser/extension_error.cc37
-rw-r--r--extensions/browser/extension_error.h22
-rw-r--r--extensions/browser/extensions_browser_client.cc7
-rw-r--r--extensions/browser/extensions_browser_client.h5
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;