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