Home | History | Annotate | Download | only in activity_log
      1 // Copyright (c) 2011 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/activity_log/activity_log.h"
      6 
      7 #include <set>
      8 #include <vector>
      9 
     10 #include "base/command_line.h"
     11 #include "base/json/json_string_value_serializer.h"
     12 #include "base/lazy_instance.h"
     13 #include "base/logging.h"
     14 #include "base/strings/string_util.h"
     15 #include "base/strings/utf_string_conversions.h"
     16 #include "base/threading/thread_checker.h"
     17 #include "chrome/browser/extensions/activity_log/activity_action_constants.h"
     18 #include "chrome/browser/extensions/activity_log/counting_policy.h"
     19 #include "chrome/browser/extensions/activity_log/fullstream_ui_policy.h"
     20 #include "chrome/browser/extensions/activity_log/uma_policy.h"
     21 #include "chrome/browser/extensions/api/activity_log_private/activity_log_private_api.h"
     22 #include "chrome/browser/extensions/extension_tab_util.h"
     23 #include "chrome/browser/prefs/pref_service_syncable.h"
     24 #include "chrome/browser/prerender/prerender_manager.h"
     25 #include "chrome/browser/prerender/prerender_manager_factory.h"
     26 #include "chrome/browser/profiles/profile.h"
     27 #include "chrome/browser/ui/browser.h"
     28 #include "chrome/common/chrome_constants.h"
     29 #include "chrome/common/chrome_switches.h"
     30 #include "chrome/common/pref_names.h"
     31 #include "content/public/browser/browser_thread.h"
     32 #include "content/public/browser/web_contents.h"
     33 #include "extensions/browser/extension_registry.h"
     34 #include "extensions/browser/extension_registry_factory.h"
     35 #include "extensions/browser/extension_system.h"
     36 #include "extensions/browser/extension_system_provider.h"
     37 #include "extensions/browser/extensions_browser_client.h"
     38 #include "extensions/common/extension.h"
     39 #include "extensions/common/one_shot_event.h"
     40 #include "third_party/re2/re2/re2.h"
     41 #include "url/gurl.h"
     42 
     43 namespace constants = activity_log_constants;
     44 
     45 namespace extensions {
     46 
     47 namespace {
     48 
     49 using constants::kArgUrlPlaceholder;
     50 using content::BrowserThread;
     51 
     52 // If DOM API methods start with this string, we flag them as being of type
     53 // DomActionType::XHR.
     54 const char kDomXhrPrefix[] = "XMLHttpRequest.";
     55 
     56 // Specifies a possible action to take to get an extracted URL in the ApiInfo
     57 // structure below.
     58 enum Transformation {
     59   NONE,
     60   DICT_LOOKUP,
     61   LOOKUP_TAB_ID,
     62 };
     63 
     64 // Information about specific Chrome and DOM APIs, such as which contain
     65 // arguments that should be extracted into the arg_url field of an Action.
     66 struct ApiInfo {
     67   // The lookup key consists of the action_type and api_name in the Action
     68   // object.
     69   Action::ActionType action_type;
     70   const char* api_name;
     71 
     72   // If non-negative, an index into args might contain a URL to be extracted
     73   // into arg_url.
     74   int arg_url_index;
     75 
     76   // A transformation to apply to the data found at index arg_url_index in the
     77   // argument list.
     78   //
     79   // If NONE, the data is expected to be a string which is treated as a URL.
     80   //
     81   // If LOOKUP_TAB_ID, the data is either an integer which is treated as a tab
     82   // ID and translated (in the context of a provided Profile), or a list of tab
     83   // IDs which are translated.
     84   //
     85   // If DICT_LOOKUP, the data is expected to be a dictionary, and
     86   // arg_url_dict_path is a path (list of keys delimited by ".") where a URL
     87   // string is to be found.
     88   Transformation arg_url_transform;
     89   const char* arg_url_dict_path;
     90 };
     91 
     92 static const ApiInfo kApiInfoTable[] = {
     93     // Tabs APIs that require tab ID translation
     94     {Action::ACTION_API_CALL, "tabs.connect", 0, LOOKUP_TAB_ID, NULL},
     95     {Action::ACTION_API_CALL, "tabs.detectLanguage", 0, LOOKUP_TAB_ID, NULL},
     96     {Action::ACTION_API_CALL, "tabs.duplicate", 0, LOOKUP_TAB_ID, NULL},
     97     {Action::ACTION_API_CALL, "tabs.executeScript", 0, LOOKUP_TAB_ID, NULL},
     98     {Action::ACTION_API_CALL, "tabs.get", 0, LOOKUP_TAB_ID, NULL},
     99     {Action::ACTION_API_CALL, "tabs.insertCSS", 0, LOOKUP_TAB_ID, NULL},
    100     {Action::ACTION_API_CALL, "tabs.move", 0, LOOKUP_TAB_ID, NULL},
    101     {Action::ACTION_API_CALL, "tabs.reload", 0, LOOKUP_TAB_ID, NULL},
    102     {Action::ACTION_API_CALL, "tabs.remove", 0, LOOKUP_TAB_ID, NULL},
    103     {Action::ACTION_API_CALL, "tabs.sendMessage", 0, LOOKUP_TAB_ID, NULL},
    104     {Action::ACTION_API_CALL, "tabs.update", 0, LOOKUP_TAB_ID, NULL},
    105     {Action::ACTION_API_EVENT, "tabs.onUpdated", 0, LOOKUP_TAB_ID, NULL},
    106     {Action::ACTION_API_EVENT, "tabs.onMoved", 0, LOOKUP_TAB_ID, NULL},
    107     {Action::ACTION_API_EVENT, "tabs.onDetached", 0, LOOKUP_TAB_ID, NULL},
    108     {Action::ACTION_API_EVENT, "tabs.onAttached", 0, LOOKUP_TAB_ID, NULL},
    109     {Action::ACTION_API_EVENT, "tabs.onRemoved", 0, LOOKUP_TAB_ID, NULL},
    110     {Action::ACTION_API_EVENT, "tabs.onReplaced", 0, LOOKUP_TAB_ID, NULL},
    111 
    112     // Other APIs that accept URLs as strings
    113     {Action::ACTION_API_CALL, "bookmarks.create", 0, DICT_LOOKUP, "url"},
    114     {Action::ACTION_API_CALL, "bookmarks.update", 1, DICT_LOOKUP, "url"},
    115     {Action::ACTION_API_CALL, "cookies.get", 0, DICT_LOOKUP, "url"},
    116     {Action::ACTION_API_CALL, "cookies.getAll", 0, DICT_LOOKUP, "url"},
    117     {Action::ACTION_API_CALL, "cookies.remove", 0, DICT_LOOKUP, "url"},
    118     {Action::ACTION_API_CALL, "cookies.set", 0, DICT_LOOKUP, "url"},
    119     {Action::ACTION_API_CALL, "downloads.download", 0, DICT_LOOKUP, "url"},
    120     {Action::ACTION_API_CALL, "history.addUrl", 0, DICT_LOOKUP, "url"},
    121     {Action::ACTION_API_CALL, "history.deleteUrl", 0, DICT_LOOKUP, "url"},
    122     {Action::ACTION_API_CALL, "history.getVisits", 0, DICT_LOOKUP, "url"},
    123     {Action::ACTION_API_CALL, "webstore.install", 0, NONE, NULL},
    124     {Action::ACTION_API_CALL, "windows.create", 0, DICT_LOOKUP, "url"},
    125     {Action::ACTION_DOM_ACCESS, "Document.location", 0, NONE, NULL},
    126     {Action::ACTION_DOM_ACCESS, "HTMLAnchorElement.href", 0, NONE, NULL},
    127     {Action::ACTION_DOM_ACCESS, "HTMLButtonElement.formAction", 0, NONE, NULL},
    128     {Action::ACTION_DOM_ACCESS, "HTMLEmbedElement.src", 0, NONE, NULL},
    129     {Action::ACTION_DOM_ACCESS, "HTMLFormElement.action", 0, NONE, NULL},
    130     {Action::ACTION_DOM_ACCESS, "HTMLFrameElement.src", 0, NONE, NULL},
    131     {Action::ACTION_DOM_ACCESS, "HTMLHtmlElement.manifest", 0, NONE, NULL},
    132     {Action::ACTION_DOM_ACCESS, "HTMLIFrameElement.src", 0, NONE, NULL},
    133     {Action::ACTION_DOM_ACCESS, "HTMLImageElement.longDesc", 0, NONE, NULL},
    134     {Action::ACTION_DOM_ACCESS, "HTMLImageElement.src", 0, NONE, NULL},
    135     {Action::ACTION_DOM_ACCESS, "HTMLImageElement.lowsrc", 0, NONE, NULL},
    136     {Action::ACTION_DOM_ACCESS, "HTMLInputElement.formAction", 0, NONE, NULL},
    137     {Action::ACTION_DOM_ACCESS, "HTMLInputElement.src", 0, NONE, NULL},
    138     {Action::ACTION_DOM_ACCESS, "HTMLLinkElement.href", 0, NONE, NULL},
    139     {Action::ACTION_DOM_ACCESS, "HTMLMediaElement.src", 0, NONE, NULL},
    140     {Action::ACTION_DOM_ACCESS, "HTMLMediaElement.currentSrc", 0, NONE, NULL},
    141     {Action::ACTION_DOM_ACCESS, "HTMLModElement.cite", 0, NONE, NULL},
    142     {Action::ACTION_DOM_ACCESS, "HTMLObjectElement.data", 0, NONE, NULL},
    143     {Action::ACTION_DOM_ACCESS, "HTMLQuoteElement.cite", 0, NONE, NULL},
    144     {Action::ACTION_DOM_ACCESS, "HTMLScriptElement.src", 0, NONE, NULL},
    145     {Action::ACTION_DOM_ACCESS, "HTMLSourceElement.src", 0, NONE, NULL},
    146     {Action::ACTION_DOM_ACCESS, "HTMLTrackElement.src", 0, NONE, NULL},
    147     {Action::ACTION_DOM_ACCESS, "HTMLVideoElement.poster", 0, NONE, NULL},
    148     {Action::ACTION_DOM_ACCESS, "Location.assign", 0, NONE, NULL},
    149     {Action::ACTION_DOM_ACCESS, "Location.replace", 0, NONE, NULL},
    150     {Action::ACTION_DOM_ACCESS, "Window.location", 0, NONE, NULL},
    151     {Action::ACTION_DOM_ACCESS, "XMLHttpRequest.open", 1, NONE, NULL}};
    152 
    153 // A singleton class which provides lookups into the kApiInfoTable data
    154 // structure.  It inserts all data into a map on first lookup.
    155 class ApiInfoDatabase {
    156  public:
    157   static ApiInfoDatabase* GetInstance() {
    158     return Singleton<ApiInfoDatabase>::get();
    159   }
    160 
    161   // Retrieves an ApiInfo record for the given Action type.  Returns either a
    162   // pointer to the record, or NULL if no such record was found.
    163   const ApiInfo* Lookup(Action::ActionType action_type,
    164                         const std::string& api_name) const {
    165     std::map<std::string, const ApiInfo*>::const_iterator i =
    166         api_database_.find(api_name);
    167     if (i == api_database_.end())
    168       return NULL;
    169     if (i->second->action_type != action_type)
    170       return NULL;
    171     return i->second;
    172   }
    173 
    174  private:
    175   ApiInfoDatabase() {
    176     for (size_t i = 0; i < arraysize(kApiInfoTable); i++) {
    177       const ApiInfo* info = &kApiInfoTable[i];
    178       api_database_[info->api_name] = info;
    179     }
    180   }
    181   virtual ~ApiInfoDatabase() {}
    182 
    183   // The map is keyed by API name only, since API names aren't be repeated
    184   // across multiple action types in kApiInfoTable.  However, the action type
    185   // should still be checked before returning a positive match.
    186   std::map<std::string, const ApiInfo*> api_database_;
    187 
    188   friend struct DefaultSingletonTraits<ApiInfoDatabase>;
    189   DISALLOW_COPY_AND_ASSIGN(ApiInfoDatabase);
    190 };
    191 
    192 // Gets the URL for a given tab ID.  Helper method for ExtractUrls.  Returns
    193 // true if able to perform the lookup.  The URL is stored to *url, and
    194 // *is_incognito is set to indicate whether the URL is for an incognito tab.
    195 bool GetUrlForTabId(int tab_id,
    196                     Profile* profile,
    197                     GURL* url,
    198                     bool* is_incognito) {
    199   content::WebContents* contents = NULL;
    200   Browser* browser = NULL;
    201   bool found = ExtensionTabUtil::GetTabById(
    202       tab_id,
    203       profile,
    204       true,  // Search incognito tabs, too.
    205       &browser,
    206       NULL,
    207       &contents,
    208       NULL);
    209 
    210   if (found) {
    211     *url = contents->GetURL();
    212     *is_incognito = browser->profile()->IsOffTheRecord();
    213     return true;
    214   } else {
    215     return false;
    216   }
    217 }
    218 
    219 // Resolves an argument URL relative to a base page URL.  If the page URL is
    220 // not valid, then only absolute argument URLs are supported.
    221 bool ResolveUrl(const GURL& base, const std::string& arg, GURL* arg_out) {
    222   if (base.is_valid())
    223     *arg_out = base.Resolve(arg);
    224   else
    225     *arg_out = GURL(arg);
    226 
    227   return arg_out->is_valid();
    228 }
    229 
    230 // Performs processing of the Action object to extract URLs from the argument
    231 // list and translate tab IDs to URLs, according to the API call metadata in
    232 // kApiInfoTable.  Mutates the Action object in place.  There is a small chance
    233 // that the tab id->URL translation could be wrong, if the tab has already been
    234 // navigated by the time of invocation.
    235 //
    236 // Any extracted URL is stored into the arg_url field of the action, and the
    237 // URL in the argument list is replaced with the marker value "<arg_url>".  For
    238 // APIs that take a list of tab IDs, extracts the first valid URL into arg_url
    239 // and overwrites the other tab IDs in the argument list with the translated
    240 // URL.
    241 void ExtractUrls(scoped_refptr<Action> action, Profile* profile) {
    242   const ApiInfo* api_info = ApiInfoDatabase::GetInstance()->Lookup(
    243       action->action_type(), action->api_name());
    244   if (api_info == NULL)
    245     return;
    246 
    247   int url_index = api_info->arg_url_index;
    248 
    249   if (!action->args() || url_index < 0 ||
    250       static_cast<size_t>(url_index) >= action->args()->GetSize())
    251     return;
    252 
    253   // Do not overwrite an existing arg_url value in the Action, so that callers
    254   // have the option of doing custom arg_url extraction.
    255   if (action->arg_url().is_valid())
    256     return;
    257 
    258   GURL arg_url;
    259   bool arg_incognito = action->page_incognito();
    260 
    261   switch (api_info->arg_url_transform) {
    262     case NONE: {
    263       // No translation needed; just extract the URL directly from a raw string
    264       // or from a dictionary.  Succeeds if we can find a string in the
    265       // argument list and that the string resolves to a valid URL.
    266       std::string url_string;
    267       if (action->args()->GetString(url_index, &url_string) &&
    268           ResolveUrl(action->page_url(), url_string, &arg_url)) {
    269         action->mutable_args()->Set(url_index,
    270                                     new base::StringValue(kArgUrlPlaceholder));
    271       }
    272       break;
    273     }
    274 
    275     case DICT_LOOKUP: {
    276       CHECK(api_info->arg_url_dict_path);
    277       // Look up the URL from a dictionary at the specified location.  Succeeds
    278       // if we can find a dictionary in the argument list, the dictionary
    279       // contains the specified key, and the corresponding value resolves to a
    280       // valid URL.
    281       base::DictionaryValue* dict = NULL;
    282       std::string url_string;
    283       if (action->mutable_args()->GetDictionary(url_index, &dict) &&
    284           dict->GetString(api_info->arg_url_dict_path, &url_string) &&
    285           ResolveUrl(action->page_url(), url_string, &arg_url)) {
    286         dict->SetString(api_info->arg_url_dict_path, kArgUrlPlaceholder);
    287       }
    288       break;
    289     }
    290 
    291     case LOOKUP_TAB_ID: {
    292       // Translation of tab IDs to URLs has been requested.  There are two
    293       // cases to consider: either a single integer or a list of integers (when
    294       // multiple tabs are manipulated).
    295       int tab_id;
    296       base::ListValue* tab_list = NULL;
    297       if (action->args()->GetInteger(url_index, &tab_id)) {
    298         // Single tab ID to translate.
    299         GetUrlForTabId(tab_id, profile, &arg_url, &arg_incognito);
    300         if (arg_url.is_valid()) {
    301           action->mutable_args()->Set(
    302               url_index, new base::StringValue(kArgUrlPlaceholder));
    303         }
    304       } else if (action->mutable_args()->GetList(url_index, &tab_list)) {
    305         // A list of possible IDs to translate.  Work through in reverse order
    306         // so the last one translated is left in arg_url.
    307         int extracted_index = -1;  // Which list item is copied to arg_url?
    308         for (int i = tab_list->GetSize() - 1; i >= 0; --i) {
    309           if (tab_list->GetInteger(i, &tab_id) &&
    310               GetUrlForTabId(tab_id, profile, &arg_url, &arg_incognito)) {
    311             if (!arg_incognito)
    312               tab_list->Set(i, new base::StringValue(arg_url.spec()));
    313             extracted_index = i;
    314           }
    315         }
    316         if (extracted_index >= 0) {
    317           tab_list->Set(
    318               extracted_index, new base::StringValue(kArgUrlPlaceholder));
    319         }
    320       }
    321       break;
    322     }
    323 
    324     default:
    325       NOTREACHED();
    326   }
    327 
    328   if (arg_url.is_valid()) {
    329     action->set_arg_incognito(arg_incognito);
    330     action->set_arg_url(arg_url);
    331   }
    332 }
    333 
    334 }  // namespace
    335 
    336 // SET THINGS UP. --------------------------------------------------------------
    337 
    338 static base::LazyInstance<BrowserContextKeyedAPIFactory<ActivityLog> >
    339     g_factory = LAZY_INSTANCE_INITIALIZER;
    340 
    341 BrowserContextKeyedAPIFactory<ActivityLog>* ActivityLog::GetFactoryInstance() {
    342   return g_factory.Pointer();
    343 }
    344 
    345 // static
    346 ActivityLog* ActivityLog::GetInstance(content::BrowserContext* context) {
    347   return ActivityLog::GetFactoryInstance()->Get(
    348       Profile::FromBrowserContext(context));
    349 }
    350 
    351 // Use GetInstance instead of directly creating an ActivityLog.
    352 ActivityLog::ActivityLog(content::BrowserContext* context)
    353     : database_policy_(NULL),
    354       database_policy_type_(ActivityLogPolicy::POLICY_INVALID),
    355       uma_policy_(NULL),
    356       profile_(Profile::FromBrowserContext(context)),
    357       db_enabled_(false),
    358       testing_mode_(false),
    359       has_threads_(true),
    360       extension_registry_observer_(this),
    361       watchdog_apps_active_(0) {
    362   // This controls whether logging statements are printed & which policy is set.
    363   testing_mode_ = CommandLine::ForCurrentProcess()->HasSwitch(
    364     switches::kEnableExtensionActivityLogTesting);
    365 
    366   // Check if the watchdog extension is previously installed and active.
    367   // It was originally a boolean, but we've had to move to an integer. Handle
    368   // the legacy case.
    369   // TODO(felt): In M34, remove the legacy code & old pref.
    370   if (profile_->GetPrefs()->GetBoolean(prefs::kWatchdogExtensionActiveOld))
    371     profile_->GetPrefs()->SetInteger(prefs::kWatchdogExtensionActive, 1);
    372   watchdog_apps_active_ =
    373       profile_->GetPrefs()->GetInteger(prefs::kWatchdogExtensionActive);
    374 
    375   observers_ = new ObserverListThreadSafe<Observer>;
    376 
    377   // Check that the right threads exist for logging to the database.
    378   // If not, we shouldn't try to do things that require them.
    379   if (!BrowserThread::IsMessageLoopValid(BrowserThread::DB) ||
    380       !BrowserThread::IsMessageLoopValid(BrowserThread::FILE) ||
    381       !BrowserThread::IsMessageLoopValid(BrowserThread::IO)) {
    382     has_threads_ = false;
    383   }
    384 
    385   db_enabled_ = has_threads_
    386       && (CommandLine::ForCurrentProcess()->
    387           HasSwitch(switches::kEnableExtensionActivityLogging)
    388       || watchdog_apps_active_);
    389 
    390   ExtensionSystem::Get(profile_)->ready().Post(
    391       FROM_HERE,
    392       base::Bind(&ActivityLog::StartObserving, base::Unretained(this)));
    393 
    394   if (!profile_->IsOffTheRecord())
    395     uma_policy_ = new UmaPolicy(profile_);
    396 
    397   ChooseDatabasePolicy();
    398 }
    399 
    400 void ActivityLog::SetDatabasePolicy(
    401     ActivityLogPolicy::PolicyType policy_type) {
    402   if (database_policy_type_ == policy_type)
    403     return;
    404   if (!IsDatabaseEnabled() && !IsWatchdogAppActive())
    405     return;
    406 
    407   // Deleting the old policy takes place asynchronously, on the database
    408   // thread.  Initializing a new policy below similarly happens
    409   // asynchronously.  Since the two operations are both queued for the
    410   // database, the queue ordering should ensure that the deletion completes
    411   // before database initialization occurs.
    412   //
    413   // However, changing policies at runtime is still not recommended, and
    414   // likely only should be done for unit tests.
    415   if (database_policy_)
    416     database_policy_->Close();
    417 
    418   switch (policy_type) {
    419     case ActivityLogPolicy::POLICY_FULLSTREAM:
    420       database_policy_ = new FullStreamUIPolicy(profile_);
    421       break;
    422     case ActivityLogPolicy::POLICY_COUNTS:
    423       database_policy_ = new CountingPolicy(profile_);
    424       break;
    425     default:
    426       NOTREACHED();
    427   }
    428   database_policy_->Init();
    429   database_policy_type_ = policy_type;
    430 }
    431 
    432 ActivityLog::~ActivityLog() {
    433   if (uma_policy_)
    434     uma_policy_->Close();
    435   if (database_policy_)
    436     database_policy_->Close();
    437 }
    438 
    439 // MAINTAIN STATUS. ------------------------------------------------------------
    440 
    441 void ActivityLog::StartObserving() {
    442   extension_registry_observer_.Add(ExtensionRegistry::Get(profile_));
    443 }
    444 
    445 void ActivityLog::ChooseDatabasePolicy() {
    446   if (!(IsDatabaseEnabled() || IsWatchdogAppActive()))
    447     return;
    448   if (testing_mode_)
    449     SetDatabasePolicy(ActivityLogPolicy::POLICY_FULLSTREAM);
    450   else
    451     SetDatabasePolicy(ActivityLogPolicy::POLICY_COUNTS);
    452 }
    453 
    454 bool ActivityLog::IsDatabaseEnabled() {
    455   // Make sure we are not enabled when there are no threads.
    456   DCHECK(has_threads_ || !db_enabled_);
    457   return db_enabled_;
    458 }
    459 
    460 bool ActivityLog::IsWatchdogAppActive() {
    461   return (watchdog_apps_active_ > 0);
    462 }
    463 
    464 void ActivityLog::SetWatchdogAppActiveForTesting(bool active) {
    465   watchdog_apps_active_ = active ? 1 : 0;
    466 }
    467 
    468 void ActivityLog::OnExtensionLoaded(content::BrowserContext* browser_context,
    469                                     const Extension* extension) {
    470   if (!ActivityLogAPI::IsExtensionWhitelisted(extension->id())) return;
    471   if (has_threads_)
    472     db_enabled_ = true;
    473   watchdog_apps_active_++;
    474   profile_->GetPrefs()->SetInteger(prefs::kWatchdogExtensionActive,
    475                                    watchdog_apps_active_);
    476   if (watchdog_apps_active_ == 1)
    477     ChooseDatabasePolicy();
    478 }
    479 
    480 void ActivityLog::OnExtensionUnloaded(content::BrowserContext* browser_context,
    481                                       const Extension* extension,
    482                                       UnloadedExtensionInfo::Reason reason) {
    483   if (!ActivityLogAPI::IsExtensionWhitelisted(extension->id())) return;
    484   watchdog_apps_active_--;
    485   profile_->GetPrefs()->SetInteger(prefs::kWatchdogExtensionActive,
    486                                    watchdog_apps_active_);
    487   if (watchdog_apps_active_ == 0 &&
    488       !CommandLine::ForCurrentProcess()->HasSwitch(
    489           switches::kEnableExtensionActivityLogging)) {
    490     db_enabled_ = false;
    491   }
    492 }
    493 
    494 // OnExtensionUnloaded will also be called right before this.
    495 void ActivityLog::OnExtensionUninstalled(
    496     content::BrowserContext* browser_context,
    497     const Extension* extension,
    498     extensions::UninstallReason reason) {
    499   if (ActivityLogAPI::IsExtensionWhitelisted(extension->id()) &&
    500       !CommandLine::ForCurrentProcess()->HasSwitch(
    501           switches::kEnableExtensionActivityLogging) &&
    502       watchdog_apps_active_ == 0) {
    503     DeleteDatabase();
    504   } else if (database_policy_) {
    505     database_policy_->RemoveExtensionData(extension->id());
    506   }
    507 }
    508 
    509 void ActivityLog::AddObserver(ActivityLog::Observer* observer) {
    510   observers_->AddObserver(observer);
    511 }
    512 
    513 void ActivityLog::RemoveObserver(ActivityLog::Observer* observer) {
    514   observers_->RemoveObserver(observer);
    515 }
    516 
    517 // static
    518 void ActivityLog::RegisterProfilePrefs(
    519     user_prefs::PrefRegistrySyncable* registry) {
    520   registry->RegisterIntegerPref(
    521       prefs::kWatchdogExtensionActive,
    522       false,
    523       user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
    524   registry->RegisterBooleanPref(
    525       prefs::kWatchdogExtensionActiveOld,
    526       false,
    527       user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
    528 }
    529 
    530 // LOG ACTIONS. ----------------------------------------------------------------
    531 
    532 void ActivityLog::LogAction(scoped_refptr<Action> action) {
    533   if (ActivityLogAPI::IsExtensionWhitelisted(action->extension_id()))
    534     return;
    535 
    536   // Perform some preprocessing of the Action data: convert tab IDs to URLs and
    537   // mask out incognito URLs if appropriate.
    538   ExtractUrls(action, profile_);
    539 
    540   // Mark DOM XHR requests as such, for easier processing later.
    541   if (action->action_type() == Action::ACTION_DOM_ACCESS &&
    542       StartsWithASCII(action->api_name(), kDomXhrPrefix, true) &&
    543       action->other()) {
    544     base::DictionaryValue* other = action->mutable_other();
    545     int dom_verb = -1;
    546     if (other->GetInteger(constants::kActionDomVerb, &dom_verb) &&
    547         dom_verb == DomActionType::METHOD) {
    548       other->SetInteger(constants::kActionDomVerb, DomActionType::XHR);
    549     }
    550   }
    551 
    552   if (uma_policy_)
    553     uma_policy_->ProcessAction(action);
    554   if (IsDatabaseEnabled() && database_policy_)
    555     database_policy_->ProcessAction(action);
    556   if (IsWatchdogAppActive())
    557     observers_->Notify(&Observer::OnExtensionActivity, action);
    558   if (testing_mode_)
    559     VLOG(1) << action->PrintForDebug();
    560 }
    561 
    562 void ActivityLog::OnScriptsExecuted(
    563     const content::WebContents* web_contents,
    564     const ExecutingScriptsMap& extension_ids,
    565     const GURL& on_url) {
    566   Profile* profile =
    567       Profile::FromBrowserContext(web_contents->GetBrowserContext());
    568   ExtensionRegistry* registry = ExtensionRegistry::Get(profile);
    569   for (ExecutingScriptsMap::const_iterator it = extension_ids.begin();
    570        it != extension_ids.end(); ++it) {
    571     const Extension* extension =
    572         registry->GetExtensionById(it->first, ExtensionRegistry::ENABLED);
    573     if (!extension || ActivityLogAPI::IsExtensionWhitelisted(extension->id()))
    574       continue;
    575 
    576     // If OnScriptsExecuted is fired because of tabs.executeScript, the list
    577     // of content scripts will be empty.  We don't want to log it because
    578     // the call to tabs.executeScript will have already been logged anyway.
    579     if (!it->second.empty()) {
    580       scoped_refptr<Action> action;
    581       action = new Action(extension->id(),
    582                           base::Time::Now(),
    583                           Action::ACTION_CONTENT_SCRIPT,
    584                           "");  // no API call here
    585       action->set_page_url(on_url);
    586       action->set_page_title(base::UTF16ToUTF8(web_contents->GetTitle()));
    587       action->set_page_incognito(
    588           web_contents->GetBrowserContext()->IsOffTheRecord());
    589 
    590       const prerender::PrerenderManager* prerender_manager =
    591           prerender::PrerenderManagerFactory::GetForProfile(profile);
    592       if (prerender_manager &&
    593           prerender_manager->IsWebContentsPrerendering(web_contents, NULL))
    594         action->mutable_other()->SetBoolean(constants::kActionPrerender, true);
    595       for (std::set<std::string>::const_iterator it2 = it->second.begin();
    596            it2 != it->second.end();
    597            ++it2) {
    598         action->mutable_args()->AppendString(*it2);
    599       }
    600       LogAction(action);
    601     }
    602   }
    603 }
    604 
    605 void ActivityLog::OnApiEventDispatched(const std::string& extension_id,
    606                                        const std::string& event_name,
    607                                        scoped_ptr<base::ListValue> event_args) {
    608   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    609   scoped_refptr<Action> action = new Action(extension_id,
    610                                             base::Time::Now(),
    611                                             Action::ACTION_API_EVENT,
    612                                             event_name);
    613   action->set_args(event_args.Pass());
    614   LogAction(action);
    615 }
    616 
    617 void ActivityLog::OnApiFunctionCalled(const std::string& extension_id,
    618                                       const std::string& api_name,
    619                                       scoped_ptr<base::ListValue> args) {
    620   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    621   scoped_refptr<Action> action = new Action(extension_id,
    622                                             base::Time::Now(),
    623                                             Action::ACTION_API_CALL,
    624                                             api_name);
    625   action->set_args(args.Pass());
    626   LogAction(action);
    627 }
    628 
    629 // LOOKUP ACTIONS. -------------------------------------------------------------
    630 
    631 void ActivityLog::GetFilteredActions(
    632     const std::string& extension_id,
    633     const Action::ActionType type,
    634     const std::string& api_name,
    635     const std::string& page_url,
    636     const std::string& arg_url,
    637     const int daysAgo,
    638     const base::Callback
    639         <void(scoped_ptr<std::vector<scoped_refptr<Action> > >)>& callback) {
    640   if (database_policy_) {
    641     database_policy_->ReadFilteredData(
    642         extension_id, type, api_name, page_url, arg_url, daysAgo, callback);
    643   }
    644 }
    645 
    646 // DELETE ACTIONS. -------------------------------------------------------------
    647 
    648 void ActivityLog::RemoveActions(const std::vector<int64>& action_ids) {
    649   if (!database_policy_)
    650     return;
    651   database_policy_->RemoveActions(action_ids);
    652 }
    653 
    654 void ActivityLog::RemoveURLs(const std::vector<GURL>& restrict_urls) {
    655   if (!database_policy_)
    656     return;
    657   database_policy_->RemoveURLs(restrict_urls);
    658 }
    659 
    660 void ActivityLog::RemoveURLs(const std::set<GURL>& restrict_urls) {
    661   if (!database_policy_)
    662     return;
    663 
    664   std::vector<GURL> urls;
    665   for (std::set<GURL>::const_iterator it = restrict_urls.begin();
    666        it != restrict_urls.end(); ++it) {
    667     urls.push_back(*it);
    668   }
    669   database_policy_->RemoveURLs(urls);
    670 }
    671 
    672 void ActivityLog::RemoveURL(const GURL& url) {
    673   if (url.is_empty())
    674     return;
    675   std::vector<GURL> urls;
    676   urls.push_back(url);
    677   RemoveURLs(urls);
    678 }
    679 
    680 void ActivityLog::DeleteDatabase() {
    681   if (!database_policy_)
    682     return;
    683   database_policy_->DeleteDatabase();
    684 }
    685 
    686 template <>
    687 void BrowserContextKeyedAPIFactory<ActivityLog>::DeclareFactoryDependencies() {
    688   DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
    689   DependsOn(ExtensionRegistryFactory::GetInstance());
    690 }
    691 
    692 }  // namespace extensions
    693