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