1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/extensions/api/declarative_content/declarative_content_is_bookmarked_condition_tracker.h"
6 
7 #include "base/bind.h"
8 #include "base/memory/ptr_util.h"
9 #include "base/stl_util.h"
10 #include "base/strings/stringprintf.h"
11 #include "base/values.h"
12 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
13 #include "content/public/browser/web_contents.h"
14 #include "extensions/common/api/declarative/declarative_constants.h"
15 #include "extensions/common/permissions/permissions_data.h"
16 
17 namespace extensions {
18 
19 namespace {
20 
21 const char kIsBookmarkedInvalidTypeOfParameter[] =
22     "Attribute '%s' has an invalid type";
23 const char kIsBookmarkedRequiresBookmarkPermission[] =
24     "Property 'isBookmarked' requires 'bookmarks' permission";
25 
HasBookmarkAPIPermission(const Extension * extension)26 bool HasBookmarkAPIPermission(const Extension* extension) {
27   return extension->permissions_data()->HasAPIPermission(
28       APIPermission::kBookmark);
29 }
30 
31 }  // namespace
32 
33 //
34 // DeclarativeContentIsBookmarkedPredicate
35 //
36 
37 DeclarativeContentIsBookmarkedPredicate::
~DeclarativeContentIsBookmarkedPredicate()38 ~DeclarativeContentIsBookmarkedPredicate() {
39 }
40 
IsIgnored() const41 bool DeclarativeContentIsBookmarkedPredicate::IsIgnored() const {
42   return !HasBookmarkAPIPermission(extension_.get());
43 }
44 
45 // static
46 std::unique_ptr<DeclarativeContentIsBookmarkedPredicate>
Create(ContentPredicateEvaluator * evaluator,const Extension * extension,const base::Value & value,std::string * error)47 DeclarativeContentIsBookmarkedPredicate::Create(
48     ContentPredicateEvaluator* evaluator,
49     const Extension* extension,
50     const base::Value& value,
51     std::string* error) {
52   bool is_bookmarked = false;
53   if (value.GetAsBoolean(&is_bookmarked)) {
54     if (!HasBookmarkAPIPermission(extension)) {
55       *error = kIsBookmarkedRequiresBookmarkPermission;
56       return std::unique_ptr<DeclarativeContentIsBookmarkedPredicate>();
57     } else {
58       return base::WrapUnique(new DeclarativeContentIsBookmarkedPredicate(
59           evaluator, extension, is_bookmarked));
60     }
61   } else {
62     *error = base::StringPrintf(kIsBookmarkedInvalidTypeOfParameter,
63                                 declarative_content_constants::kIsBookmarked);
64     return std::unique_ptr<DeclarativeContentIsBookmarkedPredicate>();
65   }
66 }
67 
68 ContentPredicateEvaluator*
GetEvaluator() const69 DeclarativeContentIsBookmarkedPredicate::GetEvaluator() const {
70   return evaluator_;
71 }
72 
73 DeclarativeContentIsBookmarkedPredicate::
DeclarativeContentIsBookmarkedPredicate(ContentPredicateEvaluator * evaluator,scoped_refptr<const Extension> extension,bool is_bookmarked)74 DeclarativeContentIsBookmarkedPredicate(
75     ContentPredicateEvaluator* evaluator,
76     scoped_refptr<const Extension> extension,
77     bool is_bookmarked)
78     : evaluator_(evaluator),
79       extension_(extension),
80       is_bookmarked_(is_bookmarked) {
81 }
82 
83 //
84 // PerWebContentsTracker
85 //
86 
87 DeclarativeContentIsBookmarkedConditionTracker::PerWebContentsTracker::
PerWebContentsTracker(content::WebContents * contents,const RequestEvaluationCallback & request_evaluation,const WebContentsDestroyedCallback & web_contents_destroyed)88 PerWebContentsTracker(
89     content::WebContents* contents,
90     const RequestEvaluationCallback& request_evaluation,
91     const WebContentsDestroyedCallback& web_contents_destroyed)
92     : WebContentsObserver(contents),
93       request_evaluation_(request_evaluation),
94       web_contents_destroyed_(web_contents_destroyed) {
95   is_url_bookmarked_ = IsCurrentUrlBookmarked();
96   request_evaluation_.Run(web_contents());
97 }
98 
99 DeclarativeContentIsBookmarkedConditionTracker::PerWebContentsTracker::
~PerWebContentsTracker()100 ~PerWebContentsTracker() {
101 }
102 
103 void DeclarativeContentIsBookmarkedConditionTracker::PerWebContentsTracker::
BookmarkAddedForUrl(const GURL & url)104 BookmarkAddedForUrl(const GURL& url) {
105   if (web_contents()->GetVisibleURL() == url) {
106     is_url_bookmarked_ = true;
107     request_evaluation_.Run(web_contents());
108   }
109 }
110 
111 void DeclarativeContentIsBookmarkedConditionTracker::PerWebContentsTracker::
BookmarkRemovedForUrls(const std::set<GURL> & urls)112 BookmarkRemovedForUrls(const std::set<GURL>& urls) {
113   if (base::Contains(urls, web_contents()->GetVisibleURL())) {
114     is_url_bookmarked_ = false;
115     request_evaluation_.Run(web_contents());
116   }
117 }
118 
119 void DeclarativeContentIsBookmarkedConditionTracker::PerWebContentsTracker::
UpdateState(bool request_evaluation_if_unchanged)120 UpdateState(bool request_evaluation_if_unchanged) {
121   bool state_changed =
122       IsCurrentUrlBookmarked() != is_url_bookmarked_;
123   if (state_changed)
124     is_url_bookmarked_ = !is_url_bookmarked_;
125   if (state_changed || request_evaluation_if_unchanged)
126     request_evaluation_.Run(web_contents());
127 }
128 
129 bool DeclarativeContentIsBookmarkedConditionTracker::PerWebContentsTracker::
IsCurrentUrlBookmarked()130 IsCurrentUrlBookmarked() {
131   bookmarks::BookmarkModel* bookmark_model =
132       BookmarkModelFactory::GetForBrowserContext(
133           web_contents()->GetBrowserContext());
134   // BookmarkModel can be null during unit test execution.
135   return bookmark_model &&
136       bookmark_model->IsBookmarked(web_contents()->GetVisibleURL());
137 }
138 
139 void DeclarativeContentIsBookmarkedConditionTracker::PerWebContentsTracker::
WebContentsDestroyed()140 WebContentsDestroyed() {
141   web_contents_destroyed_.Run(web_contents());
142 }
143 
144 //
145 // DeclarativeContentIsBookmarkedConditionTracker
146 //
147 
148 DeclarativeContentIsBookmarkedConditionTracker::
DeclarativeContentIsBookmarkedConditionTracker(content::BrowserContext * context,Delegate * delegate)149     DeclarativeContentIsBookmarkedConditionTracker(
150         content::BrowserContext* context,
151         Delegate* delegate)
152     : delegate_(delegate), extensive_bookmark_changes_in_progress_(0) {
153   bookmarks::BookmarkModel* bookmark_model =
154       BookmarkModelFactory::GetForBrowserContext(context);
155   // Can be null during unit test execution.
156   if (bookmark_model)
157     scoped_bookmarks_observer_.Add(bookmark_model);
158 }
159 
160 DeclarativeContentIsBookmarkedConditionTracker::
~DeclarativeContentIsBookmarkedConditionTracker()161 ~DeclarativeContentIsBookmarkedConditionTracker() {
162 }
163 
164 std::string DeclarativeContentIsBookmarkedConditionTracker::
GetPredicateApiAttributeName() const165 GetPredicateApiAttributeName() const {
166   return declarative_content_constants::kIsBookmarked;
167 }
168 
169 std::unique_ptr<const ContentPredicate>
CreatePredicate(const Extension * extension,const base::Value & value,std::string * error)170 DeclarativeContentIsBookmarkedConditionTracker::CreatePredicate(
171     const Extension* extension,
172     const base::Value& value,
173     std::string* error) {
174   return DeclarativeContentIsBookmarkedPredicate::Create(this, extension, value,
175                                                          error);
176 }
177 
178 // We don't have any centralized state to update for new predicates, so we don't
179 // need to take any action here or in StopTrackingPredicates().
TrackPredicates(const std::map<const void *,std::vector<const ContentPredicate * >> & predicates)180 void DeclarativeContentIsBookmarkedConditionTracker::TrackPredicates(
181     const std::map<const void*, std::vector<const ContentPredicate*>>&
182         predicates) {
183 }
184 
StopTrackingPredicates(const std::vector<const void * > & predicate_groups)185 void DeclarativeContentIsBookmarkedConditionTracker::StopTrackingPredicates(
186     const std::vector<const void*>& predicate_groups) {
187 }
188 
TrackForWebContents(content::WebContents * contents)189 void DeclarativeContentIsBookmarkedConditionTracker::TrackForWebContents(
190     content::WebContents* contents) {
191   per_web_contents_tracker_[contents] = std::make_unique<PerWebContentsTracker>(
192       contents,
193       base::Bind(&Delegate::RequestEvaluation, base::Unretained(delegate_)),
194       base::Bind(&DeclarativeContentIsBookmarkedConditionTracker::
195                      DeletePerWebContentsTracker,
196                  base::Unretained(this)));
197 }
198 
OnWebContentsNavigation(content::WebContents * contents,content::NavigationHandle * navigation_handle)199 void DeclarativeContentIsBookmarkedConditionTracker::OnWebContentsNavigation(
200     content::WebContents* contents,
201     content::NavigationHandle* navigation_handle) {
202   DCHECK(base::Contains(per_web_contents_tracker_, contents));
203   per_web_contents_tracker_[contents]->UpdateState(true);
204 }
205 
EvaluatePredicate(const ContentPredicate * predicate,content::WebContents * tab) const206 bool DeclarativeContentIsBookmarkedConditionTracker::EvaluatePredicate(
207     const ContentPredicate* predicate,
208     content::WebContents* tab) const {
209   DCHECK_EQ(this, predicate->GetEvaluator());
210   const DeclarativeContentIsBookmarkedPredicate* typed_predicate =
211       static_cast<const DeclarativeContentIsBookmarkedPredicate*>(predicate);
212   auto loc = per_web_contents_tracker_.find(tab);
213   DCHECK(loc != per_web_contents_tracker_.end());
214   return loc->second->is_url_bookmarked() == typed_predicate->is_bookmarked();
215 }
216 
BookmarkModelChanged()217 void DeclarativeContentIsBookmarkedConditionTracker::BookmarkModelChanged() {}
218 
BookmarkNodeAdded(bookmarks::BookmarkModel * model,const bookmarks::BookmarkNode * parent,size_t index)219 void DeclarativeContentIsBookmarkedConditionTracker::BookmarkNodeAdded(
220     bookmarks::BookmarkModel* model,
221     const bookmarks::BookmarkNode* parent,
222     size_t index) {
223   if (!extensive_bookmark_changes_in_progress_) {
224     for (const auto& web_contents_tracker_pair : per_web_contents_tracker_) {
225       web_contents_tracker_pair.second->BookmarkAddedForUrl(
226           parent->children()[index]->url());
227     }
228   }
229 }
230 
BookmarkNodeRemoved(bookmarks::BookmarkModel * model,const bookmarks::BookmarkNode * parent,size_t old_index,const bookmarks::BookmarkNode * node,const std::set<GURL> & no_longer_bookmarked)231 void DeclarativeContentIsBookmarkedConditionTracker::BookmarkNodeRemoved(
232     bookmarks::BookmarkModel* model,
233     const bookmarks::BookmarkNode* parent,
234     size_t old_index,
235     const bookmarks::BookmarkNode* node,
236     const std::set<GURL>& no_longer_bookmarked) {
237   if (!extensive_bookmark_changes_in_progress_) {
238     for (const auto& web_contents_tracker_pair : per_web_contents_tracker_) {
239       web_contents_tracker_pair.second->BookmarkRemovedForUrls(
240           no_longer_bookmarked);
241     }
242   }
243 }
244 
245 void DeclarativeContentIsBookmarkedConditionTracker::
ExtensiveBookmarkChangesBeginning(bookmarks::BookmarkModel * model)246 ExtensiveBookmarkChangesBeginning(
247     bookmarks::BookmarkModel* model) {
248   ++extensive_bookmark_changes_in_progress_;
249 }
250 
251 void
ExtensiveBookmarkChangesEnded(bookmarks::BookmarkModel * model)252 DeclarativeContentIsBookmarkedConditionTracker::ExtensiveBookmarkChangesEnded(
253     bookmarks::BookmarkModel* model) {
254   if (--extensive_bookmark_changes_in_progress_ == 0)
255     UpdateAllPerWebContentsTrackers();
256 }
257 
258 void
GroupedBookmarkChangesBeginning(bookmarks::BookmarkModel * model)259 DeclarativeContentIsBookmarkedConditionTracker::GroupedBookmarkChangesBeginning(
260     bookmarks::BookmarkModel* model) {
261   ++extensive_bookmark_changes_in_progress_;
262 }
263 
264 void
GroupedBookmarkChangesEnded(bookmarks::BookmarkModel * model)265 DeclarativeContentIsBookmarkedConditionTracker::GroupedBookmarkChangesEnded(
266     bookmarks::BookmarkModel* model) {
267   if (--extensive_bookmark_changes_in_progress_ == 0)
268     UpdateAllPerWebContentsTrackers();
269 }
270 
271 void
DeletePerWebContentsTracker(content::WebContents * contents)272 DeclarativeContentIsBookmarkedConditionTracker::DeletePerWebContentsTracker(
273     content::WebContents* contents) {
274   DCHECK(base::Contains(per_web_contents_tracker_, contents));
275   per_web_contents_tracker_.erase(contents);
276 }
277 
278 void DeclarativeContentIsBookmarkedConditionTracker::
UpdateAllPerWebContentsTrackers()279 UpdateAllPerWebContentsTrackers() {
280   for (const auto& web_contents_tracker_pair : per_web_contents_tracker_)
281     web_contents_tracker_pair.second->UpdateState(false);
282 }
283 
284 }  // namespace extensions
285