blob: 78586f81879c7b445aefb5c4ed9bc31325ab3707 [file] [log] [blame]
// 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_page_url_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 "components/url_matcher/url_matcher_factory.h"
#include "content/public/browser/web_contents.h"
#include "extensions/common/api/declarative/declarative_constants.h"
namespace extensions {
namespace {
const char kPageUrlInvalidTypeOfParameter[] =
"Attribute '%s' has an invalid type";
static url_matcher::URLMatcherConditionSet::ID g_next_id = 0;
} // namespace
//
// DeclarativeContentPageUrlPredicate
//
DeclarativeContentPageUrlPredicate::~DeclarativeContentPageUrlPredicate() {
}
// static
std::unique_ptr<DeclarativeContentPageUrlPredicate>
DeclarativeContentPageUrlPredicate::Create(
ContentPredicateEvaluator* evaluator,
url_matcher::URLMatcherConditionFactory* url_matcher_condition_factory,
const base::Value& value,
std::string* error) {
scoped_refptr<url_matcher::URLMatcherConditionSet> url_matcher_condition_set;
const base::DictionaryValue* dict = nullptr;
if (!value.GetAsDictionary(&dict)) {
*error = base::StringPrintf(kPageUrlInvalidTypeOfParameter,
declarative_content_constants::kPageUrl);
return std::unique_ptr<DeclarativeContentPageUrlPredicate>();
} else {
url_matcher_condition_set =
url_matcher::URLMatcherFactory::CreateFromURLFilterDictionary(
url_matcher_condition_factory, dict, ++g_next_id, error);
if (!url_matcher_condition_set)
return std::unique_ptr<DeclarativeContentPageUrlPredicate>();
return base::WrapUnique(new DeclarativeContentPageUrlPredicate(
evaluator, url_matcher_condition_set));
}
}
ContentPredicateEvaluator*
DeclarativeContentPageUrlPredicate::GetEvaluator() const {
return evaluator_;
}
DeclarativeContentPageUrlPredicate::DeclarativeContentPageUrlPredicate(
ContentPredicateEvaluator* evaluator,
scoped_refptr<url_matcher::URLMatcherConditionSet>
url_matcher_condition_set)
: evaluator_(evaluator),
url_matcher_condition_set_(url_matcher_condition_set) {
DCHECK(url_matcher_condition_set);
}
//
// PerWebContentsTracker
//
DeclarativeContentPageUrlConditionTracker::PerWebContentsTracker::
PerWebContentsTracker(
content::WebContents* contents,
url_matcher::URLMatcher* url_matcher,
const RequestEvaluationCallback& request_evaluation,
const WebContentsDestroyedCallback& web_contents_destroyed)
: WebContentsObserver(contents),
url_matcher_(url_matcher),
request_evaluation_(request_evaluation),
web_contents_destroyed_(web_contents_destroyed) {
}
DeclarativeContentPageUrlConditionTracker::PerWebContentsTracker::
~PerWebContentsTracker() {
}
void DeclarativeContentPageUrlConditionTracker::PerWebContentsTracker::
UpdateMatchesForCurrentUrl(bool request_evaluation_if_unchanged) {
std::set<url_matcher::URLMatcherConditionSet::ID> new_matches =
url_matcher_->MatchURL(web_contents()->GetVisibleURL());
matches_.swap(new_matches);
if (matches_ != new_matches || request_evaluation_if_unchanged)
request_evaluation_.Run(web_contents());
}
void DeclarativeContentPageUrlConditionTracker::PerWebContentsTracker::
WebContentsDestroyed() {
web_contents_destroyed_.Run(web_contents());
}
//
// DeclarativeContentPageUrlConditionTracker
//
DeclarativeContentPageUrlConditionTracker::
DeclarativeContentPageUrlConditionTracker(Delegate* delegate)
: delegate_(delegate) {
}
DeclarativeContentPageUrlConditionTracker::
~DeclarativeContentPageUrlConditionTracker() {
}
std::string DeclarativeContentPageUrlConditionTracker::
GetPredicateApiAttributeName() const {
return declarative_content_constants::kPageUrl;
}
std::unique_ptr<const ContentPredicate>
DeclarativeContentPageUrlConditionTracker::CreatePredicate(
const Extension* extension,
const base::Value& value,
std::string* error) {
return DeclarativeContentPageUrlPredicate::Create(this,
url_matcher_.condition_factory(), value, error);
}
void DeclarativeContentPageUrlConditionTracker::TrackPredicates(
const std::map<const void*, std::vector<const ContentPredicate*>>&
predicates) {
if (predicates.empty()) {
// Clean up temporary condition sets created during rule creation.
url_matcher_.ClearUnusedConditionSets();
return;
}
// Record the URL patterns in |url_matcher_|.
url_matcher::URLMatcherConditionSet::Vector new_condition_sets;
for (const auto& group_predicates_pair : predicates) {
for (const ContentPredicate* predicate : group_predicates_pair.second) {
DCHECK_EQ(this, predicate->GetEvaluator());
const DeclarativeContentPageUrlPredicate* typed_predicate =
static_cast<const DeclarativeContentPageUrlPredicate*>(predicate);
new_condition_sets.push_back(
typed_predicate->url_matcher_condition_set());
tracked_predicates_[group_predicates_pair.first].push_back(
typed_predicate);
}
}
url_matcher_.AddConditionSets(new_condition_sets);
UpdateMatchesForAllTrackers();
}
void DeclarativeContentPageUrlConditionTracker::StopTrackingPredicates(
const std::vector<const void*>& predicate_groups) {
// Condition set ids to be removed from |url_matcher_|.
std::vector<url_matcher::URLMatcherConditionSet::ID>
condition_set_ids_to_remove;
for (const void* group : predicate_groups) {
auto loc = tracked_predicates_.find(group);
if (loc == tracked_predicates_.end())
continue;
for (const DeclarativeContentPageUrlPredicate* predicate : loc->second) {
condition_set_ids_to_remove.push_back(
predicate->url_matcher_condition_set()->id());
}
tracked_predicates_.erase(group);
}
if (!condition_set_ids_to_remove.empty()) {
url_matcher_.RemoveConditionSets(condition_set_ids_to_remove);
UpdateMatchesForAllTrackers();
}
}
void DeclarativeContentPageUrlConditionTracker::TrackForWebContents(
content::WebContents* contents) {
per_web_contents_tracker_[contents] = std::make_unique<PerWebContentsTracker>(
contents, &url_matcher_,
base::Bind(&Delegate::RequestEvaluation, base::Unretained(delegate_)),
base::Bind(&DeclarativeContentPageUrlConditionTracker::
DeletePerWebContentsTracker,
base::Unretained(this)));
per_web_contents_tracker_[contents]->UpdateMatchesForCurrentUrl(true);
}
void DeclarativeContentPageUrlConditionTracker::OnWebContentsNavigation(
content::WebContents* contents,
content::NavigationHandle* navigation_handle) {
DCHECK(base::ContainsKey(per_web_contents_tracker_, contents));
per_web_contents_tracker_[contents]->UpdateMatchesForCurrentUrl(true);
}
bool DeclarativeContentPageUrlConditionTracker::EvaluatePredicate(
const ContentPredicate* predicate,
content::WebContents* tab) const {
DCHECK_EQ(this, predicate->GetEvaluator());
const DeclarativeContentPageUrlPredicate* typed_predicate =
static_cast<const DeclarativeContentPageUrlPredicate*>(predicate);
auto loc = per_web_contents_tracker_.find(tab);
DCHECK(loc != per_web_contents_tracker_.end());
const std::set<url_matcher::URLMatcherConditionSet::ID>&
web_contents_id_matches = loc->second->matches();
return base::ContainsKey(web_contents_id_matches,
typed_predicate->url_matcher_condition_set()->id());
}
bool DeclarativeContentPageUrlConditionTracker::IsEmpty() const {
return url_matcher_.IsEmpty();
}
void DeclarativeContentPageUrlConditionTracker::DeletePerWebContentsTracker(
content::WebContents* contents) {
DCHECK(base::ContainsKey(per_web_contents_tracker_, contents));
per_web_contents_tracker_.erase(contents);
}
void DeclarativeContentPageUrlConditionTracker::UpdateMatchesForAllTrackers() {
for (const auto& web_contents_tracker_pair : per_web_contents_tracker_)
web_contents_tracker_pair.second->UpdateMatchesForCurrentUrl(false);
}
} // namespace extensions