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