Commit 9944b6e5 authored by Devlin Cronin's avatar Devlin Cronin Committed by Commit Bot

[Extensions][Metrics] Add and use extension_install.proto

Add and use an extension_install.proto file. This will allow us to
gather more detailed metrics on the types of extensions that users
have installed, and determine the answers to more structured queries
than could be determined by UMA histograms alone.

Add the extension_install.proto and an ExtensionInstallMetricsProvider
to gather these metrics, and hook these up to the metrics service.

Add unittests for the provider.

Bug: 673875
Change-Id: Ic0bdded27cafe3de75f2edeec2e7d927e0e2ecb6
Reviewed-on: https://chromium-review.googlesource.com/621672
Commit-Queue: Devlin <rdevlin.cronin@chromium.org>
Reviewed-by: default avatarKaran Bhatia <karandeepb@chromium.org>
Reviewed-by: default avatarIlya Sherman <isherman@chromium.org>
Reviewed-by: default avatarMark Pearson <mpearson@chromium.org>
Cr-Commit-Position: refs/heads/master@{#505259}
parent 334ba250
...@@ -13,17 +13,30 @@ ...@@ -13,17 +13,30 @@
#include "base/logging.h" #include "base/logging.h"
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "chrome/browser/browser_process.h" #include "chrome/browser/browser_process.h"
#include "chrome/browser/extensions/install_verifier.h" #include "chrome/browser/extensions/install_verifier.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h" #include "chrome/browser/profiles/profile_manager.h"
#include "components/metrics/metrics_log.h" #include "components/metrics/metrics_log.h"
#include "components/metrics/metrics_state_manager.h" #include "components/metrics/metrics_state_manager.h"
#include "components/metrics/proto/system_profile.pb.h" #include "components/metrics/proto/system_profile.pb.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h" #include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h" #include "extensions/browser/extension_system.h"
#include "extensions/common/disable_reason.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_set.h" #include "extensions/common/extension_set.h"
#include "extensions/common/manifest.h"
#include "extensions/common/manifest_constants.h"
#include "extensions/common/manifest_handlers/background_info.h"
#include "extensions/common/manifest_url_handlers.h"
#include "third_party/smhasher/src/City.h" #include "third_party/smhasher/src/City.h"
using extensions::Extension;
using extensions::Manifest;
using metrics::ExtensionInstallProto;
namespace { namespace {
// The number of possible hash keys that a client may use. The UMA client_id // The number of possible hash keys that a client may use. The UMA client_id
...@@ -114,6 +127,219 @@ ExtensionState CheckForOffStore(const extensions::ExtensionSet& extensions, ...@@ -114,6 +127,219 @@ ExtensionState CheckForOffStore(const extensions::ExtensionSet& extensions,
return state; return state;
} }
ExtensionInstallProto::Type GetType(Manifest::Type type) {
switch (type) {
case Manifest::TYPE_UNKNOWN:
return ExtensionInstallProto::UNKNOWN_TYPE;
case Manifest::TYPE_EXTENSION:
return ExtensionInstallProto::EXTENSION;
case Manifest::TYPE_THEME:
return ExtensionInstallProto::THEME;
case Manifest::TYPE_USER_SCRIPT:
return ExtensionInstallProto::USER_SCRIPT;
case Manifest::TYPE_HOSTED_APP:
return ExtensionInstallProto::HOSTED_APP;
case Manifest::TYPE_LEGACY_PACKAGED_APP:
return ExtensionInstallProto::LEGACY_PACKAGED_APP;
case Manifest::TYPE_PLATFORM_APP:
return ExtensionInstallProto::PLATFORM_APP;
case Manifest::TYPE_SHARED_MODULE:
return ExtensionInstallProto::SHARED_MODULE;
case Manifest::NUM_LOAD_TYPES:
NOTREACHED();
// Fall through.
}
return ExtensionInstallProto::UNKNOWN_TYPE;
}
ExtensionInstallProto::InstallLocation GetInstallLocation(
Manifest::Location location) {
switch (location) {
case Manifest::INVALID_LOCATION:
return ExtensionInstallProto::UNKNOWN_LOCATION;
case Manifest::INTERNAL:
return ExtensionInstallProto::INTERNAL;
case Manifest::EXTERNAL_PREF:
return ExtensionInstallProto::EXTERNAL_PREF;
case Manifest::EXTERNAL_REGISTRY:
return ExtensionInstallProto::EXTERNAL_REGISTRY;
case Manifest::UNPACKED:
return ExtensionInstallProto::UNPACKED;
case Manifest::COMPONENT:
return ExtensionInstallProto::COMPONENT;
case Manifest::EXTERNAL_PREF_DOWNLOAD:
return ExtensionInstallProto::EXTERNAL_PREF_DOWNLOAD;
case Manifest::EXTERNAL_POLICY_DOWNLOAD:
return ExtensionInstallProto::EXTERNAL_POLICY_DOWNLOAD;
case Manifest::COMMAND_LINE:
return ExtensionInstallProto::COMMAND_LINE;
case Manifest::EXTERNAL_POLICY:
return ExtensionInstallProto::EXTERNAL_POLICY;
case Manifest::EXTERNAL_COMPONENT:
return ExtensionInstallProto::EXTERNAL_COMPONENT;
case Manifest::NUM_LOCATIONS:
NOTREACHED();
// Fall through.
}
return ExtensionInstallProto::UNKNOWN_LOCATION;
}
ExtensionInstallProto::ActionType GetActionType(const Manifest& manifest) {
// Arbitrary order; each of these is mutually exclusive.
if (manifest.HasKey(extensions::manifest_keys::kBrowserAction))
return ExtensionInstallProto::BROWSER_ACTION;
if (manifest.HasKey(extensions::manifest_keys::kPageAction))
return ExtensionInstallProto::PAGE_ACTION;
if (manifest.HasKey(extensions::manifest_keys::kSystemIndicator))
return ExtensionInstallProto::SYSTEM_INDICATOR;
return ExtensionInstallProto::NO_ACTION;
}
ExtensionInstallProto::BackgroundScriptType GetBackgroundScriptType(
const Extension& extension) {
// Arbitrary order; each of these is mutally exclusive.
if (extensions::BackgroundInfo::HasPersistentBackgroundPage(&extension))
return ExtensionInstallProto::PERSISTENT_BACKGROUND_PAGE;
if (extensions::BackgroundInfo::HasLazyBackgroundPage(&extension))
return ExtensionInstallProto::EVENT_PAGE;
// If an extension had neither a persistent nor lazy background page, it must
// not have a background page.
DCHECK(!extensions::BackgroundInfo::HasBackgroundPage(&extension));
return ExtensionInstallProto::NO_BACKGROUND_SCRIPT;
}
static_assert(extensions::disable_reason::DISABLE_REASON_LAST == (1LL << 17),
"Adding a new disable reason? Be sure to include the new reason "
"below, update the test to exercise it, and then adjust this "
"value for DISABLE_REASON_LAST");
std::vector<ExtensionInstallProto::DisableReason> GetDisableReasons(
const extensions::ExtensionId& id,
extensions::ExtensionPrefs* prefs) {
static struct {
extensions::disable_reason::DisableReason disable_reason;
ExtensionInstallProto::DisableReason proto_disable_reason;
} disable_reason_map[] = {
{extensions::disable_reason::DISABLE_USER_ACTION,
ExtensionInstallProto::USER_ACTION},
{extensions::disable_reason::DISABLE_PERMISSIONS_INCREASE,
ExtensionInstallProto::PERMISSIONS_INCREASE},
{extensions::disable_reason::DISABLE_RELOAD,
ExtensionInstallProto::RELOAD},
{extensions::disable_reason::DISABLE_UNSUPPORTED_REQUIREMENT,
ExtensionInstallProto::UNSUPPORTED_REQUIREMENT},
{extensions::disable_reason::DISABLE_SIDELOAD_WIPEOUT,
ExtensionInstallProto::SIDELOAD_WIPEOUT},
{extensions::disable_reason::DEPRECATED_DISABLE_UNKNOWN_FROM_SYNC,
ExtensionInstallProto::UNKNOWN_FROM_SYNC},
{extensions::disable_reason::DISABLE_NOT_VERIFIED,
ExtensionInstallProto::NOT_VERIFIED},
{extensions::disable_reason::DISABLE_GREYLIST,
ExtensionInstallProto::GREYLIST},
{extensions::disable_reason::DISABLE_CORRUPTED,
ExtensionInstallProto::CORRUPTED},
{extensions::disable_reason::DISABLE_REMOTE_INSTALL,
ExtensionInstallProto::REMOTE_INSTALL},
{extensions::disable_reason::DISABLE_EXTERNAL_EXTENSION,
ExtensionInstallProto::EXTERNAL_EXTENSION},
{extensions::disable_reason::DISABLE_UPDATE_REQUIRED_BY_POLICY,
ExtensionInstallProto::UPDATE_REQUIRED_BY_POLICY},
{extensions::disable_reason::DISABLE_CUSTODIAN_APPROVAL_REQUIRED,
ExtensionInstallProto::CUSTODIAN_APPROVAL_REQUIRED},
{extensions::disable_reason::DISABLE_BLOCKED_BY_POLICY,
ExtensionInstallProto::BLOCKED_BY_POLICY},
};
int disable_reasons = prefs->GetDisableReasons(id);
std::vector<ExtensionInstallProto::DisableReason> reasons;
for (const auto& entry : disable_reason_map) {
int mask = static_cast<int>(entry.disable_reason);
if ((disable_reasons & mask) != 0) {
reasons.push_back(entry.proto_disable_reason);
disable_reasons &= ~mask;
}
}
DCHECK_EQ(extensions::disable_reason::DisableReason::DISABLE_NONE,
disable_reasons);
return reasons;
}
ExtensionInstallProto::BlacklistState GetBlacklistState(
const extensions::ExtensionId& id,
extensions::ExtensionPrefs* prefs) {
extensions::BlacklistState state = prefs->GetExtensionBlacklistState(id);
switch (state) {
case extensions::NOT_BLACKLISTED:
return ExtensionInstallProto::NOT_BLACKLISTED;
case extensions::BLACKLISTED_MALWARE:
return ExtensionInstallProto::BLACKLISTED_MALWARE;
case extensions::BLACKLISTED_SECURITY_VULNERABILITY:
return ExtensionInstallProto::BLACKLISTED_SECURITY_VULNERABILITY;
case extensions::BLACKLISTED_CWS_POLICY_VIOLATION:
return ExtensionInstallProto::BLACKLISTED_CWS_POLICY_VIOLATION;
case extensions::BLACKLISTED_POTENTIALLY_UNWANTED:
return ExtensionInstallProto::BLACKLISTED_POTENTIALLY_UNWANTED;
case extensions::BLACKLISTED_UNKNOWN:
return ExtensionInstallProto::BLACKLISTED_UNKNOWN;
}
NOTREACHED();
return ExtensionInstallProto::BLACKLISTED_UNKNOWN;
}
// Creates the install proto for a given |extension|. |now| is the current
// time, and |time_since_last_sample| is the elapsed time since the previous
// sample was recorded. These are curried in for testing purposes.
metrics::ExtensionInstallProto ConstructInstallProto(
const extensions::Extension& extension,
extensions::ExtensionPrefs* prefs,
base::Time last_sample_time) {
ExtensionInstallProto install;
install.set_type(GetType(extension.manifest()->type()));
install.set_install_location(GetInstallLocation(extension.location()));
install.set_manifest_version(extension.manifest_version());
install.set_action_type(GetActionType(*extension.manifest()));
install.set_has_file_access(
(extension.creation_flags() & Extension::ALLOW_FILE_ACCESS) != 0);
install.set_has_incognito_access(prefs->IsIncognitoEnabled(extension.id()));
install.set_is_from_store(extension.from_webstore());
install.set_updates_from_store(
extensions::ManifestURL::UpdatesFromGallery(&extension));
install.set_is_from_bookmark(extension.from_bookmark());
install.set_is_converted_from_user_script(
extension.converted_from_user_script());
install.set_is_default_installed(extension.was_installed_by_default());
install.set_is_oem_installed(extension.was_installed_by_oem());
install.set_background_script_type(GetBackgroundScriptType(extension));
for (const ExtensionInstallProto::DisableReason reason :
GetDisableReasons(extension.id(), prefs)) {
install.add_disable_reasons(reason);
}
install.set_blacklist_state(GetBlacklistState(extension.id(), prefs));
install.set_installed_in_this_sample_period(
prefs->GetInstallTime(extension.id()) >= last_sample_time);
return install;
}
// Returns all the extension installs for a given |profile|.
std::vector<metrics::ExtensionInstallProto> GetInstallsForProfile(
Profile* profile,
base::Time last_sample_time) {
extensions::ExtensionPrefs* prefs = extensions::ExtensionPrefs::Get(profile);
std::unique_ptr<extensions::ExtensionSet> extensions =
extensions::ExtensionRegistry::Get(profile)
->GenerateInstalledExtensionsSet();
std::vector<ExtensionInstallProto> installs;
installs.reserve(extensions->size());
for (const auto& extension : *extensions) {
installs.push_back(
ConstructInstallProto(*extension, prefs, last_sample_time));
}
return installs;
}
} // namespace } // namespace
ExtensionsMetricsProvider::ExtensionsMetricsProvider( ExtensionsMetricsProvider::ExtensionsMetricsProvider(
...@@ -178,6 +404,24 @@ void ExtensionsMetricsProvider::ProvideSystemProfileMetrics( ...@@ -178,6 +404,24 @@ void ExtensionsMetricsProvider::ProvideSystemProfileMetrics(
metrics::SystemProfileProto* system_profile) { metrics::SystemProfileProto* system_profile) {
ProvideOffStoreMetric(system_profile); ProvideOffStoreMetric(system_profile);
ProvideOccupiedBucketMetric(system_profile); ProvideOccupiedBucketMetric(system_profile);
ProvideExtensionInstallsMetrics(system_profile);
}
// static
metrics::ExtensionInstallProto
ExtensionsMetricsProvider::ConstructInstallProtoForTesting(
const extensions::Extension& extension,
extensions::ExtensionPrefs* prefs,
base::Time last_sample_time) {
return ConstructInstallProto(extension, prefs, last_sample_time);
}
// static
std::vector<metrics::ExtensionInstallProto>
ExtensionsMetricsProvider::GetInstallsForProfileForTesting(
Profile* profile,
base::Time last_sample_time) {
return GetInstallsForProfile(profile, last_sample_time);
} }
void ExtensionsMetricsProvider::ProvideOffStoreMetric( void ExtensionsMetricsProvider::ProvideOffStoreMetric(
...@@ -235,3 +479,16 @@ void ExtensionsMetricsProvider::ProvideOccupiedBucketMetric( ...@@ -235,3 +479,16 @@ void ExtensionsMetricsProvider::ProvideOccupiedBucketMetric(
system_profile->add_occupied_extension_bucket(*it); system_profile->add_occupied_extension_bucket(*it);
} }
} }
void ExtensionsMetricsProvider::ProvideExtensionInstallsMetrics(
metrics::SystemProfileProto* system_profile) {
std::vector<Profile*> profiles =
g_browser_process->profile_manager()->GetLoadedProfiles();
last_sample_time_ = base::Time::Now();
for (Profile* profile : profiles) {
std::vector<ExtensionInstallProto> installs =
GetInstallsForProfile(profile, last_sample_time_);
for (ExtensionInstallProto& install : installs)
system_profile->add_extension_install()->Swap(&install);
}
}
...@@ -9,13 +9,18 @@ ...@@ -9,13 +9,18 @@
#include <memory> #include <memory>
#include <string> #include <string>
#include <vector>
#include "base/macros.h" #include "base/macros.h"
#include "base/time/time.h"
#include "components/metrics/metrics_provider.h" #include "components/metrics/metrics_provider.h"
#include "components/metrics/proto/extension_install.pb.h"
class Profile; class Profile;
namespace extensions { namespace extensions {
class Extension;
class ExtensionPrefs;
class ExtensionSet; class ExtensionSet;
} }
...@@ -39,6 +44,14 @@ class ExtensionsMetricsProvider : public metrics::MetricsProvider { ...@@ -39,6 +44,14 @@ class ExtensionsMetricsProvider : public metrics::MetricsProvider {
void ProvideSystemProfileMetrics( void ProvideSystemProfileMetrics(
metrics::SystemProfileProto* system_profile) override; metrics::SystemProfileProto* system_profile) override;
static metrics::ExtensionInstallProto ConstructInstallProtoForTesting(
const extensions::Extension& extension,
extensions::ExtensionPrefs* prefs,
base::Time last_sample_time);
static std::vector<metrics::ExtensionInstallProto>
GetInstallsForProfileForTesting(Profile* profile,
base::Time last_sample_time);
protected: protected:
// Exposed for the sake of mocking in test code. // Exposed for the sake of mocking in test code.
...@@ -68,6 +81,11 @@ class ExtensionsMetricsProvider : public metrics::MetricsProvider { ...@@ -68,6 +81,11 @@ class ExtensionsMetricsProvider : public metrics::MetricsProvider {
// SystemProfileProto object. // SystemProfileProto object.
void ProvideOccupiedBucketMetric(metrics::SystemProfileProto* system_profile); void ProvideOccupiedBucketMetric(metrics::SystemProfileProto* system_profile);
// Writes information about the installed extensions for all profiles into
// the proto.
void ProvideExtensionInstallsMetrics(
metrics::SystemProfileProto* system_profile);
// The MetricsStateManager from which the client ID is obtained. // The MetricsStateManager from which the client ID is obtained.
metrics::MetricsStateManager* metrics_state_manager_; metrics::MetricsStateManager* metrics_state_manager_;
...@@ -76,6 +94,9 @@ class ExtensionsMetricsProvider : public metrics::MetricsProvider { ...@@ -76,6 +94,9 @@ class ExtensionsMetricsProvider : public metrics::MetricsProvider {
// GetMetricsProfile() can return a consistent value. // GetMetricsProfile() can return a consistent value.
Profile* cached_profile_; Profile* cached_profile_;
// The time of our last recorded sample.
base::Time last_sample_time_;
DISALLOW_COPY_AND_ASSIGN(ExtensionsMetricsProvider); DISALLOW_COPY_AND_ASSIGN(ExtensionsMetricsProvider);
}; };
......
...@@ -6,21 +6,38 @@ ...@@ -6,21 +6,38 @@
#include <stdint.h> #include <stdint.h>
#include <algorithm>
#include <memory> #include <memory>
#include <string> #include <string>
#include "base/strings/string16.h" #include "base/strings/string16.h"
#include "base/test/scoped_task_environment.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_service_test_base.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "components/metrics/client_info.h" #include "components/metrics/client_info.h"
#include "components/metrics/metrics_service.h" #include "components/metrics/metrics_service.h"
#include "components/metrics/metrics_state_manager.h" #include "components/metrics/metrics_state_manager.h"
#include "components/metrics/proto/extension_install.pb.h"
#include "components/metrics/proto/system_profile.pb.h" #include "components/metrics/proto/system_profile.pb.h"
#include "components/metrics/test_enabled_state_provider.h" #include "components/metrics/test_enabled_state_provider.h"
#include "components/prefs/testing_pref_service.h" #include "components/prefs/testing_pref_service.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/common/disable_reason.h"
#include "extensions/common/extension.h" #include "extensions/common/extension.h"
#include "extensions/common/extension_builder.h" #include "extensions/common/extension_builder.h"
#include "extensions/common/extension_set.h" #include "extensions/common/extension_set.h"
#include "extensions/common/value_builder.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
using metrics::ExtensionInstallProto;
using extensions::Extension;
using extensions::ExtensionBuilder;
using extensions::Manifest;
using extensions::DictionaryBuilder;
namespace { namespace {
void StoreNoClientInfoBackup(const metrics::ClientInfo& /* client_info */) { void StoreNoClientInfoBackup(const metrics::ClientInfo& /* client_info */) {
...@@ -105,6 +122,10 @@ TEST(ExtensionsMetricsProvider, HashExtension) { ...@@ -105,6 +122,10 @@ TEST(ExtensionsMetricsProvider, HashExtension) {
// TestExtensionsMetricsProvider is encoded properly. // TestExtensionsMetricsProvider is encoded properly.
TEST(ExtensionsMetricsProvider, SystemProtoEncoding) { TEST(ExtensionsMetricsProvider, SystemProtoEncoding) {
metrics::SystemProfileProto system_profile; metrics::SystemProfileProto system_profile;
base::test::ScopedTaskEnvironment task_environment;
TestingProfileManager testing_profile_manager(
TestingBrowserProcess::GetGlobal());
ASSERT_TRUE(testing_profile_manager.SetUp());
TestingPrefServiceSimple local_state; TestingPrefServiceSimple local_state;
metrics::TestEnabledStateProvider enabled_state_provider(true, true); metrics::TestEnabledStateProvider enabled_state_provider(true, true);
metrics::MetricsService::RegisterPrefs(local_state.registry()); metrics::MetricsService::RegisterPrefs(local_state.registry());
...@@ -118,3 +139,269 @@ TEST(ExtensionsMetricsProvider, SystemProtoEncoding) { ...@@ -118,3 +139,269 @@ TEST(ExtensionsMetricsProvider, SystemProtoEncoding) {
EXPECT_EQ(10, system_profile.occupied_extension_bucket(0)); EXPECT_EQ(10, system_profile.occupied_extension_bucket(0));
EXPECT_EQ(1007, system_profile.occupied_extension_bucket(1)); EXPECT_EQ(1007, system_profile.occupied_extension_bucket(1));
} }
class ExtensionMetricsProviderInstallsTest
: public extensions::ExtensionServiceTestBase {
public:
ExtensionMetricsProviderInstallsTest() {}
~ExtensionMetricsProviderInstallsTest() override {}
void SetUp() override {
ExtensionServiceTestBase::SetUp();
InitializeEmptyExtensionService();
prefs_ = extensions::ExtensionPrefs::Get(profile());
last_sample_time_ = base::Time::Now() - base::TimeDelta::FromMinutes(30);
}
ExtensionInstallProto ConstructProto(const Extension& extension) {
return ExtensionsMetricsProvider::ConstructInstallProtoForTesting(
extension, prefs_, last_sample_time_);
}
std::vector<ExtensionInstallProto> GetInstallsForProfile() {
return ExtensionsMetricsProvider::GetInstallsForProfileForTesting(
profile(), last_sample_time_);
}
extensions::ExtensionPrefs* prefs() { return prefs_; }
void set_last_sample_time(base::Time last_sample_time) {
last_sample_time_ = last_sample_time;
}
private:
extensions::ExtensionPrefs* prefs_ = nullptr;
base::Time last_sample_time_;
DISALLOW_COPY_AND_ASSIGN(ExtensionMetricsProviderInstallsTest);
};
// Tests the various aspects of constructing a relevant proto for a given
// extension installation.
TEST_F(ExtensionMetricsProviderInstallsTest, TestProtoConstruction) {
auto add_extension = [this](const Extension* extension) {
prefs()->OnExtensionInstalled(extension, Extension::ENABLED,
syncer::StringOrdinal(),
extensions::kInstallFlagNone, std::string());
};
{
// Test basic prototype construction. All fields should be present, except
// disable reasons (which should be empty).
scoped_refptr<const Extension> extension =
ExtensionBuilder("test").SetLocation(Manifest::INTERNAL).Build();
add_extension(extension.get());
ExtensionInstallProto install = ConstructProto(*extension);
EXPECT_TRUE(install.has_type());
EXPECT_EQ(ExtensionInstallProto::EXTENSION, install.type());
EXPECT_TRUE(install.has_install_location());
EXPECT_EQ(ExtensionInstallProto::INTERNAL, install.install_location());
EXPECT_TRUE(install.has_manifest_version());
EXPECT_EQ(2, install.manifest_version());
EXPECT_TRUE(install.has_action_type());
EXPECT_EQ(ExtensionInstallProto::NO_ACTION, install.action_type());
EXPECT_TRUE(install.has_has_file_access());
EXPECT_FALSE(install.has_file_access());
EXPECT_TRUE(install.has_has_incognito_access());
EXPECT_FALSE(install.has_incognito_access());
EXPECT_TRUE(install.has_updates_from_store());
EXPECT_FALSE(install.updates_from_store());
EXPECT_TRUE(install.has_is_from_bookmark());
EXPECT_FALSE(install.is_from_bookmark());
EXPECT_TRUE(install.has_is_converted_from_user_script());
EXPECT_FALSE(install.is_converted_from_user_script());
EXPECT_TRUE(install.has_is_default_installed());
EXPECT_FALSE(install.is_default_installed());
EXPECT_TRUE(install.has_is_oem_installed());
EXPECT_FALSE(install.is_oem_installed());
EXPECT_TRUE(install.has_background_script_type());
EXPECT_EQ(ExtensionInstallProto::NO_BACKGROUND_SCRIPT,
install.background_script_type());
EXPECT_EQ(0, install.disable_reasons_size());
EXPECT_TRUE(install.has_blacklist_state());
EXPECT_EQ(ExtensionInstallProto::NOT_BLACKLISTED,
install.blacklist_state());
EXPECT_TRUE(install.has_installed_in_this_sample_period());
EXPECT_TRUE(install.installed_in_this_sample_period());
}
// It's not helpful to exhaustively test each possible variation of each
// field in the proto (since in many cases the test code would then be
// re-writing the original code), but we test a few of the more interesting
// cases.
{
// Test the type() field; extensions of different types should be reported
// as such.
scoped_refptr<const Extension> extension =
ExtensionBuilder("app", ExtensionBuilder::Type::PLATFORM_APP)
.SetLocation(Manifest::INTERNAL)
.Build();
add_extension(extension.get());
ExtensionInstallProto install = ConstructProto(*extension);
EXPECT_EQ(ExtensionInstallProto::PLATFORM_APP, install.type());
}
{
// Test the install location.
scoped_refptr<const Extension> extension =
ExtensionBuilder("unpacked").SetLocation(Manifest::UNPACKED).Build();
add_extension(extension.get());
ExtensionInstallProto install = ConstructProto(*extension);
EXPECT_EQ(ExtensionInstallProto::UNPACKED, install.install_location());
}
{
// Test the extension action as a browser action.
scoped_refptr<const Extension> extension =
ExtensionBuilder("browser_action")
.SetLocation(Manifest::INTERNAL)
.SetAction(ExtensionBuilder::ActionType::BROWSER_ACTION)
.Build();
add_extension(extension.get());
ExtensionInstallProto install = ConstructProto(*extension);
EXPECT_EQ(ExtensionInstallProto::BROWSER_ACTION, install.action_type());
}
{
// Test the extension action as a page action.
scoped_refptr<const Extension> extension =
ExtensionBuilder("page_action")
.SetLocation(Manifest::INTERNAL)
.SetAction(ExtensionBuilder::ActionType::PAGE_ACTION)
.Build();
add_extension(extension.get());
ExtensionInstallProto install = ConstructProto(*extension);
EXPECT_EQ(ExtensionInstallProto::PAGE_ACTION, install.action_type());
}
{
// Test the disable reasons field.
scoped_refptr<const Extension> extension =
ExtensionBuilder("disable_reasons")
.SetLocation(Manifest::INTERNAL)
.Build();
add_extension(extension.get());
prefs()->SetExtensionDisabled(
extension->id(), extensions::disable_reason::DISABLE_USER_ACTION);
{
ExtensionInstallProto install = ConstructProto(*extension);
ASSERT_EQ(1, install.disable_reasons_size());
EXPECT_EQ(ExtensionInstallProto::USER_ACTION,
install.disable_reasons().Get(0));
}
// Adding additional disable reasons should result in all reasons being
// reported.
prefs()->AddDisableReason(extension->id(),
extensions::disable_reason::DISABLE_CORRUPTED);
{
ExtensionInstallProto install = ConstructProto(*extension);
ASSERT_EQ(2, install.disable_reasons_size());
EXPECT_EQ(ExtensionInstallProto::USER_ACTION,
install.disable_reasons().Get(0));
EXPECT_EQ(ExtensionInstallProto::CORRUPTED,
install.disable_reasons().Get(1));
}
}
{
// Test that event pages are reported correctly.
DictionaryBuilder background;
background.SetBoolean("persistent", false)
.Set("scripts", extensions::ListBuilder().Append("script.js").Build());
scoped_refptr<const Extension> extension =
ExtensionBuilder("event_page")
.SetLocation(Manifest::INTERNAL)
.MergeManifest(DictionaryBuilder()
.Set("background", background.Build())
.Build())
.Build();
add_extension(extension.get());
ExtensionInstallProto install = ConstructProto(*extension);
EXPECT_EQ(ExtensionInstallProto::EVENT_PAGE,
install.background_script_type());
}
{
// Test that persistent background pages are reported correctly.
DictionaryBuilder background;
background.SetBoolean("persistent", true)
.Set("scripts", extensions::ListBuilder().Append("script.js").Build());
scoped_refptr<const Extension> extension =
ExtensionBuilder("persisent_background")
.SetLocation(Manifest::INTERNAL)
.MergeManifest(DictionaryBuilder()
.Set("background", background.Build())
.Build())
.Build();
add_extension(extension.get());
ExtensionInstallProto install = ConstructProto(*extension);
EXPECT_EQ(ExtensionInstallProto::PERSISTENT_BACKGROUND_PAGE,
install.background_script_type());
}
{
// Test changing the blacklist state.
scoped_refptr<const Extension> extension =
ExtensionBuilder("blacklist").SetLocation(Manifest::INTERNAL).Build();
add_extension(extension.get());
prefs()->SetExtensionBlacklistState(
extension->id(), extensions::BLACKLISTED_SECURITY_VULNERABILITY);
ExtensionInstallProto install = ConstructProto(*extension);
EXPECT_EQ(ExtensionInstallProto::BLACKLISTED_SECURITY_VULNERABILITY,
install.blacklist_state());
}
{
// Test that the installed_in_this_sample_period boolean is correctly
// reported.
scoped_refptr<const Extension> extension =
ExtensionBuilder("installtime").SetLocation(Manifest::INTERNAL).Build();
add_extension(extension.get());
set_last_sample_time(base::Time::Now() + base::TimeDelta::FromMinutes(60));
ExtensionInstallProto install = ConstructProto(*extension);
EXPECT_FALSE(install.installed_in_this_sample_period());
}
}
// Tests that we retrieve all extensions associated with a given profile.
TEST_F(ExtensionMetricsProviderInstallsTest,
TestGettingAllExtensionsInProfile) {
scoped_refptr<const Extension> extension =
ExtensionBuilder("extension").Build();
service()->AddExtension(extension.get());
scoped_refptr<const Extension> app =
ExtensionBuilder("app", ExtensionBuilder::Type::PLATFORM_APP).Build();
service()->AddExtension(app.get());
service()->DisableExtension(app->id(),
extensions::disable_reason::DISABLE_USER_ACTION);
std::vector<ExtensionInstallProto> installs = GetInstallsForProfile();
// There should be two installs total.
ASSERT_EQ(2u, installs.size());
// One should be the extension, and the other should be the app. We don't
// check the specifics of the proto, since that's tested above.
EXPECT_TRUE(std::any_of(installs.begin(), installs.end(),
[](const ExtensionInstallProto& install) {
return install.type() ==
ExtensionInstallProto::EXTENSION;
}));
EXPECT_TRUE(std::any_of(installs.begin(), installs.end(),
[](const ExtensionInstallProto& install) {
return install.type() ==
ExtensionInstallProto::PLATFORM_APP;
}));
}
...@@ -10,6 +10,7 @@ proto_library("proto") { ...@@ -10,6 +10,7 @@ proto_library("proto") {
"cast_logs.proto", "cast_logs.proto",
"chrome_user_metrics_extension.proto", "chrome_user_metrics_extension.proto",
"execution_context.proto", "execution_context.proto",
"extension_install.proto",
"histogram_event.proto", "histogram_event.proto",
"memory_leak_report.proto", "memory_leak_report.proto",
"omnibox_event.proto", "omnibox_event.proto",
......
// Copyright 2017 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.
//
// Stores information about an extension installed on a user's machine.
syntax = "proto2";
option optimize_for = LITE_RUNTIME;
option java_outer_classname = "ExtensionInstallProtos";
option java_package = "org.chromium.components.metrics";
package metrics;
// Next tag: 16.
message ExtensionInstallProto {
// The type of extension item this is.
enum Type {
UNKNOWN_TYPE = 0; // Unknown (hopefully never used)
EXTENSION = 1; // A browser extension
THEME = 2; // A browser theme
USER_SCRIPT = 3; // An extension converted from a user script
HOSTED_APP = 4; // A hosted app
LEGACY_PACKAGED_APP = 5; // A (deprecated) v1 packaged app
PLATFORM_APP = 6; // A platform app
SHARED_MODULE = 7; // A shared module
}
optional Type type = 1;
// The source of the extension.
enum InstallLocation {
UNKNOWN_LOCATION = 0; // Unknown (hopefully never used)
INTERNAL = 1; // A crx file from the internal Extensions directory; most
// webstore-installed extensions fall into this category.
EXTERNAL_PREF = 2; // A crx file from an external directory (via prefs).
EXTERNAL_REGISTRY = 3; // A crx file from an external directory (via the
// Windows registry)
UNPACKED = 4; // An unpacked extension loaded from chrome://extensions.
COMPONENT = 5; // An internal component extension.
EXTERNAL_PREF_DOWNLOAD = 6; // A crx file from an external directory (via
// prefs), downloaded from an update URL.
EXTERNAL_POLICY_DOWNLOAD = 7; // A crx file from an external directory (via
// admin policies), downloaded from an update
// URL.
COMMAND_LINE = 8; // Loaded from the commandline (e.g. --load-extension).
EXTERNAL_POLICY = 9; // A crx file from an external directory (via admin
// policies), cached locally and installed from the
// cache.
EXTERNAL_COMPONENT = 10; // A component extension that was downloaded
// externally via an update url.
}
optional InstallLocation install_location = 2;
// The manifest version in the extension. Note: this refers to the
// Chrome-required versioning of the manifest, not the extension version.
// Currently, it is always 1 or 2.
optional int32 manifest_version = 3;
// The associated UI action in the extension. Each extension can have at most
// one type of action.
enum ActionType {
NO_ACTION = 0;
BROWSER_ACTION = 1;
PAGE_ACTION = 2;
SYSTEM_INDICATOR = 3;
}
optional ActionType action_type = 4;
// If the extension has been granted file access.
optional bool has_file_access = 5;
// If the extension has been granted permission to run in incognito contexts.
optional bool has_incognito_access = 6;
// If the extension originated from the Chrome Web Store according to the
// prefs.
// This differs from install_location, which specifies from where the location
// on the user’s machine from where the install originated, but not whether
// the extension is hosted in the store. For instance, sideloaded extensions
// that are specified via ID in the registry are downloaded from the store.
optional bool is_from_store = 7;
// If the extension automatically updates from the Chrome Web Store.
optional bool updates_from_store = 8;
// If the extension is a bookmark app that was generated from a web page. This
// is distinct from install_location above, which specifies from where on the
// user’s machine the install originated.
optional bool is_from_bookmark = 9;
// If the extension was created from a user script. This is distinct from
// install_location above, which specifies from where on the user’s machine
// the install originated.
optional bool is_converted_from_user_script = 10;
// If the extension was installed by default when the profile was created.
// These extensions are specified by Chrome.
optional bool is_default_installed = 11;
// If the extension was installed by an OEM. This differs from
// "is_default_installed", since these extensions are specified by the OEM
// rather than by Chrome. These are specified in a file that is created as
// part of the creation of the Chrome image, and can be specific to different
// OEMs.
optional bool is_oem_installed = 12;
// The type of background page this extension has. Each extension can have at
// most one type of background presence.
enum BackgroundScriptType {
NO_BACKGROUND_SCRIPT = 0; // The extension has no background page.
PERSISTENT_BACKGROUND_PAGE = 1; // The extension has a persistent
// background page.
EVENT_PAGE = 2; // The extension has a (lazy) event page.
}
optional BackgroundScriptType background_script_type = 13;
// The reasons an extension may be disabled.
enum DisableReason {
USER_ACTION = 0; // The user disabled the extension.
PERMISSIONS_INCREASE = 1; // The extension increased permissions.
RELOAD = 2; // The extension is reloading.
UNSUPPORTED_REQUIREMENT = 3; // The extension has requirements that weren't
// met (e.g. graphics capabilities).
SIDELOAD_WIPEOUT = 4; // The extension was disabled in the sideload
// wipeout.
UNKNOWN_FROM_SYNC = 5; // The extension was disabled by sync.
NOT_VERIFIED = 6; // The extension couldn't be verified.
GREYLIST = 7; // The extension was found on the greylist.
CORRUPTED = 8; // The extension install was corrupted according to content
// verification.
REMOTE_INSTALL = 9; // The extension was installed remotely and hasn't been
// enabled.
EXTERNAL_EXTENSION = 10; // The extension was sideloaded and hasn't been
// enabled.
UPDATE_REQUIRED_BY_POLICY = 11; // Policy requires an unmet minimum
// version.
CUSTODIAN_APPROVAL_REQUIRED = 12; // The extension is pending custodian
// approval for a supervised user.
BLOCKED_BY_POLICY = 13; // The extension is disabled because it's blocked
// by enterprise policy.
}
// Any DisableReasons in effect for the extension. An empty list means the
// extension is not disabled. Note that an extension that is not disabled may
// nonetheless not be running, e.g., terminated because the extension process
// was killed.
repeated DisableReason disable_reasons = 14;
// The state of the extension in the safe browsing blacklist.
// The numeric values here match the values of the respective enum in
// ClientCRXListInfoResponse proto.
enum BlacklistState {
// The extension is not in the blacklist.
NOT_BLACKLISTED = 0;
// The extension is malware.
BLACKLISTED_MALWARE = 1;
// The extension has a serious security vulnerability.
BLACKLISTED_SECURITY_VULNERABILITY = 2;
// The extension violated CWS policy.
BLACKLISTED_CWS_POLICY_VIOLATION = 3;
// The extension is considered potentially unwanted.
BLACKLISTED_POTENTIALLY_UNWANTED = 4;
// Used when we couldn't connect to server, e.g. when offline.
BLACKLISTED_UNKNOWN = 5;
}
optional BlacklistState blacklist_state = 15;
// Whether the extension was installed in the current sampling period. This
// is useful if trying to use extension installation in conjunction with other
// metrics (e.g. page load). Since some of the metrics from this period will
// have the extension installed and others won't, these records can be
// discarded for that analysis.
optional bool installed_in_this_sample_period = 16;
}
...@@ -13,7 +13,9 @@ option java_package = "org.chromium.components.metrics"; ...@@ -13,7 +13,9 @@ option java_package = "org.chromium.components.metrics";
package metrics; package metrics;
// Next tag: 24 import "extension_install.proto";
// Next tag: 26
message SystemProfileProto { message SystemProfileProto {
// The time when the client was compiled/linked, in seconds since the epoch. // The time when the client was compiled/linked, in seconds since the epoch.
optional int64 build_timestamp = 1; optional int64 build_timestamp = 1;
...@@ -944,4 +946,9 @@ message SystemProfileProto { ...@@ -944,4 +946,9 @@ message SystemProfileProto {
optional fixed32 omaha_fingerprint = 3; optional fixed32 omaha_fingerprint = 3;
} }
repeated ChromeComponent chrome_component = 24; repeated ChromeComponent chrome_component = 24;
// Information about the user's installed extensions. This will include
// extensions from all fully-initialized profiles. If a single extension is
// installed in multiple profiles, it will be recorded multiple times.
repeated ExtensionInstallProto extension_install = 25;
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment