| // 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/memory/ptr_util.h" |
| #include "base/stl_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/values.h" |
| #include "chrome/browser/bookmarks/bookmark_model_factory.h" |
| #include "components/bookmarks/browser/bookmark_model.h" |
| #include "content/public/browser/web_contents.h" |
| #include "extensions/common/api/declarative/declarative_constants.h" |
| #include "extensions/common/permissions/permissions_data.h" |
| |
| namespace extensions { |
| |
| namespace { |
| |
| const char kIsBookmarkedInvalidTypeOfParameter[] = |
| "Attribute '%s' has an invalid type"; |
| const char kIsBookmarkedRequiresBookmarkPermission[] = |
| "Property 'isBookmarked' requires 'bookmarks' permission"; |
| |
| bool HasBookmarkAPIPermission(const Extension* extension) { |
| return extension->permissions_data()->HasAPIPermission( |
| APIPermission::kBookmark); |
| } |
| |
| } // namespace |
| |
| // |
| // DeclarativeContentIsBookmarkedPredicate |
| // |
| |
| DeclarativeContentIsBookmarkedPredicate:: |
| ~DeclarativeContentIsBookmarkedPredicate() { |
| } |
| |
| bool DeclarativeContentIsBookmarkedPredicate::IsIgnored() const { |
| return !HasBookmarkAPIPermission(extension_.get()); |
| } |
| |
| // static |
| std::unique_ptr<DeclarativeContentIsBookmarkedPredicate> |
| DeclarativeContentIsBookmarkedPredicate::Create( |
| ContentPredicateEvaluator* evaluator, |
| const Extension* extension, |
| const base::Value& value, |
| std::string* error) { |
| bool is_bookmarked = false; |
| if (value.GetAsBoolean(&is_bookmarked)) { |
| if (!HasBookmarkAPIPermission(extension)) { |
| *error = kIsBookmarkedRequiresBookmarkPermission; |
| return std::unique_ptr<DeclarativeContentIsBookmarkedPredicate>(); |
| } else { |
| return base::WrapUnique(new DeclarativeContentIsBookmarkedPredicate( |
| evaluator, extension, is_bookmarked)); |
| } |
| } else { |
| *error = base::StringPrintf(kIsBookmarkedInvalidTypeOfParameter, |
| declarative_content_constants::kIsBookmarked); |
| return std::unique_ptr<DeclarativeContentIsBookmarkedPredicate>(); |
| } |
| } |
| |
| ContentPredicateEvaluator* |
| DeclarativeContentIsBookmarkedPredicate::GetEvaluator() const { |
| return evaluator_; |
| } |
| |
| DeclarativeContentIsBookmarkedPredicate:: |
| DeclarativeContentIsBookmarkedPredicate( |
| ContentPredicateEvaluator* evaluator, |
| scoped_refptr<const Extension> extension, |
| bool is_bookmarked) |
| : evaluator_(evaluator), |
| extension_(extension), |
| is_bookmarked_(is_bookmarked) { |
| } |
| |
| // |
| // 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 (base::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::GetForBrowserContext( |
| 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, |
| Delegate* delegate) |
| : delegate_(delegate), |
| extensive_bookmark_changes_in_progress_(0), |
| scoped_bookmarks_observer_(this) { |
| bookmarks::BookmarkModel* bookmark_model = |
| BookmarkModelFactory::GetForBrowserContext(context); |
| // Can be null during unit test execution. |
| if (bookmark_model) |
| scoped_bookmarks_observer_.Add(bookmark_model); |
| } |
| |
| DeclarativeContentIsBookmarkedConditionTracker:: |
| ~DeclarativeContentIsBookmarkedConditionTracker() { |
| } |
| |
| std::string DeclarativeContentIsBookmarkedConditionTracker:: |
| GetPredicateApiAttributeName() const { |
| return declarative_content_constants::kIsBookmarked; |
| } |
| |
| std::unique_ptr<const ContentPredicate> |
| DeclarativeContentIsBookmarkedConditionTracker::CreatePredicate( |
| const Extension* extension, |
| const base::Value& value, |
| std::string* error) { |
| return DeclarativeContentIsBookmarkedPredicate::Create(this, extension, value, |
| error); |
| } |
| |
| // We don't have any centralized state to update for new predicates, so we don't |
| // need to take any action here or in StopTrackingPredicates(). |
| void DeclarativeContentIsBookmarkedConditionTracker::TrackPredicates( |
| const std::map<const void*, std::vector<const ContentPredicate*>>& |
| predicates) { |
| } |
| |
| void DeclarativeContentIsBookmarkedConditionTracker::StopTrackingPredicates( |
| const std::vector<const void*>& predicate_groups) { |
| } |
| |
| void DeclarativeContentIsBookmarkedConditionTracker::TrackForWebContents( |
| content::WebContents* contents) { |
| per_web_contents_tracker_[contents] = std::make_unique<PerWebContentsTracker>( |
| contents, |
| base::Bind(&Delegate::RequestEvaluation, base::Unretained(delegate_)), |
| base::Bind(&DeclarativeContentIsBookmarkedConditionTracker:: |
| DeletePerWebContentsTracker, |
| base::Unretained(this))); |
| } |
| |
| void DeclarativeContentIsBookmarkedConditionTracker::OnWebContentsNavigation( |
| content::WebContents* contents, |
| content::NavigationHandle* navigation_handle) { |
| DCHECK(base::ContainsKey(per_web_contents_tracker_, contents)); |
| per_web_contents_tracker_[contents]->UpdateState(true); |
| } |
| |
| bool DeclarativeContentIsBookmarkedConditionTracker::EvaluatePredicate( |
| const ContentPredicate* predicate, |
| content::WebContents* tab) const { |
| DCHECK_EQ(this, predicate->GetEvaluator()); |
| const DeclarativeContentIsBookmarkedPredicate* typed_predicate = |
| static_cast<const DeclarativeContentIsBookmarkedPredicate*>(predicate); |
| auto loc = per_web_contents_tracker_.find(tab); |
| DCHECK(loc != per_web_contents_tracker_.end()); |
| return loc->second->is_url_bookmarked() == typed_predicate->is_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(base::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 |