blob: a5fb36adff5f17665627249e754c347d52ae1999 [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_css_condition_tracker.h"
#include <stddef.h>
#include "base/memory/ptr_util.h"
#include "base/stl_util.h"
#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/extensions/api/declarative_content/content_predicate_evaluator.h"
#include "chrome/browser/profiles/profile.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/render_process_host.h"
#include "extensions/common/api/declarative/declarative_constants.h"
#include "extensions/common/extension_messages.h"
#include "ipc/ipc_message.h"
#include "ipc/ipc_message_macros.h"
namespace extensions {
namespace {
const char kCssInvalidTypeOfParameter[] = "Attribute '%s' has an invalid type";
} // namespace
//
// DeclarativeContentCssPredicate
//
DeclarativeContentCssPredicate::~DeclarativeContentCssPredicate() {
}
// static
std::unique_ptr<DeclarativeContentCssPredicate>
DeclarativeContentCssPredicate::Create(ContentPredicateEvaluator* evaluator,
const base::Value& value,
std::string* error) {
std::vector<std::string> css_rules;
const base::ListValue* css_rules_value = nullptr;
if (value.GetAsList(&css_rules_value)) {
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(kCssInvalidTypeOfParameter,
declarative_content_constants::kCss);
return std::unique_ptr<DeclarativeContentCssPredicate>();
}
css_rules.push_back(css_rule);
}
} else {
*error = base::StringPrintf(kCssInvalidTypeOfParameter,
declarative_content_constants::kCss);
return std::unique_ptr<DeclarativeContentCssPredicate>();
}
return !css_rules.empty()
? base::WrapUnique(
new DeclarativeContentCssPredicate(evaluator, css_rules))
: std::unique_ptr<DeclarativeContentCssPredicate>();
}
ContentPredicateEvaluator*
DeclarativeContentCssPredicate::GetEvaluator() const {
return evaluator_;
}
DeclarativeContentCssPredicate::DeclarativeContentCssPredicate(
ContentPredicateEvaluator* evaluator,
const std::vector<std::string>& css_selectors)
: evaluator_(evaluator),
css_selectors_(css_selectors) {
DCHECK(!css_selectors.empty());
}
//
// PerWebContentsTracker
//
DeclarativeContentCssConditionTracker::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) {
}
DeclarativeContentCssConditionTracker::PerWebContentsTracker::
~PerWebContentsTracker() {
}
void DeclarativeContentCssConditionTracker::PerWebContentsTracker::
OnWebContentsNavigation(content::NavigationHandle* navigation_handle) {
if (navigation_handle->IsSameDocument()) {
// Within-page navigations don't change the set of elements that
// exist, and we only support filtering on the top-level URL, so
// this can't change which rules match.
return;
}
// Top-level navigation produces a new document. Initially, the
// document's empty, so no CSS rules match. The renderer will send
// an ExtensionHostMsg_OnWatchedPageChange later if any CSS rules
// match.
matching_css_selectors_.clear();
request_evaluation_.Run(web_contents());
}
bool
DeclarativeContentCssConditionTracker::PerWebContentsTracker::
OnMessageReceived(
const IPC::Message& message) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(PerWebContentsTracker, message)
IPC_MESSAGE_HANDLER(ExtensionHostMsg_OnWatchedPageChange,
OnWatchedPageChange)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
}
void DeclarativeContentCssConditionTracker::PerWebContentsTracker::
WebContentsDestroyed() {
web_contents_destroyed_.Run(web_contents());
}
void
DeclarativeContentCssConditionTracker::PerWebContentsTracker::
OnWatchedPageChange(
const std::vector<std::string>& css_selectors) {
matching_css_selectors_.clear();
matching_css_selectors_.insert(css_selectors.begin(), css_selectors.end());
request_evaluation_.Run(web_contents());
}
//
// DeclarativeContentCssConditionTracker
//
DeclarativeContentCssConditionTracker::DeclarativeContentCssConditionTracker(
Delegate* delegate)
: delegate_(delegate) {
registrar_.Add(this,
content::NOTIFICATION_RENDERER_PROCESS_CREATED,
content::NotificationService::AllBrowserContextsAndSources());
}
DeclarativeContentCssConditionTracker::
~DeclarativeContentCssConditionTracker() {}
std::string DeclarativeContentCssConditionTracker::
GetPredicateApiAttributeName() const {
return declarative_content_constants::kCss;
}
std::unique_ptr<const ContentPredicate>
DeclarativeContentCssConditionTracker::CreatePredicate(
const Extension* extension,
const base::Value& value,
std::string* error) {
return DeclarativeContentCssPredicate::Create(this, value, error);
}
void DeclarativeContentCssConditionTracker::TrackPredicates(
const std::map<const void*, std::vector<const ContentPredicate*>>&
predicates) {
bool watched_selectors_updated = false;
for (const auto& group_predicates_pair : predicates) {
for (const ContentPredicate* predicate : group_predicates_pair.second) {
DCHECK_EQ(this, predicate->GetEvaluator());
const DeclarativeContentCssPredicate* typed_predicate =
static_cast<const DeclarativeContentCssPredicate*>(predicate);
tracked_predicates_[group_predicates_pair.first].push_back(
typed_predicate);
for (const std::string& selector : typed_predicate->css_selectors()) {
if (watched_css_selector_predicate_count_[selector]++ == 0)
watched_selectors_updated = true;
}
}
}
if (watched_selectors_updated)
UpdateRenderersWatchedCssSelectors(GetWatchedCssSelectors());
}
void DeclarativeContentCssConditionTracker::StopTrackingPredicates(
const std::vector<const void*>& predicate_groups) {
bool watched_selectors_updated = false;
for (const void* group : predicate_groups) {
auto loc = tracked_predicates_.find(group);
if (loc == tracked_predicates_.end())
continue;
for (const DeclarativeContentCssPredicate* predicate : loc->second) {
for (const std::string& selector : predicate->css_selectors()) {
auto loc = watched_css_selector_predicate_count_.find(selector);
DCHECK(loc != watched_css_selector_predicate_count_.end());
if (--loc->second == 0) {
watched_css_selector_predicate_count_.erase(loc);
watched_selectors_updated = true;
}
}
}
tracked_predicates_.erase(group);
}
if (watched_selectors_updated)
UpdateRenderersWatchedCssSelectors(GetWatchedCssSelectors());
}
void DeclarativeContentCssConditionTracker::TrackForWebContents(
content::WebContents* contents) {
per_web_contents_tracker_[contents] = std::make_unique<PerWebContentsTracker>(
contents,
base::Bind(&Delegate::RequestEvaluation, base::Unretained(delegate_)),
base::Bind(
&DeclarativeContentCssConditionTracker::DeletePerWebContentsTracker,
base::Unretained(this)));
// Note: the condition is always false until we receive OnWatchedPageChange,
// so there's no need to evaluate it here.
}
void DeclarativeContentCssConditionTracker::OnWebContentsNavigation(
content::WebContents* contents,
content::NavigationHandle* navigation_handle) {
DCHECK(base::ContainsKey(per_web_contents_tracker_, contents));
per_web_contents_tracker_[contents]->OnWebContentsNavigation(
navigation_handle);
}
bool DeclarativeContentCssConditionTracker::EvaluatePredicate(
const ContentPredicate* predicate,
content::WebContents* tab) const {
DCHECK_EQ(this, predicate->GetEvaluator());
const DeclarativeContentCssPredicate* typed_predicate =
static_cast<const DeclarativeContentCssPredicate*>(predicate);
auto loc = per_web_contents_tracker_.find(tab);
DCHECK(loc != per_web_contents_tracker_.end());
const base::hash_set<std::string>& matching_css_selectors =
loc->second->matching_css_selectors();
for (const std::string& predicate_css_selector :
typed_predicate->css_selectors()) {
if (!base::ContainsKey(matching_css_selectors, predicate_css_selector))
return false;
}
return true;
}
void DeclarativeContentCssConditionTracker::Observe(
int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
DCHECK_EQ(content::NOTIFICATION_RENDERER_PROCESS_CREATED, type);
content::RenderProcessHost* process =
content::Source<content::RenderProcessHost>(source).ptr();
InstructRenderProcessIfManagingBrowserContext(process,
GetWatchedCssSelectors());
}
void DeclarativeContentCssConditionTracker::
UpdateRenderersWatchedCssSelectors(
const std::vector<std::string>& watched_css_selectors) {
for (content::RenderProcessHost::iterator it(
content::RenderProcessHost::AllHostsIterator());
!it.IsAtEnd();
it.Advance()) {
InstructRenderProcessIfManagingBrowserContext(it.GetCurrentValue(),
watched_css_selectors);
}
}
std::vector<std::string> DeclarativeContentCssConditionTracker::
GetWatchedCssSelectors() const {
std::vector<std::string> selectors;
selectors.reserve(watched_css_selector_predicate_count_.size());
for (const std::pair<std::string, int>& selector_pair :
watched_css_selector_predicate_count_) {
selectors.push_back(selector_pair.first);
}
return selectors;
}
void DeclarativeContentCssConditionTracker::
InstructRenderProcessIfManagingBrowserContext(
content::RenderProcessHost* process,
std::vector<std::string> watched_css_selectors) {
if (delegate_->ShouldManageConditionsForBrowserContext(
process->GetBrowserContext())) {
process->Send(new ExtensionMsg_WatchPages(watched_css_selectors));
}
}
void DeclarativeContentCssConditionTracker::DeletePerWebContentsTracker(
content::WebContents* contents) {
DCHECK(base::ContainsKey(per_web_contents_tracker_, contents));
per_web_contents_tracker_.erase(contents);
}
} // namespace extensions