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