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
43namespace constants = activity_log_constants;
44
45namespace extensions {
46
47namespace {
48
49using constants::kArgUrlPlaceholder;
50using content::BrowserThread;
51
52// If DOM API methods start with this string, we flag them as being of type
53// DomActionType::XHR.
54const char kDomXhrPrefix[] = "XMLHttpRequest.";
55
56// Specifies a possible action to take to get an extracted URL in the ApiInfo
57// structure below.
58enum 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.
66struct 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
92static 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.
155class 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.
195bool 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.
221bool 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.
241void 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
338static base::LazyInstance<BrowserContextKeyedAPIFactory<ActivityLog> >
339    g_factory = LAZY_INSTANCE_INITIALIZER;
340
341BrowserContextKeyedAPIFactory<ActivityLog>* ActivityLog::GetFactoryInstance() {
342  return g_factory.Pointer();
343}
344
345// static
346ActivityLog* 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.
352ActivityLog::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  // 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::StartObserving, base::Unretained(this)));
393
394  if (!profile_->IsOffTheRecord())
395    uma_policy_ = new UmaPolicy(profile_);
396
397  ChooseDatabasePolicy();
398}
399
400void ActivityLog::SetDatabasePolicy(
401    ActivityLogPolicy::PolicyType policy_type) {
402  if (database_policy_type_ == policy_type)
403    return;
404  if (!IsDatabaseEnabled() && !IsWatchdogAppActive())
405    return;
406
407  // Deleting the old policy takes place asynchronously, on the database
408  // thread.  Initializing a new policy below similarly happens
409  // asynchronously.  Since the two operations are both queued for the
410  // database, the queue ordering should ensure that the deletion completes
411  // before database initialization occurs.
412  //
413  // However, changing policies at runtime is still not recommended, and
414  // likely only should be done for unit tests.
415  if (database_policy_)
416    database_policy_->Close();
417
418  switch (policy_type) {
419    case ActivityLogPolicy::POLICY_FULLSTREAM:
420      database_policy_ = new FullStreamUIPolicy(profile_);
421      break;
422    case ActivityLogPolicy::POLICY_COUNTS:
423      database_policy_ = new CountingPolicy(profile_);
424      break;
425    default:
426      NOTREACHED();
427  }
428  database_policy_->Init();
429  database_policy_type_ = policy_type;
430}
431
432ActivityLog::~ActivityLog() {
433  if (uma_policy_)
434    uma_policy_->Close();
435  if (database_policy_)
436    database_policy_->Close();
437}
438
439// MAINTAIN STATUS. ------------------------------------------------------------
440
441void ActivityLog::StartObserving() {
442  extension_registry_observer_.Add(ExtensionRegistry::Get(profile_));
443}
444
445void ActivityLog::ChooseDatabasePolicy() {
446  if (!(IsDatabaseEnabled() || IsWatchdogAppActive()))
447    return;
448  if (testing_mode_)
449    SetDatabasePolicy(ActivityLogPolicy::POLICY_FULLSTREAM);
450  else
451    SetDatabasePolicy(ActivityLogPolicy::POLICY_COUNTS);
452}
453
454bool ActivityLog::IsDatabaseEnabled() {
455  // Make sure we are not enabled when there are no threads.
456  DCHECK(has_threads_ || !db_enabled_);
457  return db_enabled_;
458}
459
460bool ActivityLog::IsWatchdogAppActive() {
461  return (watchdog_apps_active_ > 0);
462}
463
464void ActivityLog::SetWatchdogAppActiveForTesting(bool active) {
465  watchdog_apps_active_ = active ? 1 : 0;
466}
467
468void ActivityLog::OnExtensionLoaded(content::BrowserContext* browser_context,
469                                    const Extension* extension) {
470  if (!ActivityLogAPI::IsExtensionWhitelisted(extension->id())) return;
471  if (has_threads_)
472    db_enabled_ = true;
473  watchdog_apps_active_++;
474  profile_->GetPrefs()->SetInteger(prefs::kWatchdogExtensionActive,
475                                   watchdog_apps_active_);
476  if (watchdog_apps_active_ == 1)
477    ChooseDatabasePolicy();
478}
479
480void ActivityLog::OnExtensionUnloaded(content::BrowserContext* browser_context,
481                                      const Extension* extension,
482                                      UnloadedExtensionInfo::Reason reason) {
483  if (!ActivityLogAPI::IsExtensionWhitelisted(extension->id())) return;
484  watchdog_apps_active_--;
485  profile_->GetPrefs()->SetInteger(prefs::kWatchdogExtensionActive,
486                                   watchdog_apps_active_);
487  if (watchdog_apps_active_ == 0 &&
488      !CommandLine::ForCurrentProcess()->HasSwitch(
489          switches::kEnableExtensionActivityLogging)) {
490    db_enabled_ = false;
491  }
492}
493
494// OnExtensionUnloaded will also be called right before this.
495void ActivityLog::OnExtensionUninstalled(
496    content::BrowserContext* browser_context,
497    const Extension* extension,
498    extensions::UninstallReason reason) {
499  if (ActivityLogAPI::IsExtensionWhitelisted(extension->id()) &&
500      !CommandLine::ForCurrentProcess()->HasSwitch(
501          switches::kEnableExtensionActivityLogging) &&
502      watchdog_apps_active_ == 0) {
503    DeleteDatabase();
504  } else if (database_policy_) {
505    database_policy_->RemoveExtensionData(extension->id());
506  }
507}
508
509void ActivityLog::AddObserver(ActivityLog::Observer* observer) {
510  observers_->AddObserver(observer);
511}
512
513void ActivityLog::RemoveObserver(ActivityLog::Observer* observer) {
514  observers_->RemoveObserver(observer);
515}
516
517// static
518void ActivityLog::RegisterProfilePrefs(
519    user_prefs::PrefRegistrySyncable* registry) {
520  registry->RegisterIntegerPref(
521      prefs::kWatchdogExtensionActive,
522      false,
523      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
524  registry->RegisterBooleanPref(
525      prefs::kWatchdogExtensionActiveOld,
526      false,
527      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
528}
529
530// LOG ACTIONS. ----------------------------------------------------------------
531
532void ActivityLog::LogAction(scoped_refptr<Action> action) {
533  if (ActivityLogAPI::IsExtensionWhitelisted(action->extension_id()))
534    return;
535
536  // Perform some preprocessing of the Action data: convert tab IDs to URLs and
537  // mask out incognito URLs if appropriate.
538  ExtractUrls(action, profile_);
539
540  // Mark DOM XHR requests as such, for easier processing later.
541  if (action->action_type() == Action::ACTION_DOM_ACCESS &&
542      StartsWithASCII(action->api_name(), kDomXhrPrefix, true) &&
543      action->other()) {
544    base::DictionaryValue* other = action->mutable_other();
545    int dom_verb = -1;
546    if (other->GetInteger(constants::kActionDomVerb, &dom_verb) &&
547        dom_verb == DomActionType::METHOD) {
548      other->SetInteger(constants::kActionDomVerb, DomActionType::XHR);
549    }
550  }
551
552  if (uma_policy_)
553    uma_policy_->ProcessAction(action);
554  if (IsDatabaseEnabled() && database_policy_)
555    database_policy_->ProcessAction(action);
556  if (IsWatchdogAppActive())
557    observers_->Notify(&Observer::OnExtensionActivity, action);
558  if (testing_mode_)
559    VLOG(1) << action->PrintForDebug();
560}
561
562void ActivityLog::OnScriptsExecuted(
563    const content::WebContents* web_contents,
564    const ExecutingScriptsMap& extension_ids,
565    const GURL& on_url) {
566  Profile* profile =
567      Profile::FromBrowserContext(web_contents->GetBrowserContext());
568  ExtensionRegistry* registry = ExtensionRegistry::Get(profile);
569  for (ExecutingScriptsMap::const_iterator it = extension_ids.begin();
570       it != extension_ids.end(); ++it) {
571    const Extension* extension =
572        registry->GetExtensionById(it->first, ExtensionRegistry::ENABLED);
573    if (!extension || ActivityLogAPI::IsExtensionWhitelisted(extension->id()))
574      continue;
575
576    // If OnScriptsExecuted is fired because of tabs.executeScript, the list
577    // of content scripts will be empty.  We don't want to log it because
578    // the call to tabs.executeScript will have already been logged anyway.
579    if (!it->second.empty()) {
580      scoped_refptr<Action> action;
581      action = new Action(extension->id(),
582                          base::Time::Now(),
583                          Action::ACTION_CONTENT_SCRIPT,
584                          "");  // no API call here
585      action->set_page_url(on_url);
586      action->set_page_title(base::UTF16ToUTF8(web_contents->GetTitle()));
587      action->set_page_incognito(
588          web_contents->GetBrowserContext()->IsOffTheRecord());
589
590      const prerender::PrerenderManager* prerender_manager =
591          prerender::PrerenderManagerFactory::GetForProfile(profile);
592      if (prerender_manager &&
593          prerender_manager->IsWebContentsPrerendering(web_contents, NULL))
594        action->mutable_other()->SetBoolean(constants::kActionPrerender, true);
595      for (std::set<std::string>::const_iterator it2 = it->second.begin();
596           it2 != it->second.end();
597           ++it2) {
598        action->mutable_args()->AppendString(*it2);
599      }
600      LogAction(action);
601    }
602  }
603}
604
605void ActivityLog::OnApiEventDispatched(const std::string& extension_id,
606                                       const std::string& event_name,
607                                       scoped_ptr<base::ListValue> event_args) {
608  DCHECK_CURRENTLY_ON(BrowserThread::UI);
609  scoped_refptr<Action> action = new Action(extension_id,
610                                            base::Time::Now(),
611                                            Action::ACTION_API_EVENT,
612                                            event_name);
613  action->set_args(event_args.Pass());
614  LogAction(action);
615}
616
617void ActivityLog::OnApiFunctionCalled(const std::string& extension_id,
618                                      const std::string& api_name,
619                                      scoped_ptr<base::ListValue> args) {
620  DCHECK_CURRENTLY_ON(BrowserThread::UI);
621  scoped_refptr<Action> action = new Action(extension_id,
622                                            base::Time::Now(),
623                                            Action::ACTION_API_CALL,
624                                            api_name);
625  action->set_args(args.Pass());
626  LogAction(action);
627}
628
629// LOOKUP ACTIONS. -------------------------------------------------------------
630
631void 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
648void ActivityLog::RemoveActions(const std::vector<int64>& action_ids) {
649  if (!database_policy_)
650    return;
651  database_policy_->RemoveActions(action_ids);
652}
653
654void ActivityLog::RemoveURLs(const std::vector<GURL>& restrict_urls) {
655  if (!database_policy_)
656    return;
657  database_policy_->RemoveURLs(restrict_urls);
658}
659
660void ActivityLog::RemoveURLs(const std::set<GURL>& restrict_urls) {
661  if (!database_policy_)
662    return;
663
664  std::vector<GURL> urls;
665  for (std::set<GURL>::const_iterator it = restrict_urls.begin();
666       it != restrict_urls.end(); ++it) {
667    urls.push_back(*it);
668  }
669  database_policy_->RemoveURLs(urls);
670}
671
672void ActivityLog::RemoveURL(const GURL& url) {
673  if (url.is_empty())
674    return;
675  std::vector<GURL> urls;
676  urls.push_back(url);
677  RemoveURLs(urls);
678}
679
680void ActivityLog::DeleteDatabase() {
681  if (!database_policy_)
682    return;
683  database_policy_->DeleteDatabase();
684}
685
686template <>
687void BrowserContextKeyedAPIFactory<ActivityLog>::DeclareFactoryDependencies() {
688  DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
689  DependsOn(ExtensionRegistryFactory::GetInstance());
690}
691
692}  // namespace extensions
693