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