From a765cc1a419696309423c3e6389bc6572530d617 Mon Sep 17 00:00:00 2001 From: "felt@chromium.org" Date: Fri, 7 Jun 2013 12:51:20 +0000 Subject: [PATCH] This implements half of the activityLogPrivate API. The API is only available to a single whitelisted extension. It includes the schema and events. It is missing the implementation of its function, which I'll add as a separate CL. BUG=241672 Review URL: https://chromiumcodereview.appspot.com/16061002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@204796 0039d316-1c4b-4281-b951-d872f2087c98 --- .../activity_log/activity_actions.cc | 10 +- .../activity_log/activity_actions.h | 12 +- .../activity_database_unittest.cc | 17 +-- .../extensions/activity_log/activity_log.cc | 58 ++++---- .../extensions/activity_log/activity_log.h | 12 +- .../activity_log/activity_log_browsertest.cc | 1 + .../activity_log/activity_log_unittest.cc | 4 +- .../extensions/activity_log/api_actions.cc | 33 ++++- .../extensions/activity_log/api_actions.h | 3 + .../activity_log/blocked_actions.cc | 35 ++++- .../extensions/activity_log/blocked_actions.h | 3 + .../extensions/activity_log/dom_actions.cc | 43 ++++-- .../extensions/activity_log/dom_actions.h | 3 + .../activity_log_private_api.cc | 96 ++++++++++++++ .../activity_log_private_api.h | 84 ++++++++++++ .../activity_log_private_api_unittest.cc | 105 +++++++++++++++ .../activity_log_private_apitest.cc | 35 +++++ .../extension_function_histogram_value.h | 1 + ...hrome_browser_main_extra_parts_profiles.cc | 2 + chrome/chrome_browser_extensions.gypi | 4 + chrome/chrome_tests.gypi | 1 + chrome/chrome_tests_unit.gypi | 4 +- .../extensions/api/_permission_features.json | 8 ++ .../extensions/api/activity_log_private.json | 125 ++++++++++++++++++ chrome/common/extensions/api/api.gyp | 1 + .../extensions/permissions/api_permission.h | 1 + .../permissions/chrome_api_permissions.cc | 2 + .../permissions/permission_set_unittest.cc | 1 + .../activity_log_private/friend/manifest.json | 10 ++ .../activity_log_private/friend/reply.js | 9 ++ .../activity_log_private/test/manifest.json | 11 ++ .../activity_log_private/test/test.js | 22 +++ tools/metrics/histograms/histograms.xml | 1 + 33 files changed, 678 insertions(+), 79 deletions(-) create mode 100644 chrome/browser/extensions/api/activity_log_private/activity_log_private_api.cc create mode 100644 chrome/browser/extensions/api/activity_log_private/activity_log_private_api.h create mode 100644 chrome/browser/extensions/api/activity_log_private/activity_log_private_api_unittest.cc create mode 100644 chrome/browser/extensions/api/activity_log_private/activity_log_private_apitest.cc create mode 100644 chrome/common/extensions/api/activity_log_private.json create mode 100644 chrome/test/data/extensions/api_test/activity_log_private/friend/manifest.json create mode 100644 chrome/test/data/extensions/api_test/activity_log_private/friend/reply.js create mode 100644 chrome/test/data/extensions/api_test/activity_log_private/test/manifest.json create mode 100644 chrome/test/data/extensions/api_test/activity_log_private/test/test.js diff --git a/chrome/browser/extensions/activity_log/activity_actions.cc b/chrome/browser/extensions/activity_log/activity_actions.cc index b6195afed74e..7cff66d1ed2b 100644 --- a/chrome/browser/extensions/activity_log/activity_actions.cc +++ b/chrome/browser/extensions/activity_log/activity_actions.cc @@ -5,18 +5,22 @@ #include #include "base/logging.h" #include "base/stringprintf.h" -#include "chrome/browser/extensions/activity_log/api_actions.h" +#include "chrome/browser/extensions/activity_log/activity_actions.h" namespace extensions { +using api::activity_log_private::ExtensionActivity; + const char* Action::kTableBasicFields = "extension_id LONGVARCHAR NOT NULL, " "time INTEGER NOT NULL"; Action::Action(const std::string& extension_id, - const base::Time& time) + const base::Time& time, + ExtensionActivity::ActivityType activity_type) : extension_id_(extension_id), - time_(time) {} + time_(time), + activity_type_(activity_type) {} // static bool Action::InitializeTableInternal(sql::Connection* db, diff --git a/chrome/browser/extensions/activity_log/activity_actions.h b/chrome/browser/extensions/activity_log/activity_actions.h index da7c49990ec6..547d2d8bb60c 100644 --- a/chrome/browser/extensions/activity_log/activity_actions.h +++ b/chrome/browser/extensions/activity_log/activity_actions.h @@ -9,6 +9,7 @@ #include "base/memory/ref_counted_memory.h" #include "base/time.h" #include "base/values.h" +#include "chrome/common/extensions/api/activity_log_private.h" #include "sql/connection.h" #include "sql/statement.h" #include "sql/transaction.h" @@ -27,14 +28,22 @@ class Action : public base::RefCountedThreadSafe { // Record the action in the database. virtual void Record(sql::Connection* db) = 0; + // Flatten the activity's type-specific fields into an ExtensionActivity. + virtual scoped_ptr + ConvertToExtensionActivity() = 0; + // Print an action as a regular string for debugging purposes. virtual std::string PrintForDebug() = 0; const std::string& extension_id() const { return extension_id_; } const base::Time& time() const { return time_; } + api::activity_log_private::ExtensionActivity::ActivityType activity_type() + const { return activity_type_; } protected: - Action(const std::string& extension_id, const base::Time& time); + Action(const std::string& extension_id, + const base::Time& time, + api::activity_log_private::ExtensionActivity::ActivityType type); virtual ~Action() {} // Initialize the table for a given action type. @@ -53,6 +62,7 @@ class Action : public base::RefCountedThreadSafe { std::string extension_id_; base::Time time_; + api::activity_log_private::ExtensionActivity::ActivityType activity_type_; DISALLOW_COPY_AND_ASSIGN(Action); }; diff --git a/chrome/browser/extensions/activity_log/activity_database_unittest.cc b/chrome/browser/extensions/activity_log/activity_database_unittest.cc index d53e785c4c14..e101b1a203ff 100644 --- a/chrome/browser/extensions/activity_log/activity_database_unittest.cc +++ b/chrome/browser/extensions/activity_log/activity_database_unittest.cc @@ -45,11 +45,6 @@ class ActivityDatabaseTest : public ChromeRenderViewHostTestHarness { test_user_manager_.reset(new chromeos::ScopedTestUserManager()); #endif CommandLine command_line(CommandLine::NO_PROGRAM); - profile_ = - Profile::FromBrowserContext(web_contents()->GetBrowserContext()); - extension_service_ = static_cast( - ExtensionSystem::Get(profile_))->CreateExtensionService( - &command_line, base::FilePath(), false); CommandLine::ForCurrentProcess()->AppendSwitch( switches::kEnableExtensionActivityLogTesting); } @@ -61,10 +56,6 @@ class ActivityDatabaseTest : public ChromeRenderViewHostTestHarness { ChromeRenderViewHostTestHarness::TearDown(); } - protected: - ExtensionService* extension_service_; - Profile* profile_; - private: #if defined OS_CHROMEOS chromeos::ScopedStubCrosEnabler stub_cros_enabler_; @@ -217,9 +208,9 @@ TEST_F(ActivityDatabaseTest, GetTodaysActions) { activity_db->RecordAction(extra_dom_action); // Read them back - std::string api_print = "ID: punky, CATEGORY: CALL, " + std::string api_print = "ID: punky, CATEGORY: call, " "API: brewster, ARGS: woof"; - std::string dom_print = "DOM API CALL: lets, ARGS: vamoose, VERB: MODIFIED"; + std::string dom_print = "DOM API CALL: lets, ARGS: vamoose, VERB: modified"; scoped_ptr > > actions = activity_db->GetActions("punky", 0); ASSERT_EQ(2, static_cast(actions->size())); @@ -288,9 +279,9 @@ TEST_F(ActivityDatabaseTest, GetOlderActions) { activity_db->RecordAction(tooold_dom_action); // Read them back - std::string api_print = "ID: punky, CATEGORY: CALL, " + std::string api_print = "ID: punky, CATEGORY: call, " "API: brewster, ARGS: woof"; - std::string dom_print = "DOM API CALL: lets, ARGS: vamoose, VERB: MODIFIED"; + std::string dom_print = "DOM API CALL: lets, ARGS: vamoose, VERB: modified"; scoped_ptr > > actions = activity_db->GetActions("punky", 3); ASSERT_EQ(2, static_cast(actions->size())); diff --git a/chrome/browser/extensions/activity_log/activity_log.cc b/chrome/browser/extensions/activity_log/activity_log.cc index c02d93da0692..59108407e366 100644 --- a/chrome/browser/extensions/activity_log/activity_log.cc +++ b/chrome/browser/extensions/activity_log/activity_log.cc @@ -12,6 +12,7 @@ #include "chrome/browser/extensions/activity_log/activity_log.h" #include "chrome/browser/extensions/activity_log/api_actions.h" #include "chrome/browser/extensions/activity_log/blocked_actions.h" +#include "chrome/browser/extensions/api/activity_log_private/activity_log_private_api.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_system.h" #include "chrome/browser/profiles/incognito_helpers.h" @@ -41,14 +42,6 @@ std::string MakeArgList(const ListValue* args) { return call_signature; } -// Concatenate an API call with its arguments. -std::string MakeCallSignature(const std::string& name, const ListValue* args) { - std::string call_signature = name + "("; - call_signature += MakeArgList(args); - call_signature += ")"; - return call_signature; -} - // Computes whether the activity log is enabled in this browser (controlled by // command-line flags) and caches the value (which is assumed never to change). class LogIsEnabled { @@ -106,12 +99,9 @@ content::BrowserContext* ActivityLogFactory::GetBrowserContextToUse( // Use GetInstance instead of directly creating an ActivityLog. ActivityLog::ActivityLog(Profile* profile) : profile_(profile) { - // enable-extension-activity-logging and enable-extension-activity-ui - log_activity_to_stdout_ = CommandLine::ForCurrentProcess()->HasSwitch( - switches::kEnableExtensionActivityLogging); - // enable-extension-activity-log-testing // This controls whether arguments are collected. + // It also controls whether logging statements are printed. testing_mode_ = CommandLine::ForCurrentProcess()->HasSwitch( switches::kEnableExtensionActivityLogTesting); if (!testing_mode_) { @@ -130,6 +120,8 @@ ActivityLog::ActivityLog(Profile* profile) : profile_(profile) { dispatch_thread_ = BrowserThread::UI; } + observers_ = new ObserverListThreadSafe; + // If the database cannot be initialized for some reason, we keep // chugging along but nothing will get recorded. If the UI is // available, things will still get sent to the UI even if nothing @@ -158,12 +150,11 @@ ActivityLog* ActivityLog::GetInstance(Profile* profile) { } void ActivityLog::AddObserver(ActivityLog::Observer* observer) { - if (!IsLogEnabled()) return; - // TODO(felt) Re-implement Observer notification HERE for the API. + observers_->AddObserver(observer); } void ActivityLog::RemoveObserver(ActivityLog::Observer* observer) { - // TODO(felt) Re-implement Observer notification HERE for the API. + observers_->RemoveObserver(observer); } void ActivityLog::LogAPIActionInternal(const std::string& extension_id, @@ -185,9 +176,8 @@ void ActivityLog::LogAPIActionInternal(const std::string& extension_id, MakeArgList(args), extra); ScheduleAndForget(&ActivityDatabase::RecordAction, action); - // TODO(felt) Re-implement Observer notification HERE for the API. - if (log_activity_to_stdout_) - LOG(INFO) << action->PrintForDebug(); + observers_->Notify(&Observer::OnExtensionActivity, action); + if (testing_mode_) LOG(INFO) << action->PrintForDebug(); } else { LOG(ERROR) << "Unknown API call! " << api_call; } @@ -198,7 +188,8 @@ void ActivityLog::LogAPIAction(const std::string& extension_id, const std::string& api_call, ListValue* args, const std::string& extra) { - if (!IsLogEnabled()) return; + if (!IsLogEnabled() || + ActivityLogAPI::IsExtensionWhitelisted(extension_id)) return; if (!testing_mode_ && arg_whitelist_api_.find(api_call) == arg_whitelist_api_.end()) args->Clear(); @@ -217,7 +208,8 @@ void ActivityLog::LogEventAction(const std::string& extension_id, const std::string& api_call, ListValue* args, const std::string& extra) { - if (!IsLogEnabled()) return; + if (!IsLogEnabled() || + ActivityLogAPI::IsExtensionWhitelisted(extension_id)) return; if (!testing_mode_ && arg_whitelist_api_.find(api_call) == arg_whitelist_api_.end()) args->Clear(); @@ -233,7 +225,8 @@ void ActivityLog::LogBlockedAction(const std::string& extension_id, ListValue* args, BlockedAction::Reason reason, const std::string& extra) { - if (!IsLogEnabled()) return; + if (!IsLogEnabled() || + ActivityLogAPI::IsExtensionWhitelisted(extension_id)) return; if (!testing_mode_ && arg_whitelist_api_.find(blocked_call) == arg_whitelist_api_.end()) args->Clear(); @@ -244,9 +237,8 @@ void ActivityLog::LogBlockedAction(const std::string& extension_id, reason, extra); ScheduleAndForget(&ActivityDatabase::RecordAction, action); - // TODO(felt) Re-implement Observer notification HERE for the API. - if (log_activity_to_stdout_) - LOG(INFO) << action->PrintForDebug(); + observers_->Notify(&Observer::OnExtensionActivity, action); + if (testing_mode_) LOG(INFO) << action->PrintForDebug(); } void ActivityLog::LogDOMAction(const std::string& extension_id, @@ -256,7 +248,8 @@ void ActivityLog::LogDOMAction(const std::string& extension_id, const ListValue* args, DomActionType::Type call_type, const std::string& extra) { - if (!IsLogEnabled()) return; + if (!IsLogEnabled() || + ActivityLogAPI::IsExtensionWhitelisted(extension_id)) return; if (call_type == DomActionType::METHOD && api_call == "XMLHttpRequest.open") call_type = DomActionType::XHR; scoped_refptr action = new DOMAction( @@ -269,9 +262,8 @@ void ActivityLog::LogDOMAction(const std::string& extension_id, MakeArgList(args), extra); ScheduleAndForget(&ActivityDatabase::RecordAction, action); - // TODO(felt) Re-implement Observer notification HERE for the API. - if (log_activity_to_stdout_) - LOG(INFO) << action->PrintForDebug(); + observers_->Notify(&Observer::OnExtensionActivity, action); + if (testing_mode_) LOG(INFO) << action->PrintForDebug(); } void ActivityLog::LogWebRequestAction(const std::string& extension_id, @@ -280,7 +272,8 @@ void ActivityLog::LogWebRequestAction(const std::string& extension_id, scoped_ptr details, const std::string& extra) { string16 null_title; - if (!IsLogEnabled()) return; + if (!IsLogEnabled() || + ActivityLogAPI::IsExtensionWhitelisted(extension_id)) return; // Strip details of the web request modifications (for privacy reasons), // unless testing is enabled. @@ -305,9 +298,8 @@ void ActivityLog::LogWebRequestAction(const std::string& extension_id, details_string, extra); ScheduleAndForget(&ActivityDatabase::RecordAction, action); - // TODO(felt) Re-implement Observer notification HERE for the API. - if (log_activity_to_stdout_) - LOG(INFO) << action->PrintForDebug(); + observers_->Notify(&Observer::OnExtensionActivity, action); + if (testing_mode_) LOG(INFO) << action->PrintForDebug(); } void ActivityLog::GetActions( @@ -340,7 +332,7 @@ void ActivityLog::OnScriptsExecuted( for (ExecutingScriptsMap::const_iterator it = extension_ids.begin(); it != extension_ids.end(); ++it) { const Extension* extension = extensions->GetByID(it->first); - if (!extension) + if (!extension || ActivityLogAPI::IsExtensionWhitelisted(extension->id())) continue; // If OnScriptsExecuted is fired because of tabs.executeScript, the list diff --git a/chrome/browser/extensions/activity_log/activity_log.h b/chrome/browser/extensions/activity_log/activity_log.h index 2314f0e82d01..763f0d28e64c 100644 --- a/chrome/browser/extensions/activity_log/activity_log.h +++ b/chrome/browser/extensions/activity_log/activity_log.h @@ -38,7 +38,8 @@ class Extension; class ActivityLog : public BrowserContextKeyedService, public TabHelper::ScriptExecutionObserver { public: - // Observers can listen for activity events. + // Observers can listen for activity events. There is probably only one + // observer: the activityLogPrivate API. class Observer { public: virtual void OnExtensionActivity(scoped_refptr activity) = 0; @@ -57,7 +58,8 @@ class ActivityLog : public BrowserContextKeyedService, // really intended for use by unit tests. static void RecomputeLoggingIsEnabled(); - // Add/remove observer. + // Add/remove observer: the activityLogPrivate API only listens when the + // ActivityLog extension is registered for an event. void AddObserver(Observer* observer); void RemoveObserver(Observer* observer); @@ -170,6 +172,7 @@ class ActivityLog : public BrowserContextKeyedService, } typedef ObserverListThreadSafe ObserverList; + scoped_refptr observers_; // The database wrapper that does the actual database I/O. // We initialize this on the same thread as the ActivityLog, but then @@ -182,14 +185,11 @@ class ActivityLog : public BrowserContextKeyedService, // we dispatch to the UI thread. BrowserThread::ID dispatch_thread_; - // Whether to log activity to stdout or the UI. These are set by switches. - bool log_activity_to_stdout_; - bool log_activity_to_ui_; - // testing_mode_ controls whether to log API call arguments. By default, we // don't log most arguments to avoid saving too much data. In testing mode, // argument collection is enabled. We also whitelist some arguments for // collection regardless of whether this bool is true. + // When testing_mode_ is enabled, we also print to the console. bool testing_mode_; base::hash_set arg_whitelist_api_; diff --git a/chrome/browser/extensions/activity_log/activity_log_browsertest.cc b/chrome/browser/extensions/activity_log/activity_log_browsertest.cc index f0cfeb30170c..196ed3110602 100644 --- a/chrome/browser/extensions/activity_log/activity_log_browsertest.cc +++ b/chrome/browser/extensions/activity_log/activity_log_browsertest.cc @@ -26,6 +26,7 @@ class ActivityLogExtensionTest : public ExtensionApiTest { virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { ExtensionBrowserTest::SetUpCommandLine(command_line); command_line->AppendSwitch(switches::kEnableExtensionActivityLogging); + command_line->AppendSwitch(switches::kEnableExtensionActivityLogTesting); } }; diff --git a/chrome/browser/extensions/activity_log/activity_log_unittest.cc b/chrome/browser/extensions/activity_log/activity_log_unittest.cc index 15f00a12822c..e8a3492234d0 100644 --- a/chrome/browser/extensions/activity_log/activity_log_unittest.cc +++ b/chrome/browser/extensions/activity_log/activity_log_unittest.cc @@ -76,7 +76,7 @@ class ActivityLogTest : public testing::Test { scoped_refptr last = i->front(); std::string id(kExtensionId); std::string noargs = "ID: " + id + ", CATEGORY: " - "CALL, API: tabs.testMethod, ARGS: "; + "call, API: tabs.testMethod, ARGS: "; ASSERT_EQ(noargs, last->PrintForDebug()); } @@ -85,7 +85,7 @@ class ActivityLogTest : public testing::Test { scoped_refptr last = i->front(); std::string id(kExtensionId); std::string args = "ID: " + id + ", CATEGORY: " - "CALL, API: extension.connect, ARGS: \"hello\", \"world\""; + "call, API: extension.connect, ARGS: \"hello\", \"world\""; ASSERT_EQ(args, last->PrintForDebug()); } diff --git a/chrome/browser/extensions/activity_log/api_actions.cc b/chrome/browser/extensions/activity_log/api_actions.cc index e982b30d64bf..6a7cb6543214 100644 --- a/chrome/browser/extensions/activity_log/api_actions.cc +++ b/chrome/browser/extensions/activity_log/api_actions.cc @@ -96,6 +96,11 @@ class APINameMap { namespace extensions { +using api::activity_log_private::ExtensionActivity; +using api::activity_log_private::DomActivityDetail; +using api::activity_log_private::ChromeActivityDetail; +using api::activity_log_private::BlockedChromeActivityDetail; + const char* APIAction::kTableName = "activitylog_apis"; const char* APIAction::kTableContentFields[] = {"api_type", "api_call", "args", "extra"}; @@ -115,7 +120,7 @@ APIAction::APIAction(const std::string& extension_id, const std::string& api_call, const std::string& args, const std::string& extra) - : Action(extension_id, time), + : Action(extension_id, time, ExtensionActivity::ACTIVITY_TYPE_CHROME), type_(type), api_call_(api_call), args_(args), @@ -123,7 +128,8 @@ APIAction::APIAction(const std::string& extension_id, APIAction::APIAction(const sql::Statement& s) : Action(s.ColumnString(0), - base::Time::FromInternalValue(s.ColumnInt64(1))), + base::Time::FromInternalValue(s.ColumnInt64(1)), + ExtensionActivity::ACTIVITY_TYPE_CHROME), type_(static_cast(s.ColumnInt(2))), api_call_(APINameMap::GetInstance()->ShortnameToApi(s.ColumnString(3))), args_(s.ColumnString(4)), @@ -132,6 +138,23 @@ APIAction::APIAction(const sql::Statement& s) APIAction::~APIAction() { } +scoped_ptr APIAction::ConvertToExtensionActivity() { + scoped_ptr formatted_activity; + formatted_activity.reset(new ExtensionActivity); + formatted_activity->extension_id.reset( + new std::string(extension_id())); + formatted_activity->activity_type = activity_type(); + formatted_activity->time.reset(new double(time().ToJsTime())); + ChromeActivityDetail* details = new ChromeActivityDetail; + details->api_activity_type = ChromeActivityDetail::ParseApiActivityType( + TypeAsString()); + details->api_call.reset(new std::string(api_call_)); + details->args.reset(new std::string(args_)); + details->extra.reset(new std::string(extra_)); + formatted_activity->chrome_activity_detail.reset(details); + return formatted_activity.Pass(); +} + // static bool APIAction::InitializeTable(sql::Connection* db) { // The original table schema was different than the existing one. @@ -229,11 +252,11 @@ std::string APIAction::PrintForDebug() { std::string APIAction::TypeAsString() const { switch (type_) { case CALL: - return "CALL"; + return "call"; case EVENT_CALLBACK: - return "EVENT_CALLBACK"; + return "event_callback"; default: - return "UNKNOWN_TYPE"; + return "unknown_type"; } } diff --git a/chrome/browser/extensions/activity_log/api_actions.h b/chrome/browser/extensions/activity_log/api_actions.h index 3744f52380af..1269444e20de 100644 --- a/chrome/browser/extensions/activity_log/api_actions.h +++ b/chrome/browser/extensions/activity_log/api_actions.h @@ -47,6 +47,9 @@ class APIAction : public Action { // Record the action in the database. virtual void Record(sql::Connection* db) OVERRIDE; + virtual scoped_ptr + ConvertToExtensionActivity() OVERRIDE; + // Used to associate tab IDs with URLs. It will swap out the int in args with // a URL as a string. If the tab is in incognito mode, we leave it alone as // the original int. There is a small chance that the URL translation could diff --git a/chrome/browser/extensions/activity_log/blocked_actions.cc b/chrome/browser/extensions/activity_log/blocked_actions.cc index b7732991b8e6..aa35f70041c2 100644 --- a/chrome/browser/extensions/activity_log/blocked_actions.cc +++ b/chrome/browser/extensions/activity_log/blocked_actions.cc @@ -11,6 +11,11 @@ using content::BrowserThread; namespace extensions { +using api::activity_log_private::ExtensionActivity; +using api::activity_log_private::DomActivityDetail; +using api::activity_log_private::ChromeActivityDetail; +using api::activity_log_private::BlockedChromeActivityDetail; + const char* BlockedAction::kTableName = "activitylog_blocked"; const char* BlockedAction::kTableContentFields[] = {"api_call", "args", "reason", "extra"}; @@ -23,7 +28,9 @@ BlockedAction::BlockedAction(const std::string& extension_id, const std::string& args, const BlockedAction::Reason reason, const std::string& extra) - : Action(extension_id, time), + : Action(extension_id, + time, + ExtensionActivity::ACTIVITY_TYPE_BLOCKED_CHROME), api_call_(api_call), args_(args), reason_(reason), @@ -31,7 +38,8 @@ BlockedAction::BlockedAction(const std::string& extension_id, BlockedAction::BlockedAction(const sql::Statement& s) : Action(s.ColumnString(0), - base::Time::FromInternalValue(s.ColumnInt64(1))), + base::Time::FromInternalValue(s.ColumnInt64(1)), + ExtensionActivity::ACTIVITY_TYPE_BLOCKED_CHROME), api_call_(s.ColumnString(2)), args_(s.ColumnString(3)), reason_(static_cast(s.ColumnInt(4))), @@ -40,6 +48,23 @@ BlockedAction::BlockedAction(const sql::Statement& s) BlockedAction::~BlockedAction() { } +scoped_ptr BlockedAction::ConvertToExtensionActivity() { + scoped_ptr formatted_activity; + formatted_activity.reset(new ExtensionActivity); + formatted_activity->extension_id.reset( + new std::string(extension_id())); + formatted_activity->activity_type = activity_type(); + formatted_activity->time.reset(new double(time().ToJsTime())); + BlockedChromeActivityDetail* details = new BlockedChromeActivityDetail; + details->api_call.reset(new std::string(api_call_)); + details->args.reset(new std::string(args_)); + details->reason = BlockedChromeActivityDetail::ParseReason( + ReasonAsString()); + details->extra.reset(new std::string(extra_)); + formatted_activity->blocked_chrome_activity_detail.reset(details); + return formatted_activity.Pass(); +} + // static bool BlockedAction::InitializeTable(sql::Connection* db) { // The original table schema was different than the existing one. @@ -93,11 +118,11 @@ std::string BlockedAction::PrintForDebug() { std::string BlockedAction::ReasonAsString() const { if (reason_ == ACCESS_DENIED) - return std::string("access denied"); + return std::string("access_denied"); else if (reason_ == QUOTA_EXCEEDED) - return std::string("quota exceeded"); + return std::string("quota_exceeded"); else - return std::string("unknown"); + return std::string("unknown_reason_type"); // To avoid Win header name. } } // namespace extensions diff --git a/chrome/browser/extensions/activity_log/blocked_actions.h b/chrome/browser/extensions/activity_log/blocked_actions.h index 930b70a5ac4d..76abe95ef940 100644 --- a/chrome/browser/extensions/activity_log/blocked_actions.h +++ b/chrome/browser/extensions/activity_log/blocked_actions.h @@ -43,6 +43,9 @@ class BlockedAction : public Action { // Record the action in the database. virtual void Record(sql::Connection* db) OVERRIDE; + virtual scoped_ptr + ConvertToExtensionActivity() OVERRIDE; + // Print a BlockedAction as a string for debugging purposes. virtual std::string PrintForDebug() OVERRIDE; diff --git a/chrome/browser/extensions/activity_log/dom_actions.cc b/chrome/browser/extensions/activity_log/dom_actions.cc index d4312701812e..ea2486cdb26c 100644 --- a/chrome/browser/extensions/activity_log/dom_actions.cc +++ b/chrome/browser/extensions/activity_log/dom_actions.cc @@ -13,6 +13,11 @@ using content::BrowserThread; namespace extensions { +using api::activity_log_private::ExtensionActivity; +using api::activity_log_private::DomActivityDetail; +using api::activity_log_private::ChromeActivityDetail; +using api::activity_log_private::BlockedChromeActivityDetail; + const char* DOMAction::kTableName = "activitylog_urls"; const char* DOMAction::kTableContentFields[] = {"url_action_type", "url", "url_title", "api_call", "args", "extra"}; @@ -28,7 +33,7 @@ DOMAction::DOMAction(const std::string& extension_id, const std::string& api_call, const std::string& args, const std::string& extra) - : Action(extension_id, time), + : Action(extension_id, time, ExtensionActivity::ACTIVITY_TYPE_DOM), verb_(verb), url_(url), url_title_(url_title), @@ -38,7 +43,8 @@ DOMAction::DOMAction(const std::string& extension_id, DOMAction::DOMAction(const sql::Statement& s) : Action(s.ColumnString(0), - base::Time::FromInternalValue(s.ColumnInt64(1))), + base::Time::FromInternalValue(s.ColumnInt64(1)), + ExtensionActivity::ACTIVITY_TYPE_DOM), verb_(static_cast(s.ColumnInt(2))), url_(GURL(s.ColumnString(3))), url_title_(s.ColumnString16(4)), @@ -49,6 +55,25 @@ DOMAction::DOMAction(const sql::Statement& s) DOMAction::~DOMAction() { } +scoped_ptr DOMAction::ConvertToExtensionActivity() { + scoped_ptr formatted_activity; + formatted_activity.reset(new ExtensionActivity); + formatted_activity->extension_id.reset( + new std::string(extension_id())); + formatted_activity->activity_type = activity_type(); + formatted_activity->time.reset(new double(time().ToJsTime())); + DomActivityDetail* details = new DomActivityDetail; + details->dom_activity_type = DomActivityDetail::ParseDomActivityType( + VerbAsString()); + details->url.reset(new std::string(url_.spec())); + details->url_title.reset(new std::string(base::UTF16ToUTF8(url_title_))); + details->api_call.reset(new std::string(api_call_)); + details->args.reset(new std::string(args_)); + details->extra.reset(new std::string(extra_)); + formatted_activity->dom_activity_detail.reset(details); + return formatted_activity.Pass(); +} + // static bool DOMAction::InitializeTable(sql::Connection* db) { // The original table schema was different than the existing one. @@ -111,19 +136,19 @@ std::string DOMAction::PrintForDebug() { std::string DOMAction::VerbAsString() const { switch (verb_) { case DomActionType::GETTER: - return "GETTER"; + return "getter"; case DomActionType::SETTER: - return "SETTER"; + return "setter"; case DomActionType::METHOD: - return "METHOD"; + return "method"; case DomActionType::INSERTED: - return "INSERTED"; + return "inserted"; case DomActionType::XHR: - return "XHR"; + return "xhr"; case DomActionType::WEBREQUEST: - return "WEBREQUEST"; + return "webrequest"; case DomActionType::MODIFIED: // legacy - return "MODIFIED"; + return "modified"; default: NOTREACHED(); return NULL; diff --git a/chrome/browser/extensions/activity_log/dom_actions.h b/chrome/browser/extensions/activity_log/dom_actions.h index c430803e97ea..4b4825d8bb8b 100644 --- a/chrome/browser/extensions/activity_log/dom_actions.h +++ b/chrome/browser/extensions/activity_log/dom_actions.h @@ -41,6 +41,9 @@ class DOMAction : public Action { // Create a new DOMAction from a database row. explicit DOMAction(const sql::Statement& s); + virtual scoped_ptr + ConvertToExtensionActivity() OVERRIDE; + // Record the action in the database. virtual void Record(sql::Connection* db) OVERRIDE; diff --git a/chrome/browser/extensions/api/activity_log_private/activity_log_private_api.cc b/chrome/browser/extensions/api/activity_log_private/activity_log_private_api.cc new file mode 100644 index 000000000000..7035417d5425 --- /dev/null +++ b/chrome/browser/extensions/api/activity_log_private/activity_log_private_api.cc @@ -0,0 +1,96 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/extensions/api/activity_log_private/activity_log_private_api.h" + +#include "base/lazy_instance.h" +#include "base/prefs/pref_service.h" +#include "base/strings/string_number_conversions.h" +#include "base/values.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/extensions/event_router_forwarder.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/extensions/extension_system.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/common/extensions/api/activity_log_private.h" +#include "chrome/common/pref_names.h" + +namespace extensions { + +using api::activity_log_private::ExtensionActivity; + +const char kActivityLogExtensionId[] = "acldcpdepobcjbdanifkmfndkjoilgba"; +const char kActivityLogTestExtensionId[] = "ajabfgledjhbabeoojlabelaifmakodf"; +const char kNewActivityEventName[] = "activityLogPrivate.onExtensionActivity"; + +static base::LazyInstance > + g_factory = LAZY_INSTANCE_INITIALIZER; + +// static +ProfileKeyedAPIFactory* ActivityLogAPI::GetFactoryInstance() { + return &g_factory.Get(); +} + +template<> +void ProfileKeyedAPIFactory::DeclareFactoryDependencies() { + DependsOn(ExtensionSystemFactory::GetInstance()); + DependsOn(ActivityLogFactory::GetInstance()); +} + +ActivityLogAPI::ActivityLogAPI(Profile* profile) + : profile_(profile), + initialized_(false) { + if (!ExtensionSystem::Get(profile_)->event_router()) { // Check for testing. + LOG(ERROR) << "ExtensionSystem event_router does not exist."; + return; + } + activity_log_ = extensions::ActivityLog::GetInstance(profile_); + DCHECK(activity_log_); + ExtensionSystem::Get(profile_)->event_router()->RegisterObserver( + this, kNewActivityEventName); + activity_log_->AddObserver(this); + initialized_ = true; +} + +ActivityLogAPI::~ActivityLogAPI() { +} + +void ActivityLogAPI::Shutdown() { + if (!initialized_) { // Check for testing. + LOG(ERROR) << "ExtensionSystem event_router does not exist."; + return; + } + ExtensionSystem::Get(profile_)->event_router()->UnregisterObserver(this); + activity_log_->RemoveObserver(this); +} + +// static +bool ActivityLogAPI::IsExtensionWhitelisted(const std::string& extension_id) { + return (extension_id == kActivityLogExtensionId || + extension_id == kActivityLogTestExtensionId); +} + +void ActivityLogAPI::OnListenerAdded(const EventListenerInfo& details) { + // TODO(felt): Only observe activity_log_ events when we have a customer. +} + +void ActivityLogAPI::OnListenerRemoved(const EventListenerInfo& details) { + // TODO(felt): Only observe activity_log_ events when we have a customer. +} + +void ActivityLogAPI::OnExtensionActivity(scoped_refptr activity) { + scoped_ptr value(new base::ListValue()); + scoped_ptr activity_arg = + activity->ConvertToExtensionActivity(); + value->Append(activity_arg->ToValue().release()); + scoped_ptr event(new Event(kNewActivityEventName, value.Pass())); + event->restrict_to_profile = profile_; + ExtensionSystem::Get(profile_)->event_router()->BroadcastEvent(event.Pass()); +} + +bool ActivityLogPrivateGetExtensionActivitiesFunction::RunImpl() { + return true; +} + +} // namespace extensions diff --git a/chrome/browser/extensions/api/activity_log_private/activity_log_private_api.h b/chrome/browser/extensions/api/activity_log_private/activity_log_private_api.h new file mode 100644 index 000000000000..ea74c1fd3771 --- /dev/null +++ b/chrome/browser/extensions/api/activity_log_private/activity_log_private_api.h @@ -0,0 +1,84 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This extension API provides access to the Activity Log, which is a +// monitoring framework for extension behavior. Only specific Google-produced +// extensions should have access to it. + +#ifndef CHROME_BROWSER_EXTENSIONS_API_ACTIVITY_LOG_PRIVATE_ACTIVITY_LOG_PRIVATE_API_H_ +#define CHROME_BROWSER_EXTENSIONS_API_ACTIVITY_LOG_PRIVATE_ACTIVITY_LOG_PRIVATE_API_H_ + +#include "base/synchronization/lock.h" +#include "chrome/browser/extensions/activity_log/activity_actions.h" +#include "chrome/browser/extensions/activity_log/activity_log.h" +#include "chrome/browser/extensions/api/activity_log_private/activity_log_private_api.h" +#include "chrome/browser/extensions/api/profile_keyed_api_factory.h" +#include "chrome/browser/extensions/event_router.h" +#include "chrome/browser/extensions/extension_function.h" + +namespace extensions { + +class ActivityLog; + +// The ID of the trusted/whitelisted ActivityLog extension. +extern const char kActivityLogExtensionId[]; +extern const char kActivityLogTestExtensionId[]; +extern const char kNewActivityEventName[]; + +// Handles interactions between the Activity Log API and implementation. +class ActivityLogAPI : public ProfileKeyedAPI, + public extensions::ActivityLog::Observer, + public EventRouter::Observer { + public: + explicit ActivityLogAPI(Profile* profile); + virtual ~ActivityLogAPI(); + + // ProfileKeyedAPI implementation. + static ProfileKeyedAPIFactory* GetFactoryInstance(); + + virtual void Shutdown() OVERRIDE; + + // Lookup whether the extension ID is whitelisted. + static bool IsExtensionWhitelisted(const std::string& extension_id); + + private: + friend class ProfileKeyedAPIFactory; + static const char* service_name() { return "ActivityLogPrivateAPI"; } + + // ActivityLog::Observer + // We pass this along to activityLogPrivate.onExtensionActivity. + virtual void OnExtensionActivity(scoped_refptr activity) OVERRIDE; + + // EventRouter::Observer + // We only keep track of OnExtensionActivity if we have any listeners. + virtual void OnListenerAdded(const EventListenerInfo& details) OVERRIDE; + virtual void OnListenerRemoved(const EventListenerInfo& details) OVERRIDE; + + Profile* profile_; + ActivityLog* activity_log_; + bool initialized_; + + DISALLOW_COPY_AND_ASSIGN(ActivityLogAPI); +}; + +template<> +void ProfileKeyedAPIFactory::DeclareFactoryDependencies(); + +// The implementation of activityLogPrivate.getExtensionActivities +class ActivityLogPrivateGetExtensionActivitiesFunction + : public AsyncExtensionFunction { + public: + DECLARE_EXTENSION_FUNCTION("activityLogPrivate.getExtensionActivities", + ACTIVITYLOGPRIVATE_GETEXTENSIONACTIVITIES) + + protected: + virtual ~ActivityLogPrivateGetExtensionActivitiesFunction() {} + + // ExtensionFunction: + virtual bool RunImpl() OVERRIDE; +}; + +} // namespace extensions + +#endif // CHROME_BROWSER_EXTENSIONS_API_ACTIVITY_LOG_PRIVATE_ACTIVITY_LOG_PRIVATE_API_H_ diff --git a/chrome/browser/extensions/api/activity_log_private/activity_log_private_api_unittest.cc b/chrome/browser/extensions/api/activity_log_private/activity_log_private_api_unittest.cc new file mode 100644 index 000000000000..6aef6142ba53 --- /dev/null +++ b/chrome/browser/extensions/api/activity_log_private/activity_log_private_api_unittest.cc @@ -0,0 +1,105 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/memory/scoped_ptr.h" +#include "base/strings/utf_string_conversions.h" +#include "chrome/browser/extensions/api/activity_log_private/activity_log_private_api.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +const char kExtensionId[] = "extensionid"; +const char kApiCall[] = "api.call"; +const char kArgs[] = "1, 2"; +const char kExtra[] = "extra"; + +} // extensions + +namespace extensions { + +using api::activity_log_private::BlockedChromeActivityDetail; +using api::activity_log_private::ChromeActivityDetail; +using api::activity_log_private::DomActivityDetail; +using api::activity_log_private::ExtensionActivity; + +class ActivityLogApiUnitTest : public testing::Test { +}; + +TEST_F(ActivityLogApiUnitTest, ConvertBlockedAction) { + scoped_refptr action( + new BlockedAction(kExtensionId, + base::Time::Now(), + kApiCall, + kArgs, + BlockedAction::ACCESS_DENIED, + kExtra)); + scoped_ptr result = + action->ConvertToExtensionActivity(); + ASSERT_EQ(ExtensionActivity::ACTIVITY_TYPE_BLOCKED_CHROME, + result->activity_type); + ASSERT_EQ(kExtensionId, *(result->extension_id.get())); + ASSERT_EQ(kApiCall, + *(result->blocked_chrome_activity_detail->api_call.get())); + ASSERT_EQ(kArgs, + *(result->blocked_chrome_activity_detail->args.get())); + ASSERT_EQ(BlockedChromeActivityDetail::REASON_ACCESS_DENIED, + result->blocked_chrome_activity_detail->reason); + ASSERT_EQ(kExtra, + *(result->blocked_chrome_activity_detail->extra.get())); +} + +TEST_F(ActivityLogApiUnitTest, ConvertChromeApiAction) { + scoped_refptr action( + new APIAction(kExtensionId, + base::Time::Now(), + APIAction::CALL, + kApiCall, + kArgs, + kExtra)); + scoped_ptr result = + action->ConvertToExtensionActivity(); + ASSERT_EQ(ExtensionActivity::ACTIVITY_TYPE_CHROME, + result->activity_type); + ASSERT_EQ(kExtensionId, *(result->extension_id.get())); + ASSERT_EQ(ChromeActivityDetail::API_ACTIVITY_TYPE_CALL, + result->chrome_activity_detail->api_activity_type); + ASSERT_EQ(kApiCall, + *(result->chrome_activity_detail->api_call.get())); + ASSERT_EQ(kArgs, + *(result->chrome_activity_detail->args.get())); + ASSERT_EQ(kExtra, + *(result->chrome_activity_detail->extra.get())); +} + +TEST_F(ActivityLogApiUnitTest, ConvertDomAction) { + scoped_refptr action( + new DOMAction(kExtensionId, + base::Time::Now(), + DomActionType::SETTER, + GURL("http://www.google.com"), + base::ASCIIToUTF16("Title"), + kApiCall, + kArgs, + kExtra)); + scoped_ptr result = + action->ConvertToExtensionActivity(); + ASSERT_EQ(ExtensionActivity::ACTIVITY_TYPE_DOM, result->activity_type); + ASSERT_EQ(kExtensionId, *(result->extension_id.get())); + ASSERT_EQ(DomActivityDetail::DOM_ACTIVITY_TYPE_SETTER, + result->dom_activity_detail->dom_activity_type); + ASSERT_EQ("http://www.google.com/", + *(result->dom_activity_detail->url.get())); + ASSERT_EQ("Title", *(result->dom_activity_detail->url_title.get())); + ASSERT_EQ(kApiCall, + *(result->dom_activity_detail->api_call.get())); + ASSERT_EQ(kArgs, + *(result->dom_activity_detail->args.get())); + ASSERT_EQ(kExtra, + *(result->dom_activity_detail->extra.get())); +} + +} // extensions + diff --git a/chrome/browser/extensions/api/activity_log_private/activity_log_private_apitest.cc b/chrome/browser/extensions/api/activity_log_private/activity_log_private_apitest.cc new file mode 100644 index 000000000000..7e5d77e5809a --- /dev/null +++ b/chrome/browser/extensions/api/activity_log_private/activity_log_private_apitest.cc @@ -0,0 +1,35 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "chrome/browser/extensions/activity_log/activity_log.h" +#include "chrome/browser/extensions/extension_apitest.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/extensions/extension_builder.h" + +namespace extensions { + +class ActivityLogApiTest : public ExtensionApiTest { + public: + ActivityLogApiTest() {} + + virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { + ExtensionApiTest::SetUpCommandLine(command_line); + command_line->AppendSwitch(switches::kEnableExtensionActivityLogging); + command_line->AppendSwitch(switches::kEnableExtensionActivityLogTesting); + } +}; + +// The test extension sends a message to its 'friend'. The test completes +// if it successfully sees the 'friend' receive the message. +IN_PROC_BROWSER_TEST_F(ActivityLogApiTest, TriggerEvent) { + const Extension* friend_extension = + LoadExtension(test_data_dir_.AppendASCII("activity_log_private/friend")); + ASSERT_TRUE(friend_extension); + ASSERT_TRUE(RunExtensionTest("activity_log_private/test")); +} + +} // namespace extensions + diff --git a/chrome/browser/extensions/extension_function_histogram_value.h b/chrome/browser/extensions/extension_function_histogram_value.h index 3a6723133468..3b7687a72ebc 100644 --- a/chrome/browser/extensions/extension_function_histogram_value.h +++ b/chrome/browser/extensions/extension_function_histogram_value.h @@ -546,6 +546,7 @@ enum HistogramValue { EXPERIMENTAL_SYSTEMINFO_STORAGE_GETALLWATCH, EXPERIMENTAL_SYSTEMINFO_STORAGE_REMOVEALLWATCH, SYSTEMINFO_MEMORY_GET, + ACTIVITYLOGPRIVATE_GETEXTENSIONACTIVITIES, ENUM_BOUNDARY // Last entry: Add new entries above. }; diff --git a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc index 41c5ae86364c..41c3929fb631 100644 --- a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc +++ b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc @@ -17,6 +17,7 @@ #include "chrome/browser/custom_handlers/protocol_handler_registry_factory.h" #include "chrome/browser/download/download_service_factory.h" #include "chrome/browser/extensions/activity_log/activity_log.h" +#include "chrome/browser/extensions/api/activity_log_private/activity_log_private_api.h" #include "chrome/browser/extensions/api/alarms/alarm_manager.h" #include "chrome/browser/extensions/api/audio/audio_api.h" #include "chrome/browser/extensions/api/bluetooth/bluetooth_api_factory.h" @@ -183,6 +184,7 @@ EnsureBrowserContextKeyedServiceFactoriesBuilt() { apps::ShortcutManagerFactory::GetInstance(); autofill::autocheckout::WhitelistManagerFactory::GetInstance(); extensions::ActivityLogFactory::GetInstance(); + extensions::ActivityLogAPI::GetFactoryInstance(); extensions::AlarmManager::GetFactoryInstance(); extensions::AudioAPI::GetFactoryInstance(); extensions::BookmarksAPI::GetFactoryInstance(); diff --git a/chrome/chrome_browser_extensions.gypi b/chrome/chrome_browser_extensions.gypi index cec7b76c1748..c7e2a0035187 100644 --- a/chrome/chrome_browser_extensions.gypi +++ b/chrome/chrome_browser_extensions.gypi @@ -77,6 +77,8 @@ 'browser/extensions/activity_log/web_request_constants.h', 'browser/extensions/admin_policy.cc', 'browser/extensions/admin_policy.h', + 'browser/extensions/api/activity_log_private/activity_log_private_api.cc', + 'browser/extensions/api/activity_log_private/activity_log_private_api.h', 'browser/extensions/api/api_function.cc', 'browser/extensions/api/api_function.h', 'browser/extensions/api/api_resource.cc', @@ -806,6 +808,8 @@ # less intertwined in the main codebase. ['exclude', '^browser/extensions/api/'], ['exclude', '^browser/extensions/.*_api\.cc$'], + ['include', '^browser/extensions/api/activity_log_private/activity_log_private_api.cc'], + ['include', '^browser/extensions/api/activity_log_private/activity_log_private_api.h'], ['include', '^browser/extensions/api/alarms/alarm_manager.cc'], ['include', '^browser/extensions/api/content_settings/content_settings_api_constants.cc'], ['include', '^browser/extensions/api/content_settings/content_settings_helpers.cc'], diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 38b29994a86c..c095aa54c029 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -1285,6 +1285,7 @@ 'browser/extensions/ad_view_browsertest.cc', 'browser/extensions/alert_apitest.cc', 'browser/extensions/all_urls_apitest.cc', + 'browser/extensions/api/activity_log_private/activity_log_private_apitest.cc', 'browser/extensions/api/app_window/app_window_apitest.cc', 'browser/extensions/api/audio/audio_apitest.cc', 'browser/extensions/api/autotest_private/autotest_private_apitest.cc', diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi index 54aaaf3c998d..ff6281e313a2 100644 --- a/chrome/chrome_tests_unit.gypi +++ b/chrome/chrome_tests_unit.gypi @@ -711,6 +711,7 @@ 'browser/extensions/activity_log/activity_database_unittest.cc', 'browser/extensions/activity_log/activity_log_unittest.cc', 'browser/extensions/admin_policy_unittest.cc', + 'browser/extensions/api/activity_log_private/activity_log_private_api_unittest.cc', 'browser/extensions/api/alarms/alarms_api_unittest.cc', 'browser/extensions/api/api_resource_manager_unittest.cc', 'browser/extensions/api/bluetooth/bluetooth_event_router_unittest.cc', @@ -1924,8 +1925,7 @@ ['enable_extensions==0', { 'sources/': [ ['exclude', '^../extensions/'], - ['exclude', '^browser/extensions/activity_database_unittest.cc'], - ['exclude', '^browser/extensions/activity_log_unittest.cc'], + ['exclude', '^browser/extensions/activity_log/'], ['exclude', '^browser/extensions/api/'], ['exclude', '^browser/sync/glue/chrome_extensions_activity_monitor_unittest.cc'], ['exclude', '^common/extensions/api/'], diff --git a/chrome/common/extensions/api/_permission_features.json b/chrome/common/extensions/api/_permission_features.json index 81b032f1b108..ca9395888dfa 100644 --- a/chrome/common/extensions/api/_permission_features.json +++ b/chrome/common/extensions/api/_permission_features.json @@ -8,6 +8,14 @@ "extension_types": ["extension", "packaged_app"], "min_manifest_version": 2 }, + "activityLogPrivate": { + "channel": "dev", + "extension_types": ["extension"], + "whitelist": [ + "acldcpdepobcjbdanifkmfndkjoilgba", // Activity Log + "ajabfgledjhbabeoojlabelaifmakodf" // For testing + ] + }, "adview": { "channel": "dev", "extension_types": ["platform_app"] diff --git a/chrome/common/extensions/api/activity_log_private.json b/chrome/common/extensions/api/activity_log_private.json new file mode 100644 index 000000000000..6336de2d0974 --- /dev/null +++ b/chrome/common/extensions/api/activity_log_private.json @@ -0,0 +1,125 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +[ + { + "namespace": "activityLogPrivate", + "types": [ + { + "id": "DomActivityDetail", + "type": "object", + "description": "Stores the fields associated with an ActivityLog DomAction.", + "properties": { + "domActivityType": {"type": "string", "enum": ["getter", "setter", "method", "inserted", "xhr", "webrequest", "modified"], "optional": true}, + "url": {"type": "string", "optional": true}, + "urlTitle": {"type": "string", "optional": true}, + "apiCall": {"type": "string", "optional": true}, + "args": {"type": "string", "optional": true}, + "extra": {"type": "string", "optional": true} + } + }, + { + "id": "ChromeActivityDetail", + "type": "object", + "description": "Stores the fields associated with an ActivityLog ApiAction.", + "properties": { + "apiActivityType": {"type": "string", "enum": ["call", "event_callback", "unknown_type"], "optional": true}, + "apiCall": {"type": "string", "optional": true}, + "args": {"type": "string", "optional": true}, + "extra": {"type": "string", "optional": true} + } + }, + { + "id": "BlockedChromeActivityDetail", + "type": "object", + "description": "Stores the fields associated with an ActivityLog BlockedAction.", + "properties": { + "apiCall": {"type": "string", "optional": true}, + "args": {"type": "string", "optional": true}, + "reason": {"type": "string", "enum": ["unknown_reason_type", "access_denied", "quota_exceeded"], "optional": true}, + "extra": {"type": "string", "optional": true} + } + }, + { + "id": "ActivityFilter", + "type": "object", + "description": "The result set will be limited to rows that match the specification. All matches will be exact except for the URL field of domActivityDetail, which is considered a prefix.", + "properties": { + "extensionId": {"type": "string", "optional": true}, + "daysAgo": {"type": "string", "optional": true}, + "activityType": {"type": "string", "enum": ["dom", "chrome", "blocked_chrome"], "optional": true}, + "domActivityDetail": {"$ref": "DomActivityDetail", "optional": true}, + "chromeActivityDetail": {"$ref": "ChromeActivityDetail", "optional": true}, + "blockedChromeActivityDetail": {"$ref": "BlockedChromeActivityDetail", "optional": true} + } + }, + { + "id": "ExtensionActivity", + "type": "object", + "description": "This corresponds to a row from the ActivityLog database. Fields will be blank if they were specified precisely in a lookup filter.", + "properties": { + "extensionId": {"type": "string", "optional": true}, + "time": {"type": "number", "optional": true}, + "count": {"type": "integer", "optional": true}, + "activityType": {"type": "string", "enum": ["dom", "chrome", "blocked_chrome"], "optional": true}, + "domActivityDetail": {"$ref": "DomActivityDetail", "optional": true}, + "chromeActivityDetail": {"$ref": "ChromeActivityDetail", "optional": true}, + "blockedChromeActivityDetail": {"$ref": "BlockedChromeActivityDetail", "optional": true} + } + }, + { + "id": "ActivityResultSet", + "type": "object", + "description": "This holds the results of a lookup, the filter of the lookup, and the page of the lookup if there is more than one page of results.", + "properties": { + "result": {"type": "array", "items": {"$ref": "ExtensionActivity"}}, + "filter": {"$ref": "ActivityFilter"}, + "page": {"type": "integer", "optional": true} + } + } + ], + "functions": [ + { + "name": "getExtensionActivities", + "type": "function", + "description": "Retrieves activity from the ActivityLog that matches the specified filter.", + "parameters": [ + { + "name": "filter", + "$ref": "ActivityFilter" + }, + { + "name": "page", + "type": "integer", + "optional": true, + "description": "Specify this if you want a specific page of results." + }, + { + "name": "callback", + "type": "function", + "parameters": [ + { + "name": "result", + "$ref": "ActivityResultSet" + } + ] + } + ] + } + ], + "events": [ + { + "name": "onExtensionActivity", + "type": "function", + "description": "Fired when a given extension performs another activity.", + "parameters": [ + { + "name": "activity", + "$ref": "ExtensionActivity" + } + ] + } + ] + } +] diff --git a/chrome/common/extensions/api/api.gyp b/chrome/common/extensions/api/api.gyp index b5b0f047ac99..7317cd7f297c 100644 --- a/chrome/common/extensions/api/api.gyp +++ b/chrome/common/extensions/api/api.gyp @@ -20,6 +20,7 @@ 'chromium_code': 1, 'schema_files': [ 'alarms.idl', + 'activity_log_private.json', 'app_current_window_internal.idl', 'app_runtime.idl', 'app_window.idl', diff --git a/chrome/common/extensions/permissions/api_permission.h b/chrome/common/extensions/permissions/api_permission.h index d237be157843..207b4c180df1 100644 --- a/chrome/common/extensions/permissions/api_permission.h +++ b/chrome/common/extensions/permissions/api_permission.h @@ -36,6 +36,7 @@ class APIPermission { // Real permissions. kActiveTab, + kActivityLogPrivate, kAdView, kAlarms, kAppCurrentWindowInternal, diff --git a/chrome/common/extensions/permissions/chrome_api_permissions.cc b/chrome/common/extensions/permissions/chrome_api_permissions.cc index d1b2f09c6613..2d4111973209 100644 --- a/chrome/common/extensions/permissions/chrome_api_permissions.cc +++ b/chrome/common/extensions/permissions/chrome_api_permissions.cc @@ -128,6 +128,8 @@ std::vector ChromeAPIPermissions::GetAllPermissions() APIPermissionInfo::kFlagCannotBeOptional }, // Register private permissions. + { APIPermission::kActivityLogPrivate, "activityLogPrivate", + APIPermissionInfo::kFlagCannotBeOptional }, { APIPermission::kAutoTestPrivate, "autotestPrivate", APIPermissionInfo::kFlagCannotBeOptional }, { APIPermission::kBookmarkManagerPrivate, "bookmarkManagerPrivate", diff --git a/chrome/common/extensions/permissions/permission_set_unittest.cc b/chrome/common/extensions/permissions/permission_set_unittest.cc index 2844fce28e02..8b7898d6227b 100644 --- a/chrome/common/extensions/permissions/permission_set_unittest.cc +++ b/chrome/common/extensions/permissions/permission_set_unittest.cc @@ -677,6 +677,7 @@ TEST(PermissionsTest, PermissionMessages) { skip.insert(APIPermission::kIdentity); // These are private. + skip.insert(APIPermission::kActivityLogPrivate); skip.insert(APIPermission::kAutoTestPrivate); skip.insert(APIPermission::kBookmarkManagerPrivate); skip.insert(APIPermission::kChromeosInfoPrivate); diff --git a/chrome/test/data/extensions/api_test/activity_log_private/friend/manifest.json b/chrome/test/data/extensions/api_test/activity_log_private/friend/manifest.json new file mode 100644 index 000000000000..a14619f94f19 --- /dev/null +++ b/chrome/test/data/extensions/api_test/activity_log_private/friend/manifest.json @@ -0,0 +1,10 @@ +{ + "manifest_version": 2, + "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+5rT3hvrt4N2WRxqiTCgsVC6DaOos6nJ74rxO0Q5lyq+E82+5sUs8580gXz9aJyVntW7m6KBOIJqCrTv5ZKn+KuBXZm+hA8mjeP+T5KnYkI/9tx4/CUmvH3/OVi8WU27F3L5+Yb+FfxD+ajCcudoCyUxxml6PW/sTrduh08um2wIDAQAB", + "name": "Activity Log API Test HELPER", + "version": "1.0", + "background": { + "scripts": ["reply.js"] + }, + "permissions": ["cookies", "https://www.cnn.com/"] +} \ No newline at end of file diff --git a/chrome/test/data/extensions/api_test/activity_log_private/friend/reply.js b/chrome/test/data/extensions/api_test/activity_log_private/friend/reply.js new file mode 100644 index 000000000000..9249a1f4349c --- /dev/null +++ b/chrome/test/data/extensions/api_test/activity_log_private/friend/reply.js @@ -0,0 +1,9 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +chrome.runtime.onMessageExternal.addListener( + function(message, sender, response) { + console.log("received"); + } +); diff --git a/chrome/test/data/extensions/api_test/activity_log_private/test/manifest.json b/chrome/test/data/extensions/api_test/activity_log_private/test/manifest.json new file mode 100644 index 000000000000..d268279ed69d --- /dev/null +++ b/chrome/test/data/extensions/api_test/activity_log_private/test/manifest.json @@ -0,0 +1,11 @@ +{ + "manifest_version": 2, + "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYiwqZXiIZJruD2iCxBVAHAbOgmw2DJCH7W2kmJyRNGjEAB11vgPJ2MJuqUG/N0e5OjuVyJSNo4nXhAagoKrsZA3xSUDdFfeNP1uyNw6B1NPjo3WeeBIIP8mZXHRRT9di7+xzBrZifmEhgw4dE/Pwn5g3JhUUaUZJ32Bbh9YiTvQIDAQAB", + "name": "Activity Log API Tester", + "description": "Tests chrome.activityLogPrivate.", + "version": "1.0", + "background": { + "scripts": ["test.js"] + }, + "permissions": ["activityLogPrivate"] +} \ No newline at end of file diff --git a/chrome/test/data/extensions/api_test/activity_log_private/test/test.js b/chrome/test/data/extensions/api_test/activity_log_private/test/test.js new file mode 100644 index 000000000000..3119f0cac8f9 --- /dev/null +++ b/chrome/test/data/extensions/api_test/activity_log_private/test/test.js @@ -0,0 +1,22 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +chrome.activityLogPrivate.onExtensionActivity.addListener( + function(activity) { + var activityId = activity["extensionId"]; + chrome.test.assertEq("pknkgggnfecklokoggaggchhaebkajji", activityId); + var apiCall = activity["chromeActivityDetail"]["apiCall"]; + chrome.test.assertEq("runtime.onMessageExternal", apiCall); + chrome.test.succeed(); + } +); + +chrome.test.runTests([ + function triggerAnActivity() { + chrome.runtime.sendMessage("pknkgggnfecklokoggaggchhaebkajji", + "knock knock", + function response() { }); + } +]); + diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml index a0604430e746..d512c0d1ec1d 100644 --- a/tools/metrics/histograms/histograms.xml +++ b/tools/metrics/histograms/histograms.xml @@ -13012,6 +13012,7 @@ other types of suffix sets. +