Index: chrome/browser/extensions/activity_log/activity_log.cc |
diff --git a/chrome/browser/extensions/activity_log/activity_log.cc b/chrome/browser/extensions/activity_log/activity_log.cc |
index 0a5e843193f8307187639ffab21ab78ac175881f..c7ac3ca62234cef2e5b77e7b79711270972b918d 100644 |
--- a/chrome/browser/extensions/activity_log/activity_log.cc |
+++ b/chrome/browser/extensions/activity_log/activity_log.cc |
@@ -16,7 +16,9 @@ |
#include "base/macros.h" |
#include "base/strings/string_util.h" |
#include "base/strings/utf_string_conversions.h" |
+#include "base/synchronization/lock.h" |
#include "base/threading/thread_checker.h" |
+#include "chrome/browser/chrome_notification_types.h" |
#include "chrome/browser/extensions/activity_log/activity_action_constants.h" |
#include "chrome/browser/extensions/activity_log/counting_policy.h" |
#include "chrome/browser/extensions/activity_log/fullstream_ui_policy.h" |
@@ -31,7 +33,10 @@ |
#include "chrome/common/pref_names.h" |
#include "components/syncable_prefs/pref_service_syncable.h" |
#include "content/public/browser/browser_thread.h" |
+#include "content/public/browser/notification_service.h" |
+#include "content/public/browser/notification_source.h" |
#include "content/public/browser/web_contents.h" |
+#include "extensions/browser/api_activity_monitor.h" |
#include "extensions/browser/extension_registry.h" |
#include "extensions/browser/extension_registry_factory.h" |
#include "extensions/browser/extension_system.h" |
@@ -333,6 +338,179 @@ void ExtractUrls(scoped_refptr<Action> action, Profile* profile) { |
} |
} |
+// A global, thread-safe record of activity log state. |
+class ActivityLogState { |
+ public: |
+ ActivityLogState() {} |
+ ~ActivityLogState() {} |
+ |
+ void AddActiveContext(content::BrowserContext* context) { |
+ DCHECK_CURRENTLY_ON(BrowserThread::UI); |
+ base::AutoLock lock(lock_); |
+ contexts_.insert(context); |
+ } |
+ |
+ void RemoveActiveContext(content::BrowserContext* context) { |
+ DCHECK_CURRENTLY_ON(BrowserThread::UI); |
+ base::AutoLock lock(lock_); |
+ contexts_.erase(context); |
+ } |
+ |
+ bool IsActiveContext(content::BrowserContext* context) { |
+ base::AutoLock lock(lock_); |
+ return contexts_.count(context) > 0; |
+ } |
+ |
+ void AddWhitelistedId(const std::string& id) { |
+ DCHECK_CURRENTLY_ON(BrowserThread::UI); |
+ base::AutoLock lock(lock_); |
+ whitelisted_ids_.insert(id); |
+ } |
+ |
+ // We don't remove the id entry from g_activity_log_state because it may be |
+ // loaded in multiple profiles, and being whitelisted for the ActivityLog |
+ // is a global permission. |
+ |
+ bool IsWhitelistedId(const std::string& id) { |
+ base::AutoLock lock(lock_); |
+ return whitelisted_ids_.count(id) > 0; |
+ } |
+ |
+ private: |
+ std::set<const content::BrowserContext*> contexts_; |
+ std::set<std::string> whitelisted_ids_; |
+ base::Lock lock_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(ActivityLogState); |
+}; |
+ |
+base::LazyInstance<ActivityLogState> g_activity_log_state = |
+ LAZY_INSTANCE_INITIALIZER; |
+ |
+// Calls into the ActivityLog to log an api event or function call. |
+// Must be called on the UI thread. |
+void LogApiActivityOnUI(content::BrowserContext* browser_context, |
+ const std::string& extension_id, |
+ const std::string& activity_name, |
+ std::unique_ptr<base::ListValue> args, |
+ Action::ActionType type) { |
+ DCHECK_CURRENTLY_ON(BrowserThread::UI); |
+ ActivityLog* activity_log = ActivityLog::GetInstance(browser_context); |
+ if (!activity_log || !activity_log->ShouldLog(extension_id)) |
+ return; |
+ scoped_refptr<Action> action = |
+ new Action(extension_id, base::Time::Now(), type, activity_name); |
+ action->set_args(std::move(args)); |
+ activity_log->LogAction(action); |
+} |
+ |
+// Generic thread-safe handler for API calls and events. |
+void LogApiActivity(content::BrowserContext* browser_context, |
+ const std::string& extension_id, |
+ const std::string& activity_name, |
+ const base::ListValue& args, |
+ Action::ActionType type) { |
+ ActivityLogState& state = g_activity_log_state.Get(); |
+ if (!state.IsActiveContext(browser_context) || |
+ state.IsWhitelistedId(extension_id)) |
+ return; |
+ if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { |
+ BrowserThread::PostTask( |
+ BrowserThread::UI, FROM_HERE, |
+ base::Bind(&LogApiActivityOnUI, browser_context, extension_id, |
+ activity_name, base::Passed(args.CreateDeepCopy()), type)); |
+ return; |
+ } |
+ LogApiActivityOnUI(browser_context, extension_id, activity_name, |
+ args.CreateDeepCopy(), type); |
+} |
+ |
+// Handler for API events. Thread-safe. |
+void LogApiEvent(content::BrowserContext* browser_context, |
+ const std::string& extension_id, |
+ const std::string& event_name, |
+ const base::ListValue& args) { |
+ LogApiActivity(browser_context, extension_id, event_name, args, |
+ Action::ACTION_API_EVENT); |
+} |
+ |
+// Handler for API function calls. Thread-safe. |
+void LogApiFunction(content::BrowserContext* browser_context, |
+ const std::string& extension_id, |
+ const std::string& event_name, |
+ const base::ListValue& args) { |
+ LogApiActivity(browser_context, extension_id, event_name, args, |
+ Action::ACTION_API_CALL); |
+} |
+ |
+// Calls into the ActivityLog to log a webRequest usage. |
+// Must be called on the UI thread. |
+void LogWebRequestActivityOnUI(content::BrowserContext* browser_context, |
+ const std::string& extension_id, |
+ const GURL& url, |
+ bool is_incognito, |
+ const std::string& api_call, |
+ std::unique_ptr<base::DictionaryValue> details) { |
+ DCHECK_CURRENTLY_ON(BrowserThread::UI); |
+ ActivityLog* activity_log = ActivityLog::GetInstance(browser_context); |
+ if (!activity_log || !activity_log->ShouldLog(extension_id)) |
+ return; |
+ scoped_refptr<Action> action = new Action( |
+ extension_id, base::Time::Now(), Action::ACTION_WEB_REQUEST, api_call); |
+ action->set_page_url(url); |
+ action->set_page_incognito(is_incognito); |
+ action->mutable_other()->Set(activity_log_constants::kActionWebRequest, |
+ std::move(details)); |
+ activity_log->LogAction(action); |
+} |
+ |
+// Handler for webRequest use. Thread-safe. |
+void LogWebRequestActivity(content::BrowserContext* browser_context, |
+ const std::string& extension_id, |
+ const GURL& url, |
+ bool is_incognito, |
+ const std::string& api_call, |
+ std::unique_ptr<base::DictionaryValue> details) { |
+ ActivityLogState& state = g_activity_log_state.Get(); |
+ if (!state.IsActiveContext(browser_context) || |
+ state.IsWhitelistedId(extension_id)) |
+ return; |
+ if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { |
+ BrowserThread::PostTask( |
+ BrowserThread::UI, FROM_HERE, |
+ base::Bind(&LogWebRequestActivityOnUI, browser_context, extension_id, |
+ url, is_incognito, api_call, base::Passed(&details))); |
+ return; |
+ } |
+ LogWebRequestActivityOnUI(browser_context, extension_id, url, is_incognito, |
+ api_call, std::move(details)); |
+} |
+ |
+void SetActivityHandlers() { |
+ // Set up event handlers. We don't have to worry about unsetting these, |
+ // because we check whether or not the activity log is active for the context |
+ // in the monitor methods. |
+ activity_monitor::Monitor current_function_monitor = |
+ activity_monitor::GetApiFunctionMonitor(); |
+ DCHECK(!current_function_monitor || |
+ current_function_monitor == &LogApiFunction); |
+ if (!current_function_monitor) |
+ activity_monitor::SetApiFunctionMonitor(&LogApiFunction); |
+ |
+ activity_monitor::Monitor current_event_monitor = |
+ activity_monitor::GetApiEventMonitor(); |
+ DCHECK(!current_event_monitor || current_event_monitor == &LogApiEvent); |
+ if (!current_function_monitor) |
+ activity_monitor::SetApiEventMonitor(&LogApiEvent); |
+ |
+ activity_monitor::WebRequestMonitor current_web_request_monitor = |
+ activity_monitor::GetWebRequestMonitor(); |
+ DCHECK(!current_web_request_monitor || |
+ current_web_request_monitor == &LogWebRequestActivity); |
+ if (!current_web_request_monitor) |
+ activity_monitor::SetWebRequestMonitor(&LogWebRequestActivity); |
+} |
+ |
} // namespace |
// SET THINGS UP. -------------------------------------------------------------- |
@@ -359,7 +537,10 @@ ActivityLog::ActivityLog(content::BrowserContext* context) |
testing_mode_(false), |
has_threads_(true), |
extension_registry_observer_(this), |
- watchdog_apps_active_(0) { |
+ watchdog_apps_active_(0), |
+ is_active_(false) { |
+ SetActivityHandlers(); |
+ |
// This controls whether logging statements are printed & which policy is set. |
testing_mode_ = base::CommandLine::ForCurrentProcess()->HasSwitch( |
switches::kEnableExtensionActivityLogTesting); |
@@ -385,6 +566,8 @@ ActivityLog::ActivityLog(content::BrowserContext* context) |
extension_registry_observer_.Add(ExtensionRegistry::Get(profile_)); |
ChooseDatabasePolicy(); |
+ |
+ CheckActive(); |
} |
void ActivityLog::SetDatabasePolicy( |
@@ -422,6 +605,8 @@ void ActivityLog::SetDatabasePolicy( |
ActivityLog::~ActivityLog() { |
if (database_policy_) |
database_policy_->Close(); |
+ if (is_active_) |
+ g_activity_log_state.Get().RemoveActiveContext(profile_); |
} |
// MAINTAIN STATUS. ------------------------------------------------------------ |
@@ -447,24 +632,31 @@ bool ActivityLog::IsWatchdogAppActive() { |
void ActivityLog::SetWatchdogAppActiveForTesting(bool active) { |
watchdog_apps_active_ = active ? 1 : 0; |
+ CheckActive(); |
} |
void ActivityLog::OnExtensionLoaded(content::BrowserContext* browser_context, |
const Extension* extension) { |
- if (!ActivityLogAPI::IsExtensionWhitelisted(extension->id())) return; |
+ if (!ActivityLogAPI::IsExtensionWhitelisted(extension->id())) |
+ return; |
if (has_threads_) |
db_enabled_ = true; |
+ g_activity_log_state.Get().AddWhitelistedId(extension->id()); |
watchdog_apps_active_++; |
profile_->GetPrefs()->SetInteger(prefs::kWatchdogExtensionActive, |
watchdog_apps_active_); |
if (watchdog_apps_active_ == 1) |
ChooseDatabasePolicy(); |
+ |
+ if (!is_active_) |
+ CheckActive(); |
} |
void ActivityLog::OnExtensionUnloaded(content::BrowserContext* browser_context, |
const Extension* extension, |
UnloadedExtensionInfo::Reason reason) { |
- if (!ActivityLogAPI::IsExtensionWhitelisted(extension->id())) return; |
+ if (!ActivityLogAPI::IsExtensionWhitelisted(extension->id())) |
+ return; |
watchdog_apps_active_--; |
profile_->GetPrefs()->SetInteger(prefs::kWatchdogExtensionActive, |
watchdog_apps_active_); |
@@ -473,6 +665,9 @@ void ActivityLog::OnExtensionUnloaded(content::BrowserContext* browser_context, |
switches::kEnableExtensionActivityLogging)) { |
db_enabled_ = false; |
} |
+ |
+ if (is_active_) |
+ CheckActive(); |
} |
// OnExtensionUnloaded will also be called right before this. |
@@ -507,8 +702,7 @@ void ActivityLog::RegisterProfilePrefs( |
// LOG ACTIONS. ---------------------------------------------------------------- |
void ActivityLog::LogAction(scoped_refptr<Action> action) { |
- if (ActivityLogAPI::IsExtensionWhitelisted(action->extension_id())) |
- return; |
+ DCHECK(ShouldLog(action->extension_id())); |
// Perform some preprocessing of the Action data: convert tab IDs to URLs and |
// mask out incognito URLs if appropriate. |
@@ -535,13 +729,17 @@ void ActivityLog::LogAction(scoped_refptr<Action> action) { |
VLOG(1) << action->PrintForDebug(); |
} |
+bool ActivityLog::ShouldLog(const std::string& extension_id) const { |
+ return is_active_ && !ActivityLogAPI::IsExtensionWhitelisted(extension_id); |
+} |
+ |
void ActivityLog::OnScriptsExecuted( |
const content::WebContents* web_contents, |
const ExecutingScriptsMap& extension_ids, |
const GURL& on_url) { |
- Profile* profile = |
- Profile::FromBrowserContext(web_contents->GetBrowserContext()); |
- ExtensionRegistry* registry = ExtensionRegistry::Get(profile); |
+ if (!is_active_) |
+ return; |
+ ExtensionRegistry* registry = ExtensionRegistry::Get(profile_); |
for (ExecutingScriptsMap::const_iterator it = extension_ids.begin(); |
it != extension_ids.end(); ++it) { |
const Extension* extension = |
@@ -564,7 +762,7 @@ void ActivityLog::OnScriptsExecuted( |
web_contents->GetBrowserContext()->IsOffTheRecord()); |
const prerender::PrerenderManager* prerender_manager = |
- prerender::PrerenderManagerFactory::GetForProfile(profile); |
+ prerender::PrerenderManagerFactory::GetForProfile(profile_); |
if (prerender_manager && |
prerender_manager->IsWebContentsPrerendering(web_contents, NULL)) |
action->mutable_other()->SetBoolean(constants::kActionPrerender, true); |
@@ -578,31 +776,6 @@ void ActivityLog::OnScriptsExecuted( |
} |
} |
-void ActivityLog::OnApiEventDispatched( |
- const std::string& extension_id, |
- const std::string& event_name, |
- std::unique_ptr<base::ListValue> event_args) { |
- DCHECK_CURRENTLY_ON(BrowserThread::UI); |
- scoped_refptr<Action> action = new Action(extension_id, |
- base::Time::Now(), |
- Action::ACTION_API_EVENT, |
- event_name); |
- action->set_args(std::move(event_args)); |
- LogAction(action); |
-} |
- |
-void ActivityLog::OnApiFunctionCalled(const std::string& extension_id, |
- const std::string& api_name, |
- std::unique_ptr<base::ListValue> args) { |
- DCHECK_CURRENTLY_ON(BrowserThread::UI); |
- scoped_refptr<Action> action = new Action(extension_id, |
- base::Time::Now(), |
- Action::ACTION_API_CALL, |
- api_name); |
- action->set_args(std::move(args)); |
- LogAction(action); |
-} |
- |
// LOOKUP ACTIONS. ------------------------------------------------------------- |
void ActivityLog::GetFilteredActions( |
@@ -660,6 +833,54 @@ void ActivityLog::DeleteDatabase() { |
database_policy_->DeleteDatabase(); |
} |
+void ActivityLog::CheckActive() { |
+ bool has_db = db_enabled_ && database_policy_; |
+ ActivityLogState& state = g_activity_log_state.Get(); |
+ content::BrowserContext* off_the_record = |
+ profile_->HasOffTheRecordProfile() ? profile_->GetOffTheRecordProfile() |
+ : nullptr; |
+ if (has_db || IsWatchdogAppActive()) { |
+ if (is_active_) |
+ return; // Already enabled. |
+ state.AddActiveContext(profile_); |
+ if (off_the_record) |
+ state.AddActiveContext(off_the_record); |
+ registrar_.Add(this, chrome::NOTIFICATION_PROFILE_CREATED, |
+ content::NotificationService::AllSources()); |
+ registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED, |
+ content::NotificationService::AllSources()); |
+ is_active_ = true; |
+ } else if (is_active_) { |
+ state.RemoveActiveContext(profile_); |
+ if (off_the_record) |
+ state.RemoveActiveContext(off_the_record); |
+ registrar_.RemoveAll(); |
+ is_active_ = false; |
+ } |
+} |
+ |
+void ActivityLog::Observe(int type, |
+ const content::NotificationSource& source, |
+ const content::NotificationDetails& details) { |
+ DCHECK(is_active_); |
+ switch (type) { |
+ case chrome::NOTIFICATION_PROFILE_CREATED: { |
+ Profile* profile = content::Source<Profile>(source).ptr(); |
+ if (profile_->IsSameProfile(profile)) |
+ g_activity_log_state.Get().AddActiveContext(profile); |
+ break; |
+ } |
+ case chrome::NOTIFICATION_PROFILE_DESTROYED: { |
+ Profile* profile = content::Source<Profile>(source).ptr(); |
+ if (profile_->IsSameProfile(profile)) |
+ g_activity_log_state.Get().RemoveActiveContext(profile); |
+ break; |
+ } |
+ default: |
+ NOTREACHED(); |
+ } |
+} |
+ |
template <> |
void BrowserContextKeyedAPIFactory<ActivityLog>::DeclareFactoryDependencies() { |
DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory()); |