a9a598f23bf7a8ed31b06fddc8dae2118db24c34
[platform/framework/web/crosswalk.git] / src / chrome / browser / extensions / activity_log / activity_log.cc
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 base::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       base::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(
281               url_index, new base::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(
297               extracted_index, new base::StringValue(kArgUrlPlaceholder));
298         }
299       }
300       break;
301     }
302
303     default:
304       NOTREACHED();
305   }
306
307   if (arg_url.is_valid()) {
308     action->set_arg_incognito(arg_incognito);
309     action->set_arg_url(arg_url);
310   }
311 }
312
313 }  // namespace
314
315 namespace extensions {
316
317 // ActivityLogFactory
318
319 ActivityLogFactory* ActivityLogFactory::GetInstance() {
320   return Singleton<ActivityLogFactory>::get();
321 }
322
323 BrowserContextKeyedService* ActivityLogFactory::BuildServiceInstanceFor(
324     content::BrowserContext* profile) const {
325   return new ActivityLog(static_cast<Profile*>(profile));
326 }
327
328 content::BrowserContext* ActivityLogFactory::GetBrowserContextToUse(
329     content::BrowserContext* context) const {
330   return chrome::GetBrowserContextRedirectedInIncognito(context);
331 }
332
333 ActivityLogFactory::ActivityLogFactory()
334     : BrowserContextKeyedServiceFactory(
335         "ActivityLog",
336         BrowserContextDependencyManager::GetInstance()) {
337   DependsOn(ExtensionSystemFactory::GetInstance());
338   DependsOn(InstallTrackerFactory::GetInstance());
339 }
340
341 // static
342 ActivityLog* ActivityLog::GetInstance(content::BrowserContext* context) {
343   return ActivityLogFactory::GetForBrowserContext(context);
344 }
345
346 ActivityLogFactory::~ActivityLogFactory() {
347 }
348
349 // ActivityLog
350
351 // SET THINGS UP. --------------------------------------------------------------
352
353 // Use GetInstance instead of directly creating an ActivityLog.
354 ActivityLog::ActivityLog(Profile* profile)
355     : database_policy_(NULL),
356       database_policy_type_(ActivityLogPolicy::POLICY_INVALID),
357       uma_policy_(NULL),
358       profile_(profile),
359       db_enabled_(false),
360       testing_mode_(false),
361       has_threads_(true),
362       tracker_(NULL),
363       watchdog_apps_active_(0) {
364   // This controls whether logging statements are printed & which policy is set.
365   testing_mode_ = CommandLine::ForCurrentProcess()->HasSwitch(
366     switches::kEnableExtensionActivityLogTesting);
367
368   // Check if the watchdog extension is previously installed and active.
369   // It was originally a boolean, but we've had to move to an integer. Handle
370   // the legacy case.
371   // TODO(felt): In M34, remove the legacy code & old pref.
372   if (profile_->GetPrefs()->GetBoolean(prefs::kWatchdogExtensionActiveOld))
373     profile_->GetPrefs()->SetInteger(prefs::kWatchdogExtensionActive, 1);
374   watchdog_apps_active_ =
375       profile_->GetPrefs()->GetInteger(prefs::kWatchdogExtensionActive);
376
377   observers_ = new ObserverListThreadSafe<Observer>;
378
379   // Check that the right threads exist for logging to the database.
380   // If not, we shouldn't try to do things that require them.
381   if (!BrowserThread::IsMessageLoopValid(BrowserThread::DB) ||
382       !BrowserThread::IsMessageLoopValid(BrowserThread::FILE) ||
383       !BrowserThread::IsMessageLoopValid(BrowserThread::IO)) {
384     has_threads_ = false;
385   }
386
387   db_enabled_ = has_threads_
388       && (CommandLine::ForCurrentProcess()->
389           HasSwitch(switches::kEnableExtensionActivityLogging)
390       || watchdog_apps_active_);
391
392   ExtensionSystem::Get(profile_)->ready().Post(
393       FROM_HERE,
394       base::Bind(&ActivityLog::InitInstallTracker, base::Unretained(this)));
395
396   EventRouter* event_router = ExtensionSystem::Get(profile_)->event_router();
397   if (event_router)
398     event_router->SetEventDispatchObserver(this);
399
400 // None of this should run on Android since the AL is behind ENABLE_EXTENSION
401 // checks. However, UmaPolicy can't even compile on Android because it uses
402 // BrowserList and related classes that aren't compiled for Android.
403 #if !defined(OS_ANDROID)
404   if (!profile->IsOffTheRecord())
405     uma_policy_ = new UmaPolicy(profile_);
406 #endif
407
408   ChooseDatabasePolicy();
409 }
410
411 void ActivityLog::SetDatabasePolicy(
412     ActivityLogPolicy::PolicyType policy_type) {
413   if (database_policy_type_ == policy_type)
414     return;
415   if (!IsDatabaseEnabled() && !IsWatchdogAppActive())
416     return;
417
418   // Deleting the old policy takes place asynchronously, on the database
419   // thread.  Initializing a new policy below similarly happens
420   // asynchronously.  Since the two operations are both queued for the
421   // database, the queue ordering should ensure that the deletion completes
422   // before database initialization occurs.
423   //
424   // However, changing policies at runtime is still not recommended, and
425   // likely only should be done for unit tests.
426   if (database_policy_)
427     database_policy_->Close();
428
429   switch (policy_type) {
430     case ActivityLogPolicy::POLICY_FULLSTREAM:
431       database_policy_ = new FullStreamUIPolicy(profile_);
432       break;
433     case ActivityLogPolicy::POLICY_COUNTS:
434       database_policy_ = new CountingPolicy(profile_);
435       break;
436     default:
437       NOTREACHED();
438   }
439   database_policy_->Init();
440   database_policy_type_ = policy_type;
441 }
442
443 // SHUT DOWN. ------------------------------------------------------------------
444
445 void ActivityLog::Shutdown() {
446   if (tracker_) tracker_->RemoveObserver(this);
447 }
448
449 ActivityLog::~ActivityLog() {
450   if (uma_policy_)
451     uma_policy_->Close();
452   if (database_policy_)
453     database_policy_->Close();
454 }
455
456 // MAINTAIN STATUS. ------------------------------------------------------------
457
458 void ActivityLog::InitInstallTracker() {
459   tracker_ = InstallTrackerFactory::GetForProfile(profile_);
460   tracker_->AddObserver(this);
461 }
462
463 void ActivityLog::ChooseDatabasePolicy() {
464   if (!(IsDatabaseEnabled() || IsWatchdogAppActive()))
465     return;
466   if (testing_mode_)
467     SetDatabasePolicy(ActivityLogPolicy::POLICY_FULLSTREAM);
468   else
469     SetDatabasePolicy(ActivityLogPolicy::POLICY_COUNTS);
470 }
471
472 bool ActivityLog::IsDatabaseEnabled() {
473   // Make sure we are not enabled when there are no threads.
474   DCHECK(has_threads_ || !db_enabled_);
475   return db_enabled_;
476 }
477
478 bool ActivityLog::IsWatchdogAppActive() {
479   return (watchdog_apps_active_ > 0);
480 }
481
482 void ActivityLog::SetWatchdogAppActive(bool active) {
483   watchdog_apps_active_ = active ? 1 : 0;
484 }
485
486 void ActivityLog::OnExtensionLoaded(const Extension* extension) {
487   if (!ActivityLogAPI::IsExtensionWhitelisted(extension->id())) return;
488   if (has_threads_)
489     db_enabled_ = true;
490   watchdog_apps_active_++;
491   profile_->GetPrefs()->SetInteger(prefs::kWatchdogExtensionActive,
492                                    watchdog_apps_active_);
493   if (watchdog_apps_active_ == 1)
494     ChooseDatabasePolicy();
495 }
496
497 void ActivityLog::OnExtensionUnloaded(const Extension* extension) {
498   if (!ActivityLogAPI::IsExtensionWhitelisted(extension->id())) return;
499   watchdog_apps_active_--;
500   profile_->GetPrefs()->SetInteger(prefs::kWatchdogExtensionActive,
501                                    watchdog_apps_active_);
502   if (watchdog_apps_active_ == 0 &&
503       !CommandLine::ForCurrentProcess()->HasSwitch(
504           switches::kEnableExtensionActivityLogging)) {
505    db_enabled_ = false;
506   }
507 }
508
509 // OnExtensionUnloaded will also be called right before this.
510 void ActivityLog::OnExtensionUninstalled(const Extension* extension) {
511   if (ActivityLogAPI::IsExtensionWhitelisted(extension->id()) &&
512       !CommandLine::ForCurrentProcess()->HasSwitch(
513           switches::kEnableExtensionActivityLogging) &&
514       watchdog_apps_active_ == 0) {
515     DeleteDatabase();
516   } else if (database_policy_) {
517     database_policy_->RemoveExtensionData(extension->id());
518   }
519 }
520
521 void ActivityLog::AddObserver(ActivityLog::Observer* observer) {
522   observers_->AddObserver(observer);
523 }
524
525 void ActivityLog::RemoveObserver(ActivityLog::Observer* observer) {
526   observers_->RemoveObserver(observer);
527 }
528
529 // static
530 void ActivityLog::RegisterProfilePrefs(
531     user_prefs::PrefRegistrySyncable* registry) {
532   registry->RegisterIntegerPref(
533       prefs::kWatchdogExtensionActive,
534       false,
535       user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
536   registry->RegisterBooleanPref(
537       prefs::kWatchdogExtensionActiveOld,
538       false,
539       user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
540 }
541
542 // LOG ACTIONS. ----------------------------------------------------------------
543
544 void ActivityLog::LogAction(scoped_refptr<Action> action) {
545   if (ActivityLogAPI::IsExtensionWhitelisted(action->extension_id()))
546     return;
547
548   // Perform some preprocessing of the Action data: convert tab IDs to URLs and
549   // mask out incognito URLs if appropriate.
550   ExtractUrls(action, profile_);
551
552   // Mark DOM XHR requests as such, for easier processing later.
553   if (action->action_type() == Action::ACTION_DOM_ACCESS &&
554       StartsWithASCII(action->api_name(), kDomXhrPrefix, true) &&
555       action->other()) {
556     base::DictionaryValue* other = action->mutable_other();
557     int dom_verb = -1;
558     if (other->GetInteger(constants::kActionDomVerb, &dom_verb) &&
559         dom_verb == DomActionType::METHOD) {
560       other->SetInteger(constants::kActionDomVerb, DomActionType::XHR);
561     }
562   }
563
564   if (uma_policy_)
565     uma_policy_->ProcessAction(action);
566   if (IsDatabaseEnabled() && database_policy_)
567     database_policy_->ProcessAction(action);
568   if (IsWatchdogAppActive())
569     observers_->Notify(&Observer::OnExtensionActivity, action);
570   if (testing_mode_)
571     VLOG(1) << action->PrintForDebug();
572 }
573
574 void ActivityLog::OnScriptsExecuted(
575     const content::WebContents* web_contents,
576     const ExecutingScriptsMap& extension_ids,
577     int32 on_page_id,
578     const GURL& on_url) {
579   Profile* profile =
580       Profile::FromBrowserContext(web_contents->GetBrowserContext());
581   const ExtensionService* extension_service =
582       ExtensionSystem::Get(profile)->extension_service();
583   const ExtensionSet* extensions = extension_service->extensions();
584   const prerender::PrerenderManager* prerender_manager =
585       prerender::PrerenderManagerFactory::GetForProfile(
586           Profile::FromBrowserContext(web_contents->GetBrowserContext()));
587
588   for (ExecutingScriptsMap::const_iterator it = extension_ids.begin();
589        it != extension_ids.end(); ++it) {
590     const Extension* extension = extensions->GetByID(it->first);
591     if (!extension || ActivityLogAPI::IsExtensionWhitelisted(extension->id()))
592       continue;
593
594     // If OnScriptsExecuted is fired because of tabs.executeScript, the list
595     // of content scripts will be empty.  We don't want to log it because
596     // the call to tabs.executeScript will have already been logged anyway.
597     if (!it->second.empty()) {
598       scoped_refptr<Action> action;
599       action = new Action(extension->id(),
600                           base::Time::Now(),
601                           Action::ACTION_CONTENT_SCRIPT,
602                           "");  // no API call here
603       action->set_page_url(on_url);
604       action->set_page_title(base::UTF16ToUTF8(web_contents->GetTitle()));
605       action->set_page_incognito(
606           web_contents->GetBrowserContext()->IsOffTheRecord());
607       if (prerender_manager &&
608           prerender_manager->IsWebContentsPrerendering(web_contents, NULL))
609         action->mutable_other()->SetBoolean(constants::kActionPrerender, true);
610       for (std::set<std::string>::const_iterator it2 = it->second.begin();
611            it2 != it->second.end();
612            ++it2) {
613         action->mutable_args()->AppendString(*it2);
614       }
615       LogAction(action);
616     }
617   }
618 }
619
620 void ActivityLog::OnWillDispatchEvent(scoped_ptr<EventDispatchInfo> details) {
621   scoped_refptr<Action> action = new Action(details->extension_id,
622                                             base::Time::Now(),
623                                             Action::ACTION_API_EVENT,
624                                             details->event_name);
625   action->set_args(details->event_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::RemoveURLs(const std::vector<GURL>& restrict_urls) {
649   if (!database_policy_)
650     return;
651   database_policy_->RemoveURLs(restrict_urls);
652 }
653
654 void ActivityLog::RemoveURLs(const std::set<GURL>& restrict_urls) {
655   if (!database_policy_)
656     return;
657
658   std::vector<GURL> urls;
659   for (std::set<GURL>::const_iterator it = restrict_urls.begin();
660        it != restrict_urls.end(); ++it) {
661     urls.push_back(*it);
662   }
663   database_policy_->RemoveURLs(urls);
664 }
665
666 void ActivityLog::RemoveURL(const GURL& url) {
667   if (url.is_empty())
668     return;
669   std::vector<GURL> urls;
670   urls.push_back(url);
671   RemoveURLs(urls);
672 }
673
674 void ActivityLog::DeleteDatabase() {
675   if (!database_policy_)
676     return;
677   database_policy_->DeleteDatabase();
678 }
679
680 }  // namespace extensions