1// Copyright (c) 2012 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/content_rules_registry.h" 6 7#include "chrome/browser/chrome_notification_types.h" 8#include "chrome/browser/extensions/api/declarative_content/content_action.h" 9#include "chrome/browser/extensions/api/declarative_content/content_condition.h" 10#include "chrome/browser/extensions/api/declarative_content/content_constants.h" 11#include "chrome/browser/extensions/extension_service.h" 12#include "chrome/browser/extensions/extension_system.h" 13#include "chrome/browser/extensions/extension_tab_util.h" 14#include "chrome/browser/profiles/profile.h" 15#include "chrome/common/extensions/extension_messages.h" 16#include "content/public/browser/navigation_details.h" 17#include "content/public/browser/notification_service.h" 18#include "content/public/browser/notification_source.h" 19#include "content/public/browser/render_process_host.h" 20#include "content/public/browser/web_contents.h" 21 22using url_matcher::URLMatcherConditionSet; 23 24namespace extensions { 25 26ContentRulesRegistry::ContentRulesRegistry(Profile* profile, 27 RulesCacheDelegate* cache_delegate) 28 : RulesRegistry(profile, 29 declarative_content_constants::kOnPageChanged, 30 content::BrowserThread::UI, 31 cache_delegate, 32 WebViewKey(0, 0)) { 33 extension_info_map_ = ExtensionSystem::Get(profile)->info_map(); 34 35 registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CREATED, 36 content::NotificationService::AllBrowserContextsAndSources()); 37 registrar_.Add(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED, 38 content::NotificationService::AllBrowserContextsAndSources()); 39} 40 41void ContentRulesRegistry::Observe( 42 int type, 43 const content::NotificationSource& source, 44 const content::NotificationDetails& details) { 45 switch (type) { 46 case content::NOTIFICATION_RENDERER_PROCESS_CREATED: { 47 content::RenderProcessHost* process = 48 content::Source<content::RenderProcessHost>(source).ptr(); 49 if (process->GetBrowserContext() == profile()) 50 InstructRenderProcess(process); 51 break; 52 } 53 case content::NOTIFICATION_WEB_CONTENTS_DESTROYED: { 54 content::WebContents* tab = 55 content::Source<content::WebContents>(source).ptr(); 56 // GetTabId() returns -1 for non-tab WebContents, which won't be 57 // in the map. Similarly, tabs from other profiles won't be in 58 // the map. 59 active_rules_.erase(ExtensionTabUtil::GetTabId(tab)); 60 break; 61 } 62 } 63} 64 65void ContentRulesRegistry::Apply( 66 content::WebContents* contents, 67 const std::vector<std::string>& matching_css_selectors) { 68 const int tab_id = ExtensionTabUtil::GetTabId(contents); 69 RendererContentMatchData renderer_data; 70 renderer_data.page_url_matches = url_matcher_.MatchURL(contents->GetURL()); 71 renderer_data.css_selectors.insert(matching_css_selectors.begin(), 72 matching_css_selectors.end()); 73 std::set<ContentRule*> matching_rules = GetMatches(renderer_data); 74 if (matching_rules.empty() && !ContainsKey(active_rules_, tab_id)) 75 return; 76 77 std::set<ContentRule*>& prev_matching_rules = active_rules_[tab_id]; 78 ContentAction::ApplyInfo apply_info = { 79 profile(), contents 80 }; 81 for (std::set<ContentRule*>::const_iterator it = matching_rules.begin(); 82 it != matching_rules.end(); ++it) { 83 if (!ContainsKey(prev_matching_rules, *it)) 84 (*it)->actions().Apply((*it)->extension_id(), base::Time(), &apply_info); 85 } 86 for (std::set<ContentRule*>::const_iterator it = prev_matching_rules.begin(); 87 it != prev_matching_rules.end(); ++it) { 88 if (!ContainsKey(matching_rules, *it)) 89 (*it)->actions().Revert((*it)->extension_id(), base::Time(), &apply_info); 90 } 91 92 if (matching_rules.empty()) 93 active_rules_.erase(tab_id); 94 else 95 swap(matching_rules, prev_matching_rules); 96} 97 98void ContentRulesRegistry::DidNavigateMainFrame( 99 content::WebContents* contents, 100 const content::LoadCommittedDetails& details, 101 const content::FrameNavigateParams& params) { 102 if (details.is_in_page) { 103 // Within-page navigations don't change the set of elements that 104 // exist, and we only support filtering on the top-level URL, so 105 // this can't change which rules match. 106 return; 107 } 108 109 // Top-level navigation produces a new document. Initially, the 110 // document's empty, so no CSS rules match. The renderer will send 111 // an ExtensionHostMsg_OnWatchedPageChange later if any CSS rules 112 // match. 113 std::vector<std::string> no_css_selectors; 114 Apply(contents, no_css_selectors); 115} 116 117std::set<ContentRule*> 118ContentRulesRegistry::GetMatches( 119 const RendererContentMatchData& renderer_data) const { 120 std::set<ContentRule*> result; 121 122 // Then we need to check for each of these, whether the other 123 // attributes are also fulfilled. 124 for (std::set<URLMatcherConditionSet::ID>::iterator 125 url_match = renderer_data.page_url_matches.begin(); 126 url_match != renderer_data.page_url_matches.end(); ++url_match) { 127 URLMatcherIdToRule::const_iterator rule_iter = 128 match_id_to_rule_.find(*url_match); 129 CHECK(rule_iter != match_id_to_rule_.end()); 130 131 ContentRule* rule = rule_iter->second; 132 if (rule->conditions().IsFulfilled(*url_match, renderer_data)) 133 result.insert(rule); 134 } 135 return result; 136} 137 138std::string ContentRulesRegistry::AddRulesImpl( 139 const std::string& extension_id, 140 const std::vector<linked_ptr<RulesRegistry::Rule> >& rules) { 141 ExtensionService* service = 142 ExtensionSystem::Get(profile())->extension_service(); 143 const Extension* extension = service->GetInstalledExtension(extension_id); 144 DCHECK(extension) << "Must have extension with id " << extension_id; 145 146 base::Time extension_installation_time = 147 GetExtensionInstallationTime(extension_id); 148 149 std::string error; 150 RulesMap new_content_rules; 151 152 for (std::vector<linked_ptr<RulesRegistry::Rule> >::const_iterator rule = 153 rules.begin(); rule != rules.end(); ++rule) { 154 ContentRule::GlobalRuleId rule_id(extension_id, *(*rule)->id); 155 DCHECK(content_rules_.find(rule_id) == content_rules_.end()); 156 157 scoped_ptr<ContentRule> content_rule( 158 ContentRule::Create(url_matcher_.condition_factory(), 159 extension, 160 extension_installation_time, 161 *rule, 162 ContentRule::ConsistencyChecker(), 163 &error)); 164 if (!error.empty()) { 165 // Clean up temporary condition sets created during rule creation. 166 url_matcher_.ClearUnusedConditionSets(); 167 return error; 168 } 169 DCHECK(content_rule); 170 171 new_content_rules[rule_id] = make_linked_ptr(content_rule.release()); 172 } 173 174 // Wohoo, everything worked fine. 175 content_rules_.insert(new_content_rules.begin(), new_content_rules.end()); 176 177 // Create the triggers. 178 for (RulesMap::iterator i = new_content_rules.begin(); 179 i != new_content_rules.end(); ++i) { 180 URLMatcherConditionSet::Vector url_condition_sets; 181 const ContentConditionSet& conditions = i->second->conditions(); 182 conditions.GetURLMatcherConditionSets(&url_condition_sets); 183 for (URLMatcherConditionSet::Vector::iterator j = 184 url_condition_sets.begin(); j != url_condition_sets.end(); ++j) { 185 match_id_to_rule_[(*j)->id()] = i->second.get(); 186 } 187 } 188 189 // Register url patterns in url_matcher_. 190 URLMatcherConditionSet::Vector all_new_condition_sets; 191 for (RulesMap::iterator i = new_content_rules.begin(); 192 i != new_content_rules.end(); ++i) { 193 i->second->conditions().GetURLMatcherConditionSets(&all_new_condition_sets); 194 } 195 url_matcher_.AddConditionSets(all_new_condition_sets); 196 197 UpdateConditionCache(); 198 199 return std::string(); 200} 201 202std::string ContentRulesRegistry::RemoveRulesImpl( 203 const std::string& extension_id, 204 const std::vector<std::string>& rule_identifiers) { 205 // URLMatcherConditionSet IDs that can be removed from URLMatcher. 206 std::vector<URLMatcherConditionSet::ID> remove_from_url_matcher; 207 208 for (std::vector<std::string>::const_iterator i = rule_identifiers.begin(); 209 i != rule_identifiers.end(); ++i) { 210 ContentRule::GlobalRuleId rule_id(extension_id, *i); 211 212 // Skip unknown rules. 213 RulesMap::iterator content_rules_entry = content_rules_.find(rule_id); 214 if (content_rules_entry == content_rules_.end()) 215 continue; 216 217 // Remove all triggers but collect their IDs. 218 URLMatcherConditionSet::Vector condition_sets; 219 ContentRule* rule = content_rules_entry->second.get(); 220 rule->conditions().GetURLMatcherConditionSets(&condition_sets); 221 for (URLMatcherConditionSet::Vector::iterator j = condition_sets.begin(); 222 j != condition_sets.end(); ++j) { 223 remove_from_url_matcher.push_back((*j)->id()); 224 match_id_to_rule_.erase((*j)->id()); 225 } 226 227 // Remove the ContentRule from active_rules_. 228 for (std::map<int, std::set<ContentRule*> >::iterator 229 it = active_rules_.begin(); 230 it != active_rules_.end(); ++it) { 231 if (ContainsKey(it->second, rule)) { 232 content::WebContents* tab; 233 if (!ExtensionTabUtil::GetTabById( 234 it->first, profile(), true, NULL, NULL, &tab, NULL)) { 235 LOG(DFATAL) << "Tab id " << it->first 236 << " still in active_rules_, but tab has been destroyed"; 237 continue; 238 } 239 ContentAction::ApplyInfo apply_info = {profile(), tab}; 240 rule->actions().Revert(rule->extension_id(), base::Time(), &apply_info); 241 it->second.erase(rule); 242 } 243 } 244 245 // Remove reference to actual rule. 246 content_rules_.erase(content_rules_entry); 247 } 248 249 // Clear URLMatcher based on condition_set_ids that are not needed any more. 250 url_matcher_.RemoveConditionSets(remove_from_url_matcher); 251 252 UpdateConditionCache(); 253 254 return std::string(); 255} 256 257std::string ContentRulesRegistry::RemoveAllRulesImpl( 258 const std::string& extension_id) { 259 // Search all identifiers of rules that belong to extension |extension_id|. 260 std::vector<std::string> rule_identifiers; 261 for (RulesMap::iterator i = content_rules_.begin(); 262 i != content_rules_.end(); ++i) { 263 const ContentRule::GlobalRuleId& global_rule_id = i->first; 264 if (global_rule_id.first == extension_id) 265 rule_identifiers.push_back(global_rule_id.second); 266 } 267 268 return RemoveRulesImpl(extension_id, rule_identifiers); 269} 270 271void ContentRulesRegistry::UpdateConditionCache() { 272 std::set<std::string> css_selectors; // We rely on this being sorted. 273 for (RulesMap::const_iterator i = content_rules_.begin(); 274 i != content_rules_.end(); ++i) { 275 ContentRule& rule = *i->second; 276 for (ContentConditionSet::const_iterator 277 condition = rule.conditions().begin(); 278 condition != rule.conditions().end(); ++condition) { 279 const std::vector<std::string>& condition_css_selectors = 280 (*condition)->css_selectors(); 281 css_selectors.insert(condition_css_selectors.begin(), 282 condition_css_selectors.end()); 283 } 284 } 285 286 if (css_selectors.size() != watched_css_selectors_.size() || 287 !std::equal(css_selectors.begin(), css_selectors.end(), 288 watched_css_selectors_.begin())) { 289 watched_css_selectors_.assign(css_selectors.begin(), css_selectors.end()); 290 291 for (content::RenderProcessHost::iterator it( 292 content::RenderProcessHost::AllHostsIterator()); 293 !it.IsAtEnd(); it.Advance()) { 294 content::RenderProcessHost* process = it.GetCurrentValue(); 295 if (process->GetBrowserContext() == profile()) 296 InstructRenderProcess(process); 297 } 298 } 299} 300 301void ContentRulesRegistry::InstructRenderProcess( 302 content::RenderProcessHost* process) { 303 process->Send(new ExtensionMsg_WatchPages(watched_css_selectors_)); 304} 305 306bool ContentRulesRegistry::IsEmpty() const { 307 return match_id_to_rule_.empty() && content_rules_.empty() && 308 url_matcher_.IsEmpty(); 309} 310 311ContentRulesRegistry::~ContentRulesRegistry() {} 312 313base::Time ContentRulesRegistry::GetExtensionInstallationTime( 314 const std::string& extension_id) const { 315 if (!extension_info_map_.get()) // May be NULL during testing. 316 return base::Time(); 317 318 return extension_info_map_->GetInstallTime(extension_id); 319} 320 321} // namespace extensions 322