Index: chrome/browser/extensions/activity_log.cc |
=================================================================== |
--- chrome/browser/extensions/activity_log.cc (revision 172624) |
+++ chrome/browser/extensions/activity_log.cc (working copy) |
@@ -4,30 +4,162 @@ |
#include "chrome/browser/extensions/activity_log.h" |
+#include <set> |
#include "base/command_line.h" |
+#include "base/json/json_string_value_serializer.h" |
#include "base/logging.h" |
#include "base/string_util.h" |
+#include "base/threading/thread_checker.h" |
+#include "chrome/browser/extensions/blocked_actions.h" |
#include "chrome/browser/extensions/extension_service.h" |
#include "chrome/browser/extensions/extension_system.h" |
+#include "chrome/browser/extensions/manager_actions.h" |
#include "chrome/browser/profiles/profile.h" |
+#include "chrome/browser/profiles/profile_dependency_manager.h" |
+#include "chrome/browser/profiles/profile_keyed_service_factory.h" |
+#include "chrome/common/chrome_constants.h" |
#include "chrome/common/chrome_switches.h" |
#include "chrome/common/extensions/extension.h" |
#include "content/public/browser/web_contents.h" |
#include "googleurl/src/gurl.h" |
+#include "sql/error_delegate_util.h" |
+#include "third_party/re2/re2/re2.h" |
+namespace { |
+ |
+static const char* kActivityThreadName = "Chrome_ActivityLogThread"; |
Eric Dingle
2012/12/13 20:37:34
No need for static keyword in this context.
felt
2012/12/15 02:51:52
Done.
|
+ |
+// Concatenate an API call with its arguments. |
+std::string MakeCallSignature(const std::string& name, const ListValue& args) { |
+ std::string call_signature = name + "("; |
+ ListValue::const_iterator it = args.begin(); |
+ for (; it != args.end(); ++it) { |
+ std::string arg; |
+ JSONStringValueSerializer serializer(&arg); |
+ if (serializer.SerializeAndOmitBinaryValues(**it)) { |
+ if (it != args.begin()) |
+ call_signature += ", "; |
+ call_signature += arg; |
+ } |
+ } |
+ call_signature += ")"; |
+ return call_signature; |
+} |
+ |
+} // namespace |
+ |
namespace extensions { |
-ActivityLog::ActivityLog() { |
+namespace { |
+ |
+// Each profile has different extensions, so we keep a different database for |
+// each profile. |
+class ActivityLogFactory : public ProfileKeyedServiceFactory { |
Eric Dingle
2012/12/13 20:37:34
All classes should have at least one constructor a
felt
2012/12/15 02:51:52
Done.
|
+ public: |
+ static ActivityLog* GetForProfile(Profile* profile) { |
+ return static_cast<ActivityLog*>( |
+ GetInstance()->GetServiceForProfile(profile, true)); |
+ } |
+ |
+ static ActivityLogFactory* GetInstance(); |
+ |
+ private: |
+ friend struct DefaultSingletonTraits<ActivityLogFactory>; |
+ |
+ ActivityLogFactory() |
+ : ProfileKeyedServiceFactory("ActivityLog", |
+ ProfileDependencyManager::GetInstance()) { } |
+ |
+ virtual ProfileKeyedService* BuildServiceInstanceFor( |
+ Profile* profile) const OVERRIDE { |
+ return new ActivityLog(profile); |
+ } |
+ |
+ virtual bool ServiceRedirectedInIncognito() const OVERRIDE { |
+ return true; |
+ } |
+}; |
+ |
+ActivityLogFactory* ActivityLogFactory::GetInstance() { |
+ return Singleton<ActivityLogFactory>::get(); |
+} |
+ |
+// This handles errors from the database. |
+class KillActivityDatabaseErrorDelegate : public sql::ErrorDelegate { |
+ public: |
+ explicit KillActivityDatabaseErrorDelegate(ActivityLog* backend) |
+ : backend_(backend), |
+ scheduled_death_(false) { } |
+ |
+ virtual int OnError(int error, |
+ sql::Connection* connection, |
+ sql::Statement* stmt) OVERRIDE { |
+ if (!scheduled_death_ && sql::IsErrorCatastrophic(error)) { |
+ scheduled_death_ = true; |
+ backend_->KillActivityLogDatabase(); |
+ } |
+ return error; |
+ } |
+ |
+ bool scheduled_death() const { |
+ return scheduled_death_; |
+ } |
+ |
+ private: |
+ ActivityLog* backend_; |
+ bool scheduled_death_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(KillActivityDatabaseErrorDelegate); |
+}; |
+ |
+} // namespace |
+ |
+// Use GetInstance instead of directly creating an ActivityLog. |
+ActivityLog::ActivityLog(Profile* profile) |
+ : thread_(new base::Thread(kActivityThreadName)) { |
Eric Dingle
2012/12/13 20:37:34
I'm not saying that this is wrong, but you should
felt
2012/12/17 22:45:09
Now using the DB thread.
|
+ |
Eric Dingle
2012/12/13 20:37:34
Remove extra blank line.
felt
2012/12/15 02:51:52
Done.
|
log_activity_to_stdout_ = CommandLine::ForCurrentProcess()-> |
HasSwitch(switches::kEnableExtensionActivityLogging); |
+ |
+ // If the database cannot be initialized, we keep chugging along and still |
+ // log actions for displaying in the UI. |
+ db_ = new ActivityDatabase(); |
Eric Dingle
2012/12/13 20:37:34
Would suggest using db_.reset(new ActivityDatabase
felt
2012/12/15 02:51:52
The docs in base/memory/ref_counted.h say to use a
|
+ KillActivityDatabaseErrorDelegate* error_delegate = |
+ new KillActivityDatabaseErrorDelegate(this); |
+ FilePath base_dir = profile->GetPath(); |
+ FilePath database_name = base_dir.Append(chrome::kActivityLogFilename); |
+ sql::InitStatus status = db_->Init(database_name, error_delegate); |
+ if (status == sql::INIT_OK) { |
+ thread_->Start(); |
+ } else { |
+ LOG(ERROR) << "Couldn't initialize the activity log database."; |
+ if (error_delegate->scheduled_death()) { |
+ KillActivityLogDatabase(); |
+ } |
+ } |
} |
+// If you call this constructor, none of the actions will be recorded in the |
+// database, but they will still be displayed in the UI. You should use |
+// GetInstance instead of directly creating an ActivityLog. |
+ActivityLog::ActivityLog() |
+ : thread_(NULL) { |
+ log_activity_to_stdout_ = CommandLine::ForCurrentProcess()-> |
+ HasSwitch(switches::kEnableExtensionActivityLogging); |
+} |
+ |
ActivityLog::~ActivityLog() { |
+ if (thread_) { |
+ // Delete and defensively NULL the thread. |
+ base::Thread* thread = thread_; |
+ thread_ = NULL; |
+ delete thread; |
+ } |
} |
// static |
-ActivityLog* ActivityLog::GetInstance() { |
- return Singleton<ActivityLog>::get(); |
+ActivityLog* ActivityLog::GetInstance(Profile* profile) { |
+ return ActivityLogFactory::GetForProfile(profile); |
} |
void ActivityLog::AddObserver(const Extension* extension, |
@@ -50,7 +182,6 @@ |
} |
} |
-// Extension* |
bool ActivityLog::HasObservers(const Extension* extension) const { |
base::AutoLock scoped_lock(lock_); |
@@ -59,27 +190,81 @@ |
return observers_.count(extension) > 0 || log_activity_to_stdout_; |
} |
-void ActivityLog::Log(const Extension* extension, |
- Activity activity, |
- const std::string& message) const { |
- std::vector<std::string> messages(1, message); |
- Log(extension, activity, messages); |
+void ActivityLog::LogManagerAction(const Extension* extension, |
+ const std::string& name, |
+ const ListValue& args) { |
+ base::AutoLock scoped_lock(lock_); |
+ std::string verb; |
Eric Dingle
2012/12/13 20:37:34
std::string manager, verb;
felt
2012/12/15 02:51:52
Done.
|
+ std::string manager; |
+ bool matches = RE2::FullMatch(name, "(.*?)\\.(.*)", &manager, &verb); |
+ if (matches) { |
+ std::string call_signature = MakeCallSignature(name, args); |
+ scoped_refptr<ManagerAction> action = new ManagerAction( |
+ extension->id(), |
+ ManagerAction::StringAsActionType(verb), |
+ ManagerAction::StringAsTargetType(manager), |
+ call_signature, |
+ base::Time::Now()); |
+ ScheduleAndForget(&ActivityDatabase::RecordManagerAction, action); |
+ |
+ // Display the action. |
+ ObserverMap::const_iterator iter = observers_.find(extension); |
+ if (iter != observers_.end()) { |
+ iter->second->Notify(&Observer::OnExtensionActivity, |
+ extension, |
+ ActivityLog::ACTIVITY_EXTENSION_API_CALL, |
+ std::vector<std::string>(1, call_signature)); |
+ } |
+ if (log_activity_to_stdout_) { |
+ LOG(INFO) << action->PrettyPrintForDebug(); |
+ } |
+ } else { |
+ LOG(ERROR) << "Unknown API call! " << name; |
+ } |
} |
-void ActivityLog::Log(const Extension* extension, |
- Activity activity, |
- const std::vector<std::string>& messages) const { |
+void ActivityLog::LogBlockedAction(const Extension* extension, |
Eric Dingle
2012/12/13 20:37:34
You don't notify the observers on blocked calls?
felt
2012/12/15 02:51:52
Whoops. Done.
|
+ const std::string& blocked_name, |
+ const ListValue& args, |
+ const char* reason) { |
base::AutoLock scoped_lock(lock_); |
+ std::string blocked_call = MakeCallSignature(blocked_name, args); |
+ scoped_refptr<BlockedAction> action = new BlockedAction(extension->id(), |
+ blocked_call, |
+ std::string(reason), |
+ base::Time::Now()); |
+ ScheduleAndForget(&ActivityDatabase::RecordBlockedAction, action); |
+ if (log_activity_to_stdout_) { |
+ LOG(INFO) << action->PrettyPrintForDebug(); |
+ } |
+} |
+void ActivityLog::LogUrlAction(const Extension* extension, |
+ const GURL& url, |
+ const string16& url_title, |
+ std::string& message, |
+ const UrlAction::UrlActionType verb) { |
+ base::AutoLock scoped_lock(lock_); |
+ scoped_refptr<UrlAction> action = new UrlAction( |
+ extension->id(), |
+ verb, |
+ url, |
+ url_title, |
+ message, |
+ base::Time::Now()); |
+ ScheduleAndForget(&ActivityDatabase::RecordUrlAction, action); |
+ |
+ // Display the action. |
ObserverMap::const_iterator iter = observers_.find(extension); |
if (iter != observers_.end()) { |
- iter->second->Notify(&Observer::OnExtensionActivity, extension, activity, |
- messages); |
+ iter->second->Notify(&Observer::OnExtensionActivity, |
+ extension, |
+ ActivityLog::ACTIVITY_CONTENT_SCRIPT, |
Eric Dingle
2012/12/13 20:37:34
I assume that you'll notify for a different kind o
felt
2012/12/15 02:51:52
Yes, I'm going to change how this works as part of
|
+ std::vector<std::string>(1, |
+ action->PrettyPrintForDebug())); |
} |
- |
if (log_activity_to_stdout_) { |
- LOG(INFO) << extension->id() << ":" << ActivityToString(activity) << ":" << |
- JoinString(messages, ' '); |
+ LOG(INFO) << action->PrettyPrintForDebug(); |
} |
} |
@@ -100,16 +285,31 @@ |
if (!extension || !HasObservers(extension)) |
continue; |
- for (std::set<std::string>::const_iterator it2 = it->second.begin(); |
- it2 != it->second.end(); ++it2) { |
- std::vector<std::string> messages; |
- messages.push_back(on_url.spec()); |
- messages.push_back(*it2); |
- Log(extension, ActivityLog::ACTIVITY_CONTENT_SCRIPT, messages); |
+ // If OnScriptsExecuted is fired because of tabs.executeScript, the list |
+ // of content scripts will be empty. We don't want to log it because |
+ // the call to tabs.executeScript will have already been logged anyway. |
+ if (!it->second.empty()) { |
+ std::string ext_scripts_str = ""; |
+ for (std::set<std::string>::const_iterator it2 = it->second.begin(); |
+ it2 != it->second.end(); ++it2) { |
+ ext_scripts_str += *it2; |
+ ext_scripts_str += " "; |
+ } |
+ LogUrlAction(extension, |
+ on_url, |
+ web_contents->GetTitle(), |
+ ext_scripts_str, |
+ UrlAction::INSERTED); |
} |
} |
} |
+void ActivityLog::KillActivityLogDatabase() { |
+ if (db_.get()) { |
+ ScheduleAndForget(&ActivityDatabase::KillDatabase); |
+ } |
+} |
+ |
// static |
const char* ActivityLog::ActivityToString(Activity activity) { |
switch (activity) { |