OLD | NEW |
| (Empty) |
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/rules_registry_with_cache.h" | |
6 | |
7 #include "base/bind.h" | |
8 #include "base/logging.h" | |
9 #include "base/message_loop/message_loop.h" | |
10 #include "base/metrics/histogram.h" | |
11 #include "base/strings/stringprintf.h" | |
12 #include "base/time/time.h" | |
13 #include "base/values.h" | |
14 #include "chrome/browser/chrome_notification_types.h" | |
15 #include "chrome/browser/extensions/extension_info_map.h" | |
16 #include "chrome/browser/extensions/extension_prefs.h" | |
17 #include "chrome/browser/extensions/extension_service.h" | |
18 #include "chrome/browser/extensions/extension_system.h" | |
19 #include "chrome/browser/extensions/extension_util.h" | |
20 #include "chrome/browser/extensions/state_store.h" | |
21 #include "chrome/browser/profiles/profile.h" | |
22 #include "chrome/common/extensions/extension.h" | |
23 #include "content/public/browser/browser_thread.h" | |
24 #include "content/public/browser/notification_details.h" | |
25 #include "content/public/browser/notification_source.h" | |
26 | |
27 namespace { | |
28 | |
29 const char kSuccess[] = ""; | |
30 const char kDuplicateRuleId[] = "Duplicate rule ID: %s"; | |
31 | |
32 scoped_ptr<base::Value> RulesToValue( | |
33 const std::vector<linked_ptr<extensions::RulesRegistry::Rule> >& rules) { | |
34 scoped_ptr<base::ListValue> list(new base::ListValue()); | |
35 for (size_t i = 0; i < rules.size(); ++i) | |
36 list->Append(rules[i]->ToValue().release()); | |
37 return list.PassAs<base::Value>(); | |
38 } | |
39 | |
40 std::vector<linked_ptr<extensions::RulesRegistry::Rule> > RulesFromValue( | |
41 const base::Value* value) { | |
42 std::vector<linked_ptr<extensions::RulesRegistry::Rule> > rules; | |
43 | |
44 const base::ListValue* list = NULL; | |
45 if (!value || !value->GetAsList(&list)) | |
46 return rules; | |
47 | |
48 rules.reserve(list->GetSize()); | |
49 for (size_t i = 0; i < list->GetSize(); ++i) { | |
50 const base::DictionaryValue* dict = NULL; | |
51 if (!list->GetDictionary(i, &dict)) | |
52 continue; | |
53 linked_ptr<extensions::RulesRegistry::Rule> rule( | |
54 new extensions::RulesRegistry::Rule()); | |
55 if (extensions::RulesRegistry::Rule::Populate(*dict, rule.get())) | |
56 rules.push_back(rule); | |
57 } | |
58 | |
59 return rules; | |
60 } | |
61 | |
62 // Returns the key to use for storing declarative rules in the state store. | |
63 std::string GetDeclarativeRuleStorageKey(const std::string& event_name, | |
64 bool incognito) { | |
65 if (incognito) | |
66 return "declarative_rules.incognito." + event_name; | |
67 else | |
68 return "declarative_rules." + event_name; | |
69 } | |
70 | |
71 } // namespace | |
72 | |
73 | |
74 namespace extensions { | |
75 | |
76 // RulesRegistryWithCache | |
77 | |
78 RulesRegistryWithCache::RulesRegistryWithCache( | |
79 Profile* profile, | |
80 const std::string& event_name, | |
81 content::BrowserThread::ID owner_thread, | |
82 bool log_storage_init_delay, | |
83 scoped_ptr<RuleStorageOnUI>* ui_part) | |
84 : RulesRegistry(owner_thread, event_name), | |
85 weak_ptr_factory_(profile ? this : NULL), | |
86 storage_on_ui_( | |
87 (profile ? (new RuleStorageOnUI(profile, | |
88 event_name, | |
89 owner_thread, | |
90 weak_ptr_factory_.GetWeakPtr(), | |
91 log_storage_init_delay))->GetWeakPtr() | |
92 : base::WeakPtr<RuleStorageOnUI>())), | |
93 process_changed_rules_requested_(profile ? NOT_SCHEDULED_FOR_PROCESSING | |
94 : NEVER_PROCESS) { | |
95 if (!profile) { | |
96 CHECK(!ui_part); | |
97 return; | |
98 } | |
99 | |
100 ui_part->reset(storage_on_ui_.get()); | |
101 | |
102 storage_on_ui_->Init(); | |
103 } | |
104 | |
105 std::string RulesRegistryWithCache::AddRules( | |
106 const std::string& extension_id, | |
107 const std::vector<linked_ptr<Rule> >& rules) { | |
108 DCHECK(content::BrowserThread::CurrentlyOn(owner_thread())); | |
109 | |
110 // Verify that all rule IDs are new. | |
111 for (std::vector<linked_ptr<Rule> >::const_iterator i = | |
112 rules.begin(); i != rules.end(); ++i) { | |
113 const RuleId& rule_id = *((*i)->id); | |
114 RulesDictionaryKey key(extension_id, rule_id); | |
115 if (rules_.find(key) != rules_.end()) | |
116 return base::StringPrintf(kDuplicateRuleId, rule_id.c_str()); | |
117 } | |
118 | |
119 std::string error = AddRulesImpl(extension_id, rules); | |
120 | |
121 if (!error.empty()) | |
122 return error; | |
123 | |
124 // Commit all rules into |rules_| on success. | |
125 for (std::vector<linked_ptr<Rule> >::const_iterator i = | |
126 rules.begin(); i != rules.end(); ++i) { | |
127 const RuleId& rule_id = *((*i)->id); | |
128 RulesDictionaryKey key(extension_id, rule_id); | |
129 rules_[key] = *i; | |
130 } | |
131 | |
132 MaybeProcessChangedRules(extension_id); | |
133 return kSuccess; | |
134 } | |
135 | |
136 std::string RulesRegistryWithCache::RemoveRules( | |
137 const std::string& extension_id, | |
138 const std::vector<std::string>& rule_identifiers) { | |
139 DCHECK(content::BrowserThread::CurrentlyOn(owner_thread())); | |
140 | |
141 std::string error = RemoveRulesImpl(extension_id, rule_identifiers); | |
142 | |
143 if (!error.empty()) | |
144 return error; | |
145 | |
146 // Commit removal of rules from |rules_| on success. | |
147 for (std::vector<std::string>::const_iterator i = | |
148 rule_identifiers.begin(); i != rule_identifiers.end(); ++i) { | |
149 RulesDictionaryKey lookup_key(extension_id, *i); | |
150 rules_.erase(lookup_key); | |
151 } | |
152 | |
153 MaybeProcessChangedRules(extension_id); | |
154 return kSuccess; | |
155 } | |
156 | |
157 std::string RulesRegistryWithCache::RemoveAllRules( | |
158 const std::string& extension_id) { | |
159 DCHECK(content::BrowserThread::CurrentlyOn(owner_thread())); | |
160 | |
161 std::string error = RemoveAllRulesImpl(extension_id); | |
162 | |
163 if (!error.empty()) | |
164 return error; | |
165 | |
166 // Commit removal of rules from |rules_| on success. | |
167 for (RulesDictionary::const_iterator i = rules_.begin(); | |
168 i != rules_.end();) { | |
169 const RulesDictionaryKey& key = i->first; | |
170 ++i; | |
171 if (key.first == extension_id) | |
172 rules_.erase(key); | |
173 } | |
174 | |
175 MaybeProcessChangedRules(extension_id); | |
176 return kSuccess; | |
177 } | |
178 | |
179 std::string RulesRegistryWithCache::GetRules( | |
180 const std::string& extension_id, | |
181 const std::vector<std::string>& rule_identifiers, | |
182 std::vector<linked_ptr<RulesRegistry::Rule> >* out) { | |
183 DCHECK(content::BrowserThread::CurrentlyOn(owner_thread())); | |
184 | |
185 for (std::vector<std::string>::const_iterator i = rule_identifiers.begin(); | |
186 i != rule_identifiers.end(); ++i) { | |
187 RulesDictionaryKey lookup_key(extension_id, *i); | |
188 RulesDictionary::iterator entry = rules_.find(lookup_key); | |
189 if (entry != rules_.end()) | |
190 out->push_back(entry->second); | |
191 } | |
192 return kSuccess; | |
193 } | |
194 | |
195 std::string RulesRegistryWithCache::GetAllRules( | |
196 const std::string& extension_id, | |
197 std::vector<linked_ptr<RulesRegistry::Rule> >* out) { | |
198 DCHECK(content::BrowserThread::CurrentlyOn(owner_thread())); | |
199 | |
200 for (RulesDictionary::const_iterator i = rules_.begin(); | |
201 i != rules_.end(); ++i) { | |
202 const RulesDictionaryKey& key = i->first; | |
203 if (key.first == extension_id) | |
204 out->push_back(i->second); | |
205 } | |
206 return kSuccess; | |
207 } | |
208 | |
209 void RulesRegistryWithCache::OnExtensionUnloaded( | |
210 const std::string& extension_id) { | |
211 DCHECK(content::BrowserThread::CurrentlyOn(owner_thread())); | |
212 std::string error = RemoveAllRules(extension_id); | |
213 if (!error.empty()) | |
214 LOG(ERROR) << error; | |
215 } | |
216 | |
217 RulesRegistryWithCache::~RulesRegistryWithCache() { | |
218 } | |
219 | |
220 void RulesRegistryWithCache::MarkReady(base::Time storage_init_time) { | |
221 DCHECK(content::BrowserThread::CurrentlyOn(owner_thread())); | |
222 | |
223 if (!storage_init_time.is_null()) { | |
224 UMA_HISTOGRAM_TIMES("Extensions.DeclarativeRulesStorageInitialization", | |
225 base::Time::Now() - storage_init_time); | |
226 } | |
227 | |
228 ready_.Signal(); | |
229 } | |
230 | |
231 void RulesRegistryWithCache::DeserializeAndAddRules( | |
232 const std::string& extension_id, | |
233 scoped_ptr<base::Value> rules) { | |
234 DCHECK(content::BrowserThread::CurrentlyOn(owner_thread())); | |
235 | |
236 AddRules(extension_id, RulesFromValue(rules.get())); | |
237 } | |
238 | |
239 void RulesRegistryWithCache::ProcessChangedRules( | |
240 const std::string& extension_id) { | |
241 DCHECK(content::BrowserThread::CurrentlyOn(owner_thread())); | |
242 | |
243 process_changed_rules_requested_ = NOT_SCHEDULED_FOR_PROCESSING; | |
244 | |
245 std::vector<linked_ptr<RulesRegistry::Rule> > new_rules; | |
246 std::string error = GetAllRules(extension_id, &new_rules); | |
247 DCHECK_EQ(std::string(), error); | |
248 content::BrowserThread::PostTask( | |
249 content::BrowserThread::UI, | |
250 FROM_HERE, | |
251 base::Bind(&RuleStorageOnUI::WriteToStorage, | |
252 storage_on_ui_, | |
253 extension_id, | |
254 base::Passed(RulesToValue(new_rules)))); | |
255 } | |
256 | |
257 void RulesRegistryWithCache::MaybeProcessChangedRules( | |
258 const std::string& extension_id) { | |
259 if (process_changed_rules_requested_ != NOT_SCHEDULED_FOR_PROCESSING) | |
260 return; | |
261 | |
262 process_changed_rules_requested_ = SCHEDULED_FOR_PROCESSING; | |
263 ready_.Post(FROM_HERE, | |
264 base::Bind(&RulesRegistryWithCache::ProcessChangedRules, | |
265 weak_ptr_factory_.GetWeakPtr(), | |
266 extension_id)); | |
267 } | |
268 | |
269 // RulesRegistryWithCache::RuleStorageOnUI | |
270 | |
271 const char RulesRegistryWithCache::RuleStorageOnUI::kRulesStoredKey[] = | |
272 "has_declarative_rules"; | |
273 | |
274 RulesRegistryWithCache::RuleStorageOnUI::RuleStorageOnUI( | |
275 Profile* profile, | |
276 const std::string& event_name, | |
277 content::BrowserThread::ID rules_registry_thread, | |
278 base::WeakPtr<RulesRegistryWithCache> registry, | |
279 bool log_storage_init_delay) | |
280 : profile_(profile), | |
281 storage_key_(GetDeclarativeRuleStorageKey(event_name, | |
282 profile->IsOffTheRecord())), | |
283 rules_stored_key_(GetRulesStoredKey(event_name, | |
284 profile->IsOffTheRecord())), | |
285 log_storage_init_delay_(log_storage_init_delay), | |
286 registry_(registry), | |
287 rules_registry_thread_(rules_registry_thread), | |
288 notified_registry_(false), | |
289 weak_ptr_factory_(this) {} | |
290 | |
291 RulesRegistryWithCache::RuleStorageOnUI::~RuleStorageOnUI() {} | |
292 | |
293 // Returns the key to use for storing whether the rules have been stored. | |
294 // static | |
295 std::string RulesRegistryWithCache::RuleStorageOnUI::GetRulesStoredKey( | |
296 const std::string& event_name, | |
297 bool incognito) { | |
298 std::string result(kRulesStoredKey); | |
299 result += incognito ? ".incognito." : "."; | |
300 return result + event_name; | |
301 } | |
302 | |
303 // This is called from the constructor of RulesRegistryWithCache, so it is | |
304 // important that it both | |
305 // 1. calls no (in particular virtual) methods of the rules registry, and | |
306 // 2. does not create scoped_refptr holding the registry. (A short-lived | |
307 // scoped_refptr might delete the rules registry before it is constructed.) | |
308 void RulesRegistryWithCache::RuleStorageOnUI::Init() { | |
309 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
310 | |
311 ExtensionSystem& system = *ExtensionSystem::Get(profile_); | |
312 extensions::StateStore* store = system.rules_store(); | |
313 if (store) | |
314 store->RegisterKey(storage_key_); | |
315 | |
316 registrar_.Add(this, | |
317 chrome::NOTIFICATION_EXTENSION_LOADED, | |
318 content::Source<Profile>(profile_->GetOriginalProfile())); | |
319 | |
320 if (profile_->IsOffTheRecord()) | |
321 log_storage_init_delay_ = false; | |
322 | |
323 system.ready().Post( | |
324 FROM_HERE, | |
325 base::Bind(&RuleStorageOnUI::ReadRulesForInstalledExtensions, | |
326 GetWeakPtr())); | |
327 system.ready().Post(FROM_HERE, | |
328 base::Bind(&RuleStorageOnUI::CheckIfReady, GetWeakPtr())); | |
329 } | |
330 | |
331 void RulesRegistryWithCache::RuleStorageOnUI::WriteToStorage( | |
332 const std::string& extension_id, | |
333 scoped_ptr<base::Value> value) { | |
334 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
335 if (!profile_) | |
336 return; | |
337 | |
338 const base::ListValue* rules = NULL; | |
339 CHECK(value->GetAsList(&rules)); | |
340 bool rules_stored_previously = GetDeclarativeRulesStored(extension_id); | |
341 bool store_rules = !rules->empty(); | |
342 SetDeclarativeRulesStored(extension_id, store_rules); | |
343 if (!rules_stored_previously && !store_rules) | |
344 return; | |
345 | |
346 StateStore* store = ExtensionSystem::Get(profile_)->rules_store(); | |
347 if (store) | |
348 store->SetExtensionValue(extension_id, storage_key_, value.Pass()); | |
349 } | |
350 | |
351 void RulesRegistryWithCache::RuleStorageOnUI::Observe( | |
352 int type, | |
353 const content::NotificationSource& source, | |
354 const content::NotificationDetails& details) { | |
355 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
356 DCHECK(type == chrome::NOTIFICATION_EXTENSION_LOADED); | |
357 | |
358 const extensions::Extension* extension = | |
359 content::Details<const extensions::Extension>(details).ptr(); | |
360 // TODO(mpcomplete): This API check should generalize to any use of | |
361 // declarative rules, not just webRequest. | |
362 if (extension->HasAPIPermission(APIPermission::kDeclarativeContent) || | |
363 extension->HasAPIPermission(APIPermission::kDeclarativeWebRequest)) { | |
364 ExtensionInfoMap* extension_info_map = | |
365 ExtensionSystem::Get(profile_)->info_map(); | |
366 if (profile_->IsOffTheRecord() && | |
367 !extension_info_map->IsIncognitoEnabled(extension->id())) { | |
368 // Ignore this extension. | |
369 } else { | |
370 ReadFromStorage(extension->id()); | |
371 } | |
372 } | |
373 } | |
374 | |
375 void RulesRegistryWithCache::RuleStorageOnUI::CheckIfReady() { | |
376 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
377 if (notified_registry_ || !waiting_for_extensions_.empty()) | |
378 return; | |
379 | |
380 content::BrowserThread::PostTask( | |
381 rules_registry_thread_, | |
382 FROM_HERE, | |
383 base::Bind( | |
384 &RulesRegistryWithCache::MarkReady, registry_, storage_init_time_)); | |
385 notified_registry_ = true; | |
386 } | |
387 | |
388 void | |
389 RulesRegistryWithCache::RuleStorageOnUI::ReadRulesForInstalledExtensions() { | |
390 ExtensionSystem& system = *ExtensionSystem::Get(profile_); | |
391 ExtensionService* extension_service = system.extension_service(); | |
392 DCHECK(extension_service); | |
393 // In an OTR profile, we start on top of a normal profile already, so the | |
394 // extension service should be ready. | |
395 DCHECK(!profile_->IsOffTheRecord() || extension_service->is_ready()); | |
396 if (extension_service->is_ready()) { | |
397 const ExtensionSet* extensions = extension_service->extensions(); | |
398 for (ExtensionSet::const_iterator i = extensions->begin(); | |
399 i != extensions->end(); | |
400 ++i) { | |
401 bool needs_apis_storing_rules = | |
402 (*i)->HasAPIPermission(APIPermission::kDeclarativeContent) || | |
403 (*i)->HasAPIPermission(APIPermission::kDeclarativeWebRequest); | |
404 bool respects_off_the_record = | |
405 !(profile_->IsOffTheRecord()) || | |
406 extension_util::IsIncognitoEnabled((*i)->id(), extension_service); | |
407 if (needs_apis_storing_rules && respects_off_the_record) | |
408 ReadFromStorage((*i)->id()); | |
409 } | |
410 } | |
411 } | |
412 | |
413 void RulesRegistryWithCache::RuleStorageOnUI::ReadFromStorage( | |
414 const std::string& extension_id) { | |
415 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
416 if (!profile_) | |
417 return; | |
418 | |
419 if (log_storage_init_delay_ && storage_init_time_.is_null()) | |
420 storage_init_time_ = base::Time::Now(); | |
421 | |
422 if (!GetDeclarativeRulesStored(extension_id)) { | |
423 ExtensionSystem::Get(profile_)->ready().Post( | |
424 FROM_HERE, base::Bind(&RuleStorageOnUI::CheckIfReady, GetWeakPtr())); | |
425 return; | |
426 } | |
427 | |
428 extensions::StateStore* store = ExtensionSystem::Get(profile_)->rules_store(); | |
429 if (!store) | |
430 return; | |
431 waiting_for_extensions_.insert(extension_id); | |
432 store->GetExtensionValue(extension_id, | |
433 storage_key_, | |
434 base::Bind(&RuleStorageOnUI::ReadFromStorageCallback, | |
435 weak_ptr_factory_.GetWeakPtr(), | |
436 extension_id)); | |
437 } | |
438 | |
439 void RulesRegistryWithCache::RuleStorageOnUI::ReadFromStorageCallback( | |
440 const std::string& extension_id, | |
441 scoped_ptr<base::Value> value) { | |
442 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
443 content::BrowserThread::PostTask( | |
444 rules_registry_thread_, | |
445 FROM_HERE, | |
446 base::Bind(&RulesRegistryWithCache::DeserializeAndAddRules, | |
447 registry_, | |
448 extension_id, | |
449 base::Passed(&value))); | |
450 | |
451 waiting_for_extensions_.erase(extension_id); | |
452 | |
453 if (waiting_for_extensions_.empty()) | |
454 ExtensionSystem::Get(profile_)->ready().Post( | |
455 FROM_HERE, base::Bind(&RuleStorageOnUI::CheckIfReady, GetWeakPtr())); | |
456 } | |
457 | |
458 bool RulesRegistryWithCache::RuleStorageOnUI::GetDeclarativeRulesStored( | |
459 const std::string& extension_id) const { | |
460 CHECK(profile_); | |
461 const ExtensionScopedPrefs* extension_prefs = ExtensionPrefs::Get(profile_); | |
462 | |
463 bool rules_stored = true; | |
464 if (extension_prefs->ReadPrefAsBoolean( | |
465 extension_id, rules_stored_key_, &rules_stored)) | |
466 return rules_stored; | |
467 | |
468 // Safe default -- if we don't know that the rules are not stored, we force | |
469 // a read by returning true. | |
470 return true; | |
471 } | |
472 | |
473 void RulesRegistryWithCache::RuleStorageOnUI::SetDeclarativeRulesStored( | |
474 const std::string& extension_id, | |
475 bool rules_stored) { | |
476 CHECK(profile_); | |
477 ExtensionScopedPrefs* extension_prefs = ExtensionPrefs::Get(profile_); | |
478 extension_prefs->UpdateExtensionPref( | |
479 extension_id, | |
480 rules_stored_key_, | |
481 new base::FundamentalValue(rules_stored)); | |
482 } | |
483 | |
484 } // namespace extensions | |
OLD | NEW |