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