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: 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. 166 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: 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 } 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. 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. 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. 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 344 BrowserContextKeyedAPIFactory<ActivityLog>* ActivityLog::GetFactoryInstance() { 345 return g_factory.Pointer(); 346 } 347 348 // static 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. 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 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 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 449 void ActivityLog::StartObserving() { 450 extension_registry_observer_.Add(ExtensionRegistry::Get(profile_)); 451 } 452 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 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 468 bool ActivityLog::IsWatchdogAppActive() { 469 return (watchdog_apps_active_ > 0); 470 } 471 472 void ActivityLog::SetWatchdogAppActiveForTesting(bool active) { 473 watchdog_apps_active_ = active ? 1 : 0; 474 } 475 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 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. 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 516 void ActivityLog::AddObserver(ActivityLog::Observer* observer) { 517 observers_->AddObserver(observer); 518 } 519 520 void ActivityLog::RemoveObserver(ActivityLog::Observer* observer) { 521 observers_->RemoveObserver(observer); 522 } 523 524 // static 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 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 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 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 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 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 656 void ActivityLog::RemoveActions(const std::vector<int64>& action_ids) { 657 if (!database_policy_) 658 return; 659 database_policy_->RemoveActions(action_ids); 660 } 661 662 void ActivityLog::RemoveURLs(const std::vector<GURL>& restrict_urls) { 663 if (!database_policy_) 664 return; 665 database_policy_->RemoveURLs(restrict_urls); 666 } 667 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 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 688 void ActivityLog::DeleteDatabase() { 689 if (!database_policy_) 690 return; 691 database_policy_->DeleteDatabase(); 692 } 693 694 template <> 695 void BrowserContextKeyedAPIFactory<ActivityLog>::DeclareFactoryDependencies() { 696 DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory()); 697 DependsOn(ExtensionRegistryFactory::GetInstance()); 698 } 699 700 } // namespace extensions 701