// Copyright (c) 2012 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/chrome_content_rules_registry.h" #include "base/bind.h" #include "base/macros.h" #include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/extensions/api/declarative_content/content_constants.h" #include "chrome/browser/extensions/extension_util.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "content/public/browser/navigation_details.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/notification_source.h" #include "content/public/browser/web_contents.h" #include "extensions/browser/api/declarative/rules_registry_service.h" #include "extensions/browser/extension_registry.h" #include "extensions/browser/extension_system.h" namespace extensions { // // EvaluationScope // // Used to coalesce multiple requests for evaluation into a zero or one actual // evaluations (depending on the EvaluationDisposition). This is required for // correctness when multiple trackers respond to the same event. Otherwise, // executing the request from the first tracker will be done before the tracked // state has been updated for the other trackers. class ChromeContentRulesRegistry::EvaluationScope { public: // Default disposition is PERFORM_EVALUATION. explicit EvaluationScope(ChromeContentRulesRegistry* registry); EvaluationScope(ChromeContentRulesRegistry* registry, EvaluationDisposition disposition); ~EvaluationScope(); private: ChromeContentRulesRegistry* const registry_; const EvaluationDisposition previous_disposition_; DISALLOW_COPY_AND_ASSIGN(EvaluationScope); }; ChromeContentRulesRegistry::EvaluationScope::EvaluationScope( ChromeContentRulesRegistry* registry) : EvaluationScope(registry, DEFER_REQUESTS) {} ChromeContentRulesRegistry::EvaluationScope::EvaluationScope( ChromeContentRulesRegistry* registry, EvaluationDisposition disposition) : registry_(registry), previous_disposition_(registry_->evaluation_disposition_) { DCHECK_NE(EVALUATE_REQUESTS, disposition); registry_->evaluation_disposition_ = disposition; } ChromeContentRulesRegistry::EvaluationScope::~EvaluationScope() { registry_->evaluation_disposition_ = previous_disposition_; if (registry_->evaluation_disposition_ == EVALUATE_REQUESTS) { for (content::WebContents* tab : registry_->evaluation_pending_) registry_->EvaluateConditionsForTab(tab); registry_->evaluation_pending_.clear(); } } // // ChromeContentRulesRegistry // ChromeContentRulesRegistry::ChromeContentRulesRegistry( content::BrowserContext* browser_context, RulesCacheDelegate* cache_delegate, const PredicateEvaluatorsFactory& evaluators_factory) : ContentRulesRegistry(browser_context, declarative_content_constants::kOnPageChanged, content::BrowserThread::UI, cache_delegate, RulesRegistryService::kDefaultRulesRegistryID), evaluators_(evaluators_factory.Run(this)), evaluation_disposition_(EVALUATE_REQUESTS) { registrar_.Add(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED, content::NotificationService::AllBrowserContextsAndSources()); } void ChromeContentRulesRegistry::Observe( int type, const content::NotificationSource& source, const content::NotificationDetails& details) { switch (type) { case content::NOTIFICATION_WEB_CONTENTS_DESTROYED: { content::WebContents* tab = content::Source(source).ptr(); // Note that neither non-tab WebContents nor tabs from other browser // contexts will be in the map. active_rules_.erase(tab); break; } } } void ChromeContentRulesRegistry::RequestEvaluation( content::WebContents* contents) { switch (evaluation_disposition_) { case EVALUATE_REQUESTS: EvaluateConditionsForTab(contents); break; case DEFER_REQUESTS: evaluation_pending_.insert(contents); break; case IGNORE_REQUESTS: break; } } bool ChromeContentRulesRegistry::ShouldManageConditionsForBrowserContext( content::BrowserContext* context) { return ManagingRulesForBrowserContext(context); } void ChromeContentRulesRegistry::MonitorWebContentsForRuleEvaluation( content::WebContents* contents) { // We rely on active_rules_ to have a key-value pair for |contents| to know // which WebContents we are working with. active_rules_[contents] = std::set(); EvaluationScope evaluation_scope(this); for (const scoped_ptr& evaluator : evaluators_) evaluator->TrackForWebContents(contents); } void ChromeContentRulesRegistry::DidNavigateMainFrame( content::WebContents* contents, const content::LoadCommittedDetails& details, const content::FrameNavigateParams& params) { if (ContainsKey(active_rules_, contents)) { EvaluationScope evaluation_scope(this); for (const scoped_ptr& evaluator : evaluators_) evaluator->OnWebContentsNavigation(contents, details, params); } } ChromeContentRulesRegistry::ContentRule::ContentRule( const Extension* extension, std::vector> conditions, std::vector> actions, int priority) : extension(extension), conditions(std::move(conditions)), actions(std::move(actions)), priority(priority) {} ChromeContentRulesRegistry::ContentRule::~ContentRule() {} scoped_ptr ChromeContentRulesRegistry::CreateRule( const Extension* extension, const std::map& predicate_factories, const api::events::Rule& api_rule, std::string* error) { std::vector> conditions; for (const linked_ptr& value : api_rule.conditions) { conditions.push_back( CreateContentCondition(extension, predicate_factories, *value, error)); if (!error->empty()) return scoped_ptr(); } std::vector> actions; for (const linked_ptr& value : api_rule.actions) { actions.push_back(ContentAction::Create(browser_context(), extension, *value, error)); if (!error->empty()) return scoped_ptr(); } // Note: |api_rule| may contain tags, but these are ignored. return make_scoped_ptr(new ContentRule(extension, std::move(conditions), std::move(actions), *api_rule.priority)); } bool ChromeContentRulesRegistry::ManagingRulesForBrowserContext( content::BrowserContext* context) { // Manage both the normal context and incognito contexts associated with it. return Profile::FromBrowserContext(context)->GetOriginalProfile() == Profile::FromBrowserContext(browser_context()); } // static bool ChromeContentRulesRegistry::EvaluateConditionForTab( const ContentCondition* condition, content::WebContents* tab) { for (const scoped_ptr& predicate : condition->predicates) { if (predicate && !predicate->IsIgnored() && !predicate->GetEvaluator()->EvaluatePredicate(predicate.get(), tab)) { return false; } } return true; } std::set ChromeContentRulesRegistry::GetMatchingRules(content::WebContents* tab) const { const bool is_incognito_tab = tab->GetBrowserContext()->IsOffTheRecord(); std::set matching_rules; for (const RulesMap::value_type& rule_id_rule_pair : content_rules_) { const ContentRule* rule = rule_id_rule_pair.second.get(); if (is_incognito_tab && !ShouldEvaluateExtensionRulesForIncognitoRenderer(rule->extension)) continue; for (const scoped_ptr& condition : rule->conditions) { if (EvaluateConditionForTab(condition.get(), tab)) matching_rules.insert(rule); } } return matching_rules; } std::string ChromeContentRulesRegistry::AddRulesImpl( const std::string& extension_id, const std::vector>& api_rules) { EvaluationScope evaluation_scope(this); const Extension* extension = ExtensionRegistry::Get(browser_context()) ->GetInstalledExtension(extension_id); DCHECK(extension); std::string error; RulesMap new_rules; std::map>> new_predicates; std::map predicate_factories; for (const scoped_ptr& evaluator : evaluators_) { predicate_factories[evaluator->GetPredicateApiAttributeName()] = evaluator.get(); } for (const linked_ptr& api_rule : api_rules) { ExtensionIdRuleIdPair rule_id(extension_id, *api_rule->id); DCHECK(content_rules_.find(rule_id) == content_rules_.end()); scoped_ptr rule( CreateRule(extension, predicate_factories, *api_rule, &error)); if (!error.empty()) { // Notify evaluators that none of the created predicates will be tracked // after all. for (const scoped_ptr& evaluator : evaluators_) { if (!new_predicates[evaluator.get()].empty()) { evaluator->TrackPredicates( std::map>()); } } return error; } DCHECK(rule); // Group predicates by evaluator and rule, so we can later notify the // evaluators that they have new predicates to manage. for (const scoped_ptr& condition : rule->conditions) { for (const scoped_ptr& predicate : condition->predicates) { if (predicate.get()) { new_predicates[predicate->GetEvaluator()][rule.get()].push_back( predicate.get()); } } } new_rules[rule_id] = make_linked_ptr(rule.release()); } // Notify the evaluators about their new predicates. for (const scoped_ptr& evaluator : evaluators_) evaluator->TrackPredicates(new_predicates[evaluator.get()]); // Wohoo, everything worked fine. content_rules_.insert(new_rules.begin(), new_rules.end()); // Request evaluation for all WebContents, under the assumption that a // non-empty condition has been added. for (const auto& web_contents_rules_pair : active_rules_) RequestEvaluation(web_contents_rules_pair.first); return std::string(); } std::string ChromeContentRulesRegistry::RemoveRulesImpl( const std::string& extension_id, const std::vector& rule_identifiers) { // Ignore evaluation requests in this function because it reverts actions on // any active rules itself. Otherwise, we run the risk of reverting the same // rule multiple times. EvaluationScope evaluation_scope(this, IGNORE_REQUESTS); std::vector rules_to_erase; std::vector predicate_groups_to_stop_tracking; for (const std::string& id : rule_identifiers) { // Skip unknown rules. RulesMap::iterator content_rules_entry = content_rules_.find(std::make_pair(extension_id, id)); if (content_rules_entry == content_rules_.end()) continue; const ContentRule* rule = content_rules_entry->second.get(); // Remove the ContentRule from active_rules_. for (auto& tab_rules_pair : active_rules_) { if (ContainsKey(tab_rules_pair.second, rule)) { ContentAction::ApplyInfo apply_info = {rule->extension, browser_context(), tab_rules_pair.first, rule->priority}; for (const auto& action : rule->actions) action->Revert(apply_info); tab_rules_pair.second.erase(rule); } } rules_to_erase.push_back(content_rules_entry); predicate_groups_to_stop_tracking.push_back(rule); } // Notify the evaluators to stop tracking the predicates that will be removed. for (const scoped_ptr& evaluator : evaluators_) evaluator->StopTrackingPredicates(predicate_groups_to_stop_tracking); // Remove the rules. for (RulesMap::iterator it : rules_to_erase) content_rules_.erase(it); return std::string(); } std::string ChromeContentRulesRegistry::RemoveAllRulesImpl( const std::string& extension_id) { // Search all identifiers of rules that belong to extension |extension_id|. std::vector rule_identifiers; for (const RulesMap::value_type& id_rule_pair : content_rules_) { const ExtensionIdRuleIdPair& extension_id_rule_id_pair = id_rule_pair.first; if (extension_id_rule_id_pair.first == extension_id) rule_identifiers.push_back(extension_id_rule_id_pair.second); } return RemoveRulesImpl(extension_id, rule_identifiers); } void ChromeContentRulesRegistry::EvaluateConditionsForTab( content::WebContents* tab) { std::set matching_rules = GetMatchingRules(tab); if (matching_rules.empty() && !ContainsKey(active_rules_, tab)) return; std::set& prev_matching_rules = active_rules_[tab]; for (const ContentRule* rule : matching_rules) { ContentAction::ApplyInfo apply_info = {rule->extension, browser_context(), tab, rule->priority}; if (!ContainsKey(prev_matching_rules, rule)) { for (const scoped_ptr& action : rule->actions) action->Apply(apply_info); } else { for (const scoped_ptr& action : rule->actions) action->Reapply(apply_info); } } for (const ContentRule* rule : prev_matching_rules) { if (!ContainsKey(matching_rules, rule)) { ContentAction::ApplyInfo apply_info = {rule->extension, browser_context(), tab, rule->priority}; for (const scoped_ptr& action : rule->actions) action->Revert(apply_info); } } if (matching_rules.empty()) active_rules_[tab].clear(); else swap(matching_rules, prev_matching_rules); } bool ChromeContentRulesRegistry::ShouldEvaluateExtensionRulesForIncognitoRenderer( const Extension* extension) const { if (!util::IsIncognitoEnabled(extension->id(), browser_context())) return false; // Split-mode incognito extensions register their rules with separate // RulesRegistries per Original/OffTheRecord browser contexts, whereas // spanning-mode extensions share the Original browser context. if (util::CanCrossIncognito(extension, browser_context())) { // The extension uses spanning mode incognito. No rules should have been // registered for the extension in the OffTheRecord registry so // execution for that registry should never reach this point. CHECK(!browser_context()->IsOffTheRecord()); } else { // The extension uses split mode incognito. Both the Original and // OffTheRecord registries may have (separate) rules for this extension. // Since we're looking at an incognito renderer, so only the OffTheRecord // registry should process its rules. if (!browser_context()->IsOffTheRecord()) return false; } return true; } size_t ChromeContentRulesRegistry::GetActiveRulesCountForTesting() { size_t count = 0; for (const auto& web_contents_rules_pair : active_rules_) count += web_contents_rules_pair.second.size(); return count; } ChromeContentRulesRegistry::~ChromeContentRulesRegistry() { } } // namespace extensions