Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/extensions/activity_log.h" | 5 #include "chrome/browser/extensions/activity_log.h" |
| 6 | 6 |
| 7 #include <set> | |
| 7 #include "base/command_line.h" | 8 #include "base/command_line.h" |
| 9 #include "base/json/json_string_value_serializer.h" | |
| 8 #include "base/logging.h" | 10 #include "base/logging.h" |
| 9 #include "base/string_util.h" | 11 #include "base/string_util.h" |
| 12 #include "base/threading/thread_checker.h" | |
| 13 #include "chrome/browser/extensions/blocked_actions.h" | |
| 10 #include "chrome/browser/extensions/extension_service.h" | 14 #include "chrome/browser/extensions/extension_service.h" |
| 11 #include "chrome/browser/extensions/extension_system.h" | 15 #include "chrome/browser/extensions/extension_system.h" |
| 16 #include "chrome/browser/extensions/manager_actions.h" | |
| 12 #include "chrome/browser/profiles/profile.h" | 17 #include "chrome/browser/profiles/profile.h" |
| 18 #include "chrome/browser/profiles/profile_dependency_manager.h" | |
| 19 #include "chrome/browser/profiles/profile_keyed_service_factory.h" | |
| 20 #include "chrome/common/chrome_constants.h" | |
| 13 #include "chrome/common/chrome_switches.h" | 21 #include "chrome/common/chrome_switches.h" |
| 14 #include "chrome/common/extensions/extension.h" | 22 #include "chrome/common/extensions/extension.h" |
| 15 #include "content/public/browser/web_contents.h" | 23 #include "content/public/browser/web_contents.h" |
| 16 #include "googleurl/src/gurl.h" | 24 #include "googleurl/src/gurl.h" |
| 25 #include "sql/error_delegate_util.h" | |
| 26 #include "third_party/re2/re2/re2.h" | |
| 27 | |
| 28 namespace { | |
| 29 | |
| 30 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.
| |
| 31 | |
| 32 // Concatenate an API call with its arguments. | |
| 33 std::string MakeCallSignature(const std::string& name, const ListValue& args) { | |
| 34 std::string call_signature = name + "("; | |
| 35 ListValue::const_iterator it = args.begin(); | |
| 36 for (; it != args.end(); ++it) { | |
| 37 std::string arg; | |
| 38 JSONStringValueSerializer serializer(&arg); | |
| 39 if (serializer.SerializeAndOmitBinaryValues(**it)) { | |
| 40 if (it != args.begin()) | |
| 41 call_signature += ", "; | |
| 42 call_signature += arg; | |
| 43 } | |
| 44 } | |
| 45 call_signature += ")"; | |
| 46 return call_signature; | |
| 47 } | |
| 48 | |
| 49 } // namespace | |
| 17 | 50 |
| 18 namespace extensions { | 51 namespace extensions { |
| 19 | 52 |
| 20 ActivityLog::ActivityLog() { | 53 namespace { |
| 54 | |
| 55 // Each profile has different extensions, so we keep a different database for | |
| 56 // each profile. | |
| 57 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.
| |
| 58 public: | |
| 59 static ActivityLog* GetForProfile(Profile* profile) { | |
| 60 return static_cast<ActivityLog*>( | |
| 61 GetInstance()->GetServiceForProfile(profile, true)); | |
| 62 } | |
| 63 | |
| 64 static ActivityLogFactory* GetInstance(); | |
| 65 | |
| 66 private: | |
| 67 friend struct DefaultSingletonTraits<ActivityLogFactory>; | |
| 68 | |
| 69 ActivityLogFactory() | |
| 70 : ProfileKeyedServiceFactory("ActivityLog", | |
| 71 ProfileDependencyManager::GetInstance()) { } | |
| 72 | |
| 73 virtual ProfileKeyedService* BuildServiceInstanceFor( | |
| 74 Profile* profile) const OVERRIDE { | |
| 75 return new ActivityLog(profile); | |
| 76 } | |
| 77 | |
| 78 virtual bool ServiceRedirectedInIncognito() const OVERRIDE { | |
| 79 return true; | |
| 80 } | |
| 81 }; | |
| 82 | |
| 83 ActivityLogFactory* ActivityLogFactory::GetInstance() { | |
| 84 return Singleton<ActivityLogFactory>::get(); | |
| 85 } | |
| 86 | |
| 87 // This handles errors from the database. | |
| 88 class KillActivityDatabaseErrorDelegate : public sql::ErrorDelegate { | |
| 89 public: | |
| 90 explicit KillActivityDatabaseErrorDelegate(ActivityLog* backend) | |
| 91 : backend_(backend), | |
| 92 scheduled_death_(false) { } | |
| 93 | |
| 94 virtual int OnError(int error, | |
| 95 sql::Connection* connection, | |
| 96 sql::Statement* stmt) OVERRIDE { | |
| 97 if (!scheduled_death_ && sql::IsErrorCatastrophic(error)) { | |
| 98 scheduled_death_ = true; | |
| 99 backend_->KillActivityLogDatabase(); | |
| 100 } | |
| 101 return error; | |
| 102 } | |
| 103 | |
| 104 bool scheduled_death() const { | |
| 105 return scheduled_death_; | |
| 106 } | |
| 107 | |
| 108 private: | |
| 109 ActivityLog* backend_; | |
| 110 bool scheduled_death_; | |
| 111 | |
| 112 DISALLOW_COPY_AND_ASSIGN(KillActivityDatabaseErrorDelegate); | |
| 113 }; | |
| 114 | |
| 115 } // namespace | |
| 116 | |
| 117 // Use GetInstance instead of directly creating an ActivityLog. | |
| 118 ActivityLog::ActivityLog(Profile* profile) | |
| 119 : 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.
| |
| 120 | |
|
Eric Dingle
2012/12/13 20:37:34
Remove extra blank line.
felt
2012/12/15 02:51:52
Done.
| |
| 121 log_activity_to_stdout_ = CommandLine::ForCurrentProcess()-> | |
| 122 HasSwitch(switches::kEnableExtensionActivityLogging); | |
| 123 | |
| 124 // If the database cannot be initialized, we keep chugging along and still | |
| 125 // log actions for displaying in the UI. | |
| 126 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
| |
| 127 KillActivityDatabaseErrorDelegate* error_delegate = | |
| 128 new KillActivityDatabaseErrorDelegate(this); | |
| 129 FilePath base_dir = profile->GetPath(); | |
| 130 FilePath database_name = base_dir.Append(chrome::kActivityLogFilename); | |
| 131 sql::InitStatus status = db_->Init(database_name, error_delegate); | |
| 132 if (status == sql::INIT_OK) { | |
| 133 thread_->Start(); | |
| 134 } else { | |
| 135 LOG(ERROR) << "Couldn't initialize the activity log database."; | |
| 136 if (error_delegate->scheduled_death()) { | |
| 137 KillActivityLogDatabase(); | |
| 138 } | |
| 139 } | |
| 140 } | |
| 141 | |
| 142 // If you call this constructor, none of the actions will be recorded in the | |
| 143 // database, but they will still be displayed in the UI. You should use | |
| 144 // GetInstance instead of directly creating an ActivityLog. | |
| 145 ActivityLog::ActivityLog() | |
| 146 : thread_(NULL) { | |
| 21 log_activity_to_stdout_ = CommandLine::ForCurrentProcess()-> | 147 log_activity_to_stdout_ = CommandLine::ForCurrentProcess()-> |
| 22 HasSwitch(switches::kEnableExtensionActivityLogging); | 148 HasSwitch(switches::kEnableExtensionActivityLogging); |
| 23 } | 149 } |
| 24 | 150 |
| 25 ActivityLog::~ActivityLog() { | 151 ActivityLog::~ActivityLog() { |
| 152 if (thread_) { | |
| 153 // Delete and defensively NULL the thread. | |
| 154 base::Thread* thread = thread_; | |
| 155 thread_ = NULL; | |
| 156 delete thread; | |
| 157 } | |
| 26 } | 158 } |
| 27 | 159 |
| 28 // static | 160 // static |
| 29 ActivityLog* ActivityLog::GetInstance() { | 161 ActivityLog* ActivityLog::GetInstance(Profile* profile) { |
| 30 return Singleton<ActivityLog>::get(); | 162 return ActivityLogFactory::GetForProfile(profile); |
| 31 } | 163 } |
| 32 | 164 |
| 33 void ActivityLog::AddObserver(const Extension* extension, | 165 void ActivityLog::AddObserver(const Extension* extension, |
| 34 ActivityLog::Observer* observer) { | 166 ActivityLog::Observer* observer) { |
| 35 base::AutoLock scoped_lock(lock_); | 167 base::AutoLock scoped_lock(lock_); |
| 36 | 168 |
| 37 if (observers_.count(extension) == 0) { | 169 if (observers_.count(extension) == 0) { |
| 38 observers_[extension] = new ObserverListThreadSafe<Observer>; | 170 observers_[extension] = new ObserverListThreadSafe<Observer>; |
| 39 } | 171 } |
| 40 | 172 |
| 41 observers_[extension]->AddObserver(observer); | 173 observers_[extension]->AddObserver(observer); |
| 42 } | 174 } |
| 43 | 175 |
| 44 void ActivityLog::RemoveObserver(const Extension* extension, | 176 void ActivityLog::RemoveObserver(const Extension* extension, |
| 45 ActivityLog::Observer* observer) { | 177 ActivityLog::Observer* observer) { |
| 46 base::AutoLock scoped_lock(lock_); | 178 base::AutoLock scoped_lock(lock_); |
| 47 | 179 |
| 48 if (observers_.count(extension) == 1) { | 180 if (observers_.count(extension) == 1) { |
| 49 observers_[extension]->RemoveObserver(observer); | 181 observers_[extension]->RemoveObserver(observer); |
| 50 } | 182 } |
| 51 } | 183 } |
| 52 | 184 |
| 53 // Extension* | |
| 54 bool ActivityLog::HasObservers(const Extension* extension) const { | 185 bool ActivityLog::HasObservers(const Extension* extension) const { |
| 55 base::AutoLock scoped_lock(lock_); | 186 base::AutoLock scoped_lock(lock_); |
| 56 | 187 |
| 57 // We also return true if extension activity logging is enabled since in that | 188 // We also return true if extension activity logging is enabled since in that |
| 58 // case this class is observing all extensions. | 189 // case this class is observing all extensions. |
| 59 return observers_.count(extension) > 0 || log_activity_to_stdout_; | 190 return observers_.count(extension) > 0 || log_activity_to_stdout_; |
| 60 } | 191 } |
| 61 | 192 |
| 62 void ActivityLog::Log(const Extension* extension, | 193 void ActivityLog::LogManagerAction(const Extension* extension, |
| 63 Activity activity, | 194 const std::string& name, |
| 64 const std::string& message) const { | 195 const ListValue& args) { |
| 65 std::vector<std::string> messages(1, message); | 196 base::AutoLock scoped_lock(lock_); |
| 66 Log(extension, activity, messages); | 197 std::string verb; |
|
Eric Dingle
2012/12/13 20:37:34
std::string manager, verb;
felt
2012/12/15 02:51:52
Done.
| |
| 67 } | 198 std::string manager; |
| 199 bool matches = RE2::FullMatch(name, "(.*?)\\.(.*)", &manager, &verb); | |
| 200 if (matches) { | |
| 201 std::string call_signature = MakeCallSignature(name, args); | |
| 202 scoped_refptr<ManagerAction> action = new ManagerAction( | |
| 203 extension->id(), | |
| 204 ManagerAction::StringAsActionType(verb), | |
| 205 ManagerAction::StringAsTargetType(manager), | |
| 206 call_signature, | |
| 207 base::Time::Now()); | |
| 208 ScheduleAndForget(&ActivityDatabase::RecordManagerAction, action); | |
| 68 | 209 |
| 69 void ActivityLog::Log(const Extension* extension, | 210 // Display the action. |
| 70 Activity activity, | 211 ObserverMap::const_iterator iter = observers_.find(extension); |
| 71 const std::vector<std::string>& messages) const { | 212 if (iter != observers_.end()) { |
| 72 base::AutoLock scoped_lock(lock_); | 213 iter->second->Notify(&Observer::OnExtensionActivity, |
| 73 | 214 extension, |
| 74 ObserverMap::const_iterator iter = observers_.find(extension); | 215 ActivityLog::ACTIVITY_EXTENSION_API_CALL, |
| 75 if (iter != observers_.end()) { | 216 std::vector<std::string>(1, call_signature)); |
| 76 iter->second->Notify(&Observer::OnExtensionActivity, extension, activity, | 217 } |
| 77 messages); | 218 if (log_activity_to_stdout_) { |
| 78 } | 219 LOG(INFO) << action->PrettyPrintForDebug(); |
| 79 | 220 } |
| 80 if (log_activity_to_stdout_) { | 221 } else { |
| 81 LOG(INFO) << extension->id() << ":" << ActivityToString(activity) << ":" << | 222 LOG(ERROR) << "Unknown API call! " << name; |
| 82 JoinString(messages, ' '); | |
| 83 } | 223 } |
| 84 } | 224 } |
| 85 | 225 |
| 226 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.
| |
| 227 const std::string& blocked_name, | |
| 228 const ListValue& args, | |
| 229 const char* reason) { | |
| 230 base::AutoLock scoped_lock(lock_); | |
| 231 std::string blocked_call = MakeCallSignature(blocked_name, args); | |
| 232 scoped_refptr<BlockedAction> action = new BlockedAction(extension->id(), | |
| 233 blocked_call, | |
| 234 std::string(reason), | |
| 235 base::Time::Now()); | |
| 236 ScheduleAndForget(&ActivityDatabase::RecordBlockedAction, action); | |
| 237 if (log_activity_to_stdout_) { | |
| 238 LOG(INFO) << action->PrettyPrintForDebug(); | |
| 239 } | |
| 240 } | |
| 241 | |
| 242 void ActivityLog::LogUrlAction(const Extension* extension, | |
| 243 const GURL& url, | |
| 244 const string16& url_title, | |
| 245 std::string& message, | |
| 246 const UrlAction::UrlActionType verb) { | |
| 247 base::AutoLock scoped_lock(lock_); | |
| 248 scoped_refptr<UrlAction> action = new UrlAction( | |
| 249 extension->id(), | |
| 250 verb, | |
| 251 url, | |
| 252 url_title, | |
| 253 message, | |
| 254 base::Time::Now()); | |
| 255 ScheduleAndForget(&ActivityDatabase::RecordUrlAction, action); | |
| 256 | |
| 257 // Display the action. | |
| 258 ObserverMap::const_iterator iter = observers_.find(extension); | |
| 259 if (iter != observers_.end()) { | |
| 260 iter->second->Notify(&Observer::OnExtensionActivity, | |
| 261 extension, | |
| 262 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
| |
| 263 std::vector<std::string>(1, | |
| 264 action->PrettyPrintForDebug())); | |
| 265 } | |
| 266 if (log_activity_to_stdout_) { | |
| 267 LOG(INFO) << action->PrettyPrintForDebug(); | |
| 268 } | |
| 269 } | |
| 270 | |
| 86 void ActivityLog::OnScriptsExecuted( | 271 void ActivityLog::OnScriptsExecuted( |
| 87 const content::WebContents* web_contents, | 272 const content::WebContents* web_contents, |
| 88 const ExecutingScriptsMap& extension_ids, | 273 const ExecutingScriptsMap& extension_ids, |
| 89 int32 on_page_id, | 274 int32 on_page_id, |
| 90 const GURL& on_url) { | 275 const GURL& on_url) { |
| 91 Profile* profile = | 276 Profile* profile = |
| 92 Profile::FromBrowserContext(web_contents->GetBrowserContext()); | 277 Profile::FromBrowserContext(web_contents->GetBrowserContext()); |
| 93 const ExtensionService* extension_service = | 278 const ExtensionService* extension_service = |
| 94 ExtensionSystem::Get(profile)->extension_service(); | 279 ExtensionSystem::Get(profile)->extension_service(); |
| 95 const ExtensionSet* extensions = extension_service->extensions(); | 280 const ExtensionSet* extensions = extension_service->extensions(); |
| 96 | 281 |
| 97 for (ExecutingScriptsMap::const_iterator it = extension_ids.begin(); | 282 for (ExecutingScriptsMap::const_iterator it = extension_ids.begin(); |
| 98 it != extension_ids.end(); ++it) { | 283 it != extension_ids.end(); ++it) { |
| 99 const Extension* extension = extensions->GetByID(it->first); | 284 const Extension* extension = extensions->GetByID(it->first); |
| 100 if (!extension || !HasObservers(extension)) | 285 if (!extension || !HasObservers(extension)) |
| 101 continue; | 286 continue; |
| 102 | 287 |
| 103 for (std::set<std::string>::const_iterator it2 = it->second.begin(); | 288 // If OnScriptsExecuted is fired because of tabs.executeScript, the list |
| 104 it2 != it->second.end(); ++it2) { | 289 // of content scripts will be empty. We don't want to log it because |
| 105 std::vector<std::string> messages; | 290 // the call to tabs.executeScript will have already been logged anyway. |
| 106 messages.push_back(on_url.spec()); | 291 if (!it->second.empty()) { |
| 107 messages.push_back(*it2); | 292 std::string ext_scripts_str = ""; |
| 108 Log(extension, ActivityLog::ACTIVITY_CONTENT_SCRIPT, messages); | 293 for (std::set<std::string>::const_iterator it2 = it->second.begin(); |
| 294 it2 != it->second.end(); ++it2) { | |
| 295 ext_scripts_str += *it2; | |
| 296 ext_scripts_str += " "; | |
| 297 } | |
| 298 LogUrlAction(extension, | |
| 299 on_url, | |
| 300 web_contents->GetTitle(), | |
| 301 ext_scripts_str, | |
| 302 UrlAction::INSERTED); | |
| 109 } | 303 } |
| 110 } | 304 } |
| 111 } | 305 } |
| 112 | 306 |
| 307 void ActivityLog::KillActivityLogDatabase() { | |
| 308 if (db_.get()) { | |
| 309 ScheduleAndForget(&ActivityDatabase::KillDatabase); | |
| 310 } | |
| 311 } | |
| 312 | |
| 113 // static | 313 // static |
| 114 const char* ActivityLog::ActivityToString(Activity activity) { | 314 const char* ActivityLog::ActivityToString(Activity activity) { |
| 115 switch (activity) { | 315 switch (activity) { |
| 116 case ActivityLog::ACTIVITY_EXTENSION_API_CALL: | 316 case ActivityLog::ACTIVITY_EXTENSION_API_CALL: |
| 117 return "api_call"; | 317 return "api_call"; |
| 118 case ActivityLog::ACTIVITY_EXTENSION_API_BLOCK: | 318 case ActivityLog::ACTIVITY_EXTENSION_API_BLOCK: |
| 119 return "api_block"; | 319 return "api_block"; |
| 120 case ActivityLog::ACTIVITY_CONTENT_SCRIPT: | 320 case ActivityLog::ACTIVITY_CONTENT_SCRIPT: |
| 121 return "content_script"; | 321 return "content_script"; |
| 122 default: | 322 default: |
| 123 NOTREACHED(); | 323 NOTREACHED(); |
| 124 return ""; | 324 return ""; |
| 125 } | 325 } |
| 126 } | 326 } |
| 127 | 327 |
| 128 } // namespace extensions | 328 } // namespace extensions |
| OLD | NEW |