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 // 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 400 void 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 432 ActivityLog::~ActivityLog() { 433 if (uma_policy_) 434 uma_policy_->Close(); 435 if (database_policy_) 436 database_policy_->Close(); 437 } 438 439 // MAINTAIN STATUS. ------------------------------------------------------------ 440 441 void ActivityLog::StartObserving() { 442 extension_registry_observer_.Add(ExtensionRegistry::Get(profile_)); 443 } 444 445 void 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 454 bool 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 460 bool ActivityLog::IsWatchdogAppActive() { 461 return (watchdog_apps_active_ > 0); 462 } 463 464 void ActivityLog::SetWatchdogAppActiveForTesting(bool active) { 465 watchdog_apps_active_ = active ? 1 : 0; 466 } 467 468 void 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 480 void 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. 495 void 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 509 void ActivityLog::AddObserver(ActivityLog::Observer* observer) { 510 observers_->AddObserver(observer); 511 } 512 513 void ActivityLog::RemoveObserver(ActivityLog::Observer* observer) { 514 observers_->RemoveObserver(observer); 515 } 516 517 // static 518 void 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 532 void 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 562 void 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 605 void 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 617 void 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 631 void 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 648 void ActivityLog::RemoveActions(const std::vector<int64>& action_ids) { 649 if (!database_policy_) 650 return; 651 database_policy_->RemoveActions(action_ids); 652 } 653 654 void ActivityLog::RemoveURLs(const std::vector<GURL>& restrict_urls) { 655 if (!database_policy_) 656 return; 657 database_policy_->RemoveURLs(restrict_urls); 658 } 659 660 void 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 672 void 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 680 void ActivityLog::DeleteDatabase() { 681 if (!database_policy_) 682 return; 683 database_policy_->DeleteDatabase(); 684 } 685 686 template <> 687 void BrowserContextKeyedAPIFactory<ActivityLog>::DeclareFactoryDependencies() { 688 DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory()); 689 DependsOn(ExtensionRegistryFactory::GetInstance()); 690 } 691 692 } // namespace extensions 693