summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
authoraa@chromium.org <aa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-10-02 08:09:13 +0000
committeraa@chromium.org <aa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-10-02 08:09:13 +0000
commita1be705d99259f64c341810bd004bb57542d24f9 (patch)
tree4c8619677ebff3fb6ac7091cd3d7df463b0cfc79 /chrome
parentc1ae3000e2ffb664b02f962e48762df80407b0e1 (diff)
downloadchromium_src-a1be705d99259f64c341810bd004bb57542d24f9.zip
chromium_src-a1be705d99259f64c341810bd004bb57542d24f9.tar.gz
chromium_src-a1be705d99259f64c341810bd004bb57542d24f9.tar.bz2
Beginnings of the script bubble.
BUG=153137,135503 TBR=arv@chromium.org Review URL: https://codereview.chromium.org/11014009 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@159653 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r--chrome/browser/browser_resources.grd1
-rw-r--r--chrome/browser/extensions/component_loader.cc103
-rw-r--r--chrome/browser/extensions/component_loader.h51
-rw-r--r--chrome/browser/extensions/component_loader_unittest.cc83
-rw-r--r--chrome/browser/extensions/extension_browsertest.cc4
-rw-r--r--chrome/browser/extensions/script_bubble_controller.cc53
-rw-r--r--chrome/browser/extensions/script_bubble_controller.h33
-rw-r--r--chrome/browser/extensions/script_bubble_controller_unittest.cc117
-rw-r--r--chrome/browser/extensions/tab_helper.cc5
-rw-r--r--chrome/browser/extensions/tab_helper.h8
-rw-r--r--chrome/browser/resources/script_bubble/16.pngbin0 -> 491 bytes
-rw-r--r--chrome/browser/resources/script_bubble/manifest.json10
-rw-r--r--chrome/chrome_browser_extensions.gypi2
-rw-r--r--chrome/chrome_common.gypi2
-rw-r--r--chrome/chrome_tests.gypi2
-rw-r--r--chrome/common/chrome_switches.cc4
-rw-r--r--chrome/common/chrome_switches.h1
-rw-r--r--chrome/common/extensions/feature_switch.cc78
-rw-r--r--chrome/common/extensions/feature_switch.h62
-rw-r--r--chrome/common/extensions/feature_switch_unittest.cc134
20 files changed, 644 insertions, 109 deletions
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd
index 1311130..59b02ed 100644
--- a/chrome/browser/browser_resources.grd
+++ b/chrome/browser/browser_resources.grd
@@ -272,6 +272,7 @@
<if expr="pp_ifdef('chromeos') and pp_ifdef('_google_chrome')">
<include name="IDR_HELP_MANIFEST" file="resources\help_app\manifest.json" type="BINDATA" />
</if>
+ <include name="IDR_SCRIPT_BUBBLE_MANIFEST" file="resources\script_bubble\manifest.json" type="BINDATA" />
<if expr="pp_ifdef('use_ash')">
<include name="IDR_GESTURE_CONFIG_CSS" file="resources\gesture_config.css" type="BINDATA" />
<include name="IDR_GESTURE_CONFIG_HTML" file="resources\gesture_config.html" type="BINDATA" />
diff --git a/chrome/browser/extensions/component_loader.cc b/chrome/browser/extensions/component_loader.cc
index 9c33be8..683a393 100644
--- a/chrome/browser/extensions/component_loader.cc
+++ b/chrome/browser/extensions/component_loader.cc
@@ -20,6 +20,7 @@
#include "chrome/common/extensions/extension.h"
#include "chrome/common/extensions/extension_file_util.h"
#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "chrome/common/extensions/feature_switch.h"
#include "chrome/common/pref_names.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_source.h"
@@ -42,6 +43,31 @@
namespace extensions {
+namespace {
+
+std::string GenerateId(const DictionaryValue* manifest, const FilePath& path) {
+ std::string raw_key;
+ std::string id_input;
+ std::string id;
+ CHECK(manifest->GetString(extension_manifest_keys::kPublicKey, &raw_key));
+ CHECK(Extension::ParsePEMKeyBytes(raw_key, &id_input));
+ CHECK(Extension::GenerateId(id_input, &id));
+ return id;
+}
+
+} // namespace
+
+ComponentLoader::ComponentExtensionInfo::ComponentExtensionInfo(
+ const DictionaryValue* manifest, const FilePath& directory)
+ : manifest(manifest),
+ root_directory(directory) {
+ if (!root_directory.IsAbsolute()) {
+ CHECK(PathService::Get(chrome::DIR_RESOURCES, &root_directory));
+ root_directory = root_directory.Append(directory);
+ }
+ extension_id = GenerateId(manifest, root_directory);
+}
+
ComponentLoader::ComponentLoader(ExtensionServiceInterface* extension_service,
PrefService* prefs,
PrefService* local_state)
@@ -59,6 +85,13 @@ ComponentLoader::~ComponentLoader() {
ClearAllRegistered();
}
+const Extension* ComponentLoader::GetScriptBubble() const {
+ if (script_bubble_id_.empty())
+ return NULL;
+
+ return extension_service_->extensions()->GetByID(script_bubble_id_);
+}
+
void ComponentLoader::LoadAll() {
for (RegisteredComponentExtensions::iterator it =
component_extensions_.begin();
@@ -90,9 +123,8 @@ void ComponentLoader::ClearAllRegistered() {
component_extensions_.clear();
}
-const Extension* ComponentLoader::Add(
- int manifest_resource_id,
- const FilePath& root_directory) {
+std::string ComponentLoader::Add(int manifest_resource_id,
+ const FilePath& root_directory) {
std::string manifest_contents =
ResourceBundle::GetSharedInstance().GetRawDataResource(
manifest_resource_id,
@@ -100,29 +132,26 @@ const Extension* ComponentLoader::Add(
return Add(manifest_contents, root_directory);
}
-const Extension* ComponentLoader::Add(
- const std::string& manifest_contents,
- const FilePath& root_directory) {
+std::string ComponentLoader::Add(const std::string& manifest_contents,
+ const FilePath& root_directory) {
// The Value is kept for the lifetime of the ComponentLoader. This is
// required in case LoadAll() is called again.
DictionaryValue* manifest = ParseManifest(manifest_contents);
if (manifest)
return Add(manifest, root_directory);
- return NULL;
+ return "";
}
-const Extension* ComponentLoader::Add(
- const DictionaryValue* parsed_manifest,
- const FilePath& root_directory) {
- CHECK(!Exists(GenerateId(parsed_manifest)));
+std::string ComponentLoader::Add(const DictionaryValue* parsed_manifest,
+ const FilePath& root_directory) {
ComponentExtensionInfo info(parsed_manifest, root_directory);
component_extensions_.push_back(info);
if (extension_service_->is_ready())
- return Load(info);
- return NULL;
+ Load(info);
+ return info.extension_id;
}
-const Extension* ComponentLoader::AddOrReplace(const FilePath& path) {
+std::string ComponentLoader::AddOrReplace(const FilePath& path) {
FilePath absolute_path = path;
file_util::AbsolutePath(&absolute_path);
std::string error;
@@ -133,7 +162,7 @@ const Extension* ComponentLoader::AddOrReplace(const FilePath& path) {
absolute_path.value() << "'. " << error;
return NULL;
}
- Remove(GenerateId(manifest.get()));
+ Remove(GenerateId(manifest.get(), absolute_path));
return Add(manifest.release(), absolute_path);
}
@@ -142,7 +171,7 @@ void ComponentLoader::Reload(const std::string& extension_id) {
for (RegisteredComponentExtensions::iterator it =
component_extensions_.begin(); it != component_extensions_.end();
++it) {
- if (GenerateId(it->manifest) == extension_id) {
+ if (it->extension_id == extension_id) {
Load(*it);
break;
}
@@ -156,18 +185,8 @@ const Extension* ComponentLoader::Load(const ComponentExtensionInfo& info) {
std::string error;
- // Get the absolute path to the extension.
- FilePath absolute_path(info.root_directory);
- if (!absolute_path.IsAbsolute()) {
- if (PathService::Get(chrome::DIR_RESOURCES, &absolute_path)) {
- absolute_path = absolute_path.Append(info.root_directory);
- } else {
- NOTREACHED();
- }
- }
-
scoped_refptr<const Extension> extension(Extension::Create(
- absolute_path,
+ info.root_directory,
Extension::COMPONENT,
*info.manifest,
flags,
@@ -176,6 +195,7 @@ const Extension* ComponentLoader::Load(const ComponentExtensionInfo& info) {
LOG(ERROR) << error;
return NULL;
}
+ CHECK_EQ(info.extension_id, extension->id()) << extension->name();
extension_service_->AddExtension(extension);
return extension;
}
@@ -185,7 +205,7 @@ void ComponentLoader::Remove(const FilePath& root_directory) {
RegisteredComponentExtensions::iterator it = component_extensions_.begin();
for (; it != component_extensions_.end(); ++it) {
if (it->root_directory == root_directory) {
- Remove(GenerateId(it->manifest));
+ Remove(GenerateId(it->manifest, root_directory));
break;
}
}
@@ -194,7 +214,7 @@ void ComponentLoader::Remove(const FilePath& root_directory) {
void ComponentLoader::Remove(const std::string& id) {
RegisteredComponentExtensions::iterator it = component_extensions_.begin();
for (; it != component_extensions_.end(); ++it) {
- if (GenerateId(it->manifest) == id) {
+ if (it->extension_id == id) {
delete it->manifest;
it = component_extensions_.erase(it);
if (extension_service_->is_ready())
@@ -209,24 +229,11 @@ bool ComponentLoader::Exists(const std::string& id) const {
RegisteredComponentExtensions::const_iterator it =
component_extensions_.begin();
for (; it != component_extensions_.end(); ++it)
- if (GenerateId(it->manifest) == id)
+ if (it->extension_id == id)
return true;
return false;
}
-std::string ComponentLoader::GenerateId(const DictionaryValue* manifest) {
- std::string public_key;
- std::string public_key_bytes;
- std::string id;
- if (!manifest->GetString(
- extension_manifest_keys::kPublicKey, &public_key) ||
- !Extension::ParsePEMKeyBytes(public_key, &public_key_bytes) ||
- !Extension::GenerateId(public_key_bytes, &id)) {
- return std::string();
- }
- return id;
-}
-
void ComponentLoader::AddFileManagerExtension() {
#if defined(FILE_MANAGER_EXTENSION)
#ifndef NDEBUG
@@ -302,6 +309,14 @@ void ComponentLoader::AddChromeApp() {
#endif
}
+void ComponentLoader::AddScriptBubble() {
+ if (FeatureSwitch::GetScriptBubble()->IsEnabled()) {
+ script_bubble_id_ =
+ Add(IDR_SCRIPT_BUBBLE_MANIFEST,
+ FilePath(FILE_PATH_LITERAL("script_bubble")));
+ }
+}
+
void ComponentLoader::AddDefaultComponentExtensions() {
#if defined(OS_CHROMEOS)
if (!CommandLine::ForCurrentProcess()->HasSwitch(switches::kGuestSession))
@@ -380,6 +395,8 @@ void ComponentLoader::AddDefaultComponentExtensions() {
#if defined(USE_ASH)
AddChromeApp();
#endif
+
+ AddScriptBubble();
}
void ComponentLoader::Observe(
diff --git a/chrome/browser/extensions/component_loader.h b/chrome/browser/extensions/component_loader.h
index e7b5613..f6dc3c3 100644
--- a/chrome/browser/extensions/component_loader.h
+++ b/chrome/browser/extensions/component_loader.h
@@ -38,17 +38,25 @@ class ComponentLoader : public content::NotificationObserver {
// Registers and possibly loads a component extension. If ExtensionService
// has been initialized, the extension is loaded; otherwise, the load is
- // deferred until LoadAll is called.
- const Extension* Add(const std::string& manifest_contents,
- const FilePath& root_directory);
+ // deferred until LoadAll is called. The ID of the added extension is
+ // returned.
+ //
+ // Component extension manifests must contain a "key" property with a unique
+ // public key, serialized in base64. You can create a suitable value with the
+ // following commands on a unixy system:
+ //
+ // ssh-keygen -t rsa -b 1024 -N '' -f /tmp/key.pem
+ // openssl rsa -pubout -outform DER < /tmp/key.pem 2>/dev/null | base64 -w 0
+ std::string Add(const std::string& manifest_contents,
+ const FilePath& root_directory);
// Convenience method for registering a component extension by resource id.
- const Extension* Add(int manifest_resource_id,
- const FilePath& root_directory);
+ std::string Add(int manifest_resource_id,
+ const FilePath& root_directory);
// Loads a component extension from file system. Replaces previously added
// extension with the same ID.
- const Extension* AddOrReplace(const FilePath& path);
+ std::string AddOrReplace(const FilePath& path);
// Returns true if an extension with the specified id has been added.
bool Exists(const std::string& id) const;
@@ -59,13 +67,6 @@ class ComponentLoader : public content::NotificationObserver {
void Remove(const std::string& id);
// Adds the default component extensions.
- //
- // Component extension manifests must contain a 'key' property with a unique
- // public key, serialized in base64. You can create a suitable value with the
- // following commands on a unixy system:
- //
- // ssh-keygen -t rsa -b 1024 -N '' -f /tmp/key.pem
- // openssl rsa -pubout -outform DER < /tmp/key.pem 2>/dev/null | base64 -w 0
void AddDefaultComponentExtensions();
// content::NotificationObserver implementation
@@ -85,24 +86,31 @@ class ComponentLoader : public content::NotificationObserver {
// Reloads a registered component extension.
void Reload(const std::string& extension_id);
+ // Adds the "Script Bubble" component extension, which puts an icon in the
+ // omnibox indiciating the number of extensions running script in a tab.
+ void AddScriptBubble();
+
+ // Returns the extension previously added by AddScriptBubble(), if any.
+ const Extension* GetScriptBubble() const;
+
private:
// Information about a registered component extension.
struct ComponentExtensionInfo {
ComponentExtensionInfo(const DictionaryValue* manifest,
- const FilePath& root_directory)
- : manifest(manifest),
- root_directory(root_directory) {
- }
+ const FilePath& root_directory);
// The parsed contents of the extensions's manifest file.
const DictionaryValue* manifest;
// Directory where the extension is stored.
FilePath root_directory;
+
+ // The component extension's ID.
+ std::string extension_id;
};
- const Extension* Add(const DictionaryValue* parsed_manifest,
- const FilePath& root_directory);
+ std::string Add(const DictionaryValue* parsed_manifest,
+ const FilePath& root_directory);
// Loads a registered component extension.
const Extension* Load(const ComponentExtensionInfo& info);
@@ -118,9 +126,6 @@ class ComponentLoader : public content::NotificationObserver {
void AddChromeApp();
- // Determine the extension id.
- static std::string GenerateId(const base::DictionaryValue* manifest);
-
PrefService* prefs_;
PrefService* local_state_;
@@ -132,6 +137,8 @@ class ComponentLoader : public content::NotificationObserver {
PrefChangeRegistrar pref_change_registrar_;
+ std::string script_bubble_id_;
+
DISALLOW_COPY_AND_ASSIGN(ComponentLoader);
};
diff --git a/chrome/browser/extensions/component_loader_unittest.cc b/chrome/browser/extensions/component_loader_unittest.cc
index e076ba1..6ff6b01 100644
--- a/chrome/browser/extensions/component_loader_unittest.cc
+++ b/chrome/browser/extensions/component_loader_unittest.cc
@@ -124,65 +124,66 @@ TEST_F(ComponentLoaderTest, ParseManifest) {
// Test invalid JSON.
manifest.reset(
component_loader_.ParseManifest("{ 'test': 3 } invalid"));
- ASSERT_EQ((DictionaryValue*)NULL, manifest.get());
+ EXPECT_FALSE(manifest.get());
// Test manifests that are valid JSON, but don't have an object literal
// at the root. ParseManifest() should always return NULL.
manifest.reset(component_loader_.ParseManifest(""));
- ASSERT_EQ((DictionaryValue*)NULL, manifest.get());
+ EXPECT_FALSE(manifest.get());
manifest.reset(component_loader_.ParseManifest("[{ \"foo\": 3 }]"));
- ASSERT_EQ((DictionaryValue*)NULL, manifest.get());
+ EXPECT_FALSE(manifest.get());
manifest.reset(component_loader_.ParseManifest("\"Test\""));
- ASSERT_EQ((DictionaryValue*)NULL, manifest.get());
+ EXPECT_FALSE(manifest.get());
manifest.reset(component_loader_.ParseManifest("42"));
- ASSERT_EQ((DictionaryValue*)NULL, manifest.get());
+ EXPECT_FALSE(manifest.get());
manifest.reset(component_loader_.ParseManifest("true"));
- ASSERT_EQ((DictionaryValue*)NULL, manifest.get());
+ EXPECT_FALSE(manifest.get());
manifest.reset(component_loader_.ParseManifest("false"));
- ASSERT_EQ((DictionaryValue*)NULL, manifest.get());
+ EXPECT_FALSE(manifest.get());
manifest.reset(component_loader_.ParseManifest("null"));
- ASSERT_EQ((DictionaryValue*)NULL, manifest.get());
+ EXPECT_FALSE(manifest.get());
// Test parsing valid JSON.
- int value;
+ int value = 0;
manifest.reset(component_loader_.ParseManifest(
"{ \"test\": { \"one\": 1 }, \"two\": 2 }"));
- ASSERT_NE(manifest.get(), (DictionaryValue*)NULL);
- ASSERT_TRUE(manifest->GetInteger("test.one", &value));
- ASSERT_EQ(1, value);
+ ASSERT_TRUE(manifest.get());
+ EXPECT_TRUE(manifest->GetInteger("test.one", &value));
+ EXPECT_EQ(1, value);
ASSERT_TRUE(manifest->GetInteger("two", &value));
- ASSERT_EQ(2, value);
+ EXPECT_EQ(2, value);
std::string string_value;
manifest.reset(component_loader_.ParseManifest(manifest_contents_));
ASSERT_TRUE(manifest->GetString("background.page", &string_value));
- ASSERT_EQ("backgroundpage.html", string_value);
+ EXPECT_EQ("backgroundpage.html", string_value);
}
// Test that the extension isn't loaded if the extension service isn't ready.
TEST_F(ComponentLoaderTest, AddWhenNotReady) {
- scoped_refptr<const Extension> extension;
extension_service_.set_ready(false);
- extension = component_loader_.Add(manifest_contents_, extension_path_);
- ASSERT_EQ((Extension*)NULL, extension.get());
- ASSERT_EQ(0u, extension_service_.extensions()->size());
+ std::string extension_id =
+ component_loader_.Add(manifest_contents_, extension_path_);
+ EXPECT_NE("", extension_id);
+ EXPECT_EQ(0u, extension_service_.extensions()->size());
}
// Test that it *is* loaded when the extension service *is* ready.
TEST_F(ComponentLoaderTest, AddWhenReady) {
- scoped_refptr<const Extension> extension;
extension_service_.set_ready(true);
- extension = component_loader_.Add(manifest_contents_, extension_path_);
- ASSERT_NE((Extension*)NULL, extension.get());
- ASSERT_EQ(1u, extension_service_.extensions()->size());
+ std::string extension_id =
+ component_loader_.Add(manifest_contents_, extension_path_);
+ EXPECT_NE("", extension_id);
+ EXPECT_EQ(1u, extension_service_.extensions()->size());
+ EXPECT_TRUE(extension_service_.extensions()->GetByID(extension_id));
}
TEST_F(ComponentLoaderTest, Remove) {
@@ -190,25 +191,25 @@ TEST_F(ComponentLoaderTest, Remove) {
// Removing an extension that was never added should be ok.
component_loader_.Remove(extension_path_);
- ASSERT_EQ(0u, extension_service_.extensions()->size());
+ EXPECT_EQ(0u, extension_service_.extensions()->size());
// Try adding and removing before LoadAll() is called.
component_loader_.Add(manifest_contents_, extension_path_);
component_loader_.Remove(extension_path_);
component_loader_.LoadAll();
- ASSERT_EQ(0u, extension_service_.extensions()->size());
+ EXPECT_EQ(0u, extension_service_.extensions()->size());
// Load an extension, and check that it's unloaded when Remove() is called.
- scoped_refptr<const Extension> extension;
extension_service_.set_ready(true);
- extension = component_loader_.Add(manifest_contents_, extension_path_);
- ASSERT_NE((Extension*)NULL, extension.get());
+ std::string extension_id =
+ component_loader_.Add(manifest_contents_, extension_path_);
+ EXPECT_EQ(1u, extension_service_.extensions()->size());
component_loader_.Remove(extension_path_);
- ASSERT_EQ(0u, extension_service_.extensions()->size());
+ EXPECT_EQ(0u, extension_service_.extensions()->size());
// And after calling LoadAll(), it shouldn't get loaded.
component_loader_.LoadAll();
- ASSERT_EQ(0u, extension_service_.extensions()->size());
+ EXPECT_EQ(0u, extension_service_.extensions()->size());
}
TEST_F(ComponentLoaderTest, LoadAll) {
@@ -216,7 +217,7 @@ TEST_F(ComponentLoaderTest, LoadAll) {
// No extensions should be loaded if none were added.
component_loader_.LoadAll();
- ASSERT_EQ(0u, extension_service_.extensions()->size());
+ EXPECT_EQ(0u, extension_service_.extensions()->size());
// Use LoadAll() to load the default extensions.
component_loader_.AddDefaultComponentExtensions();
@@ -228,7 +229,7 @@ TEST_F(ComponentLoaderTest, LoadAll) {
component_loader_.Add(manifest_contents_, extension_path_);
component_loader_.LoadAll();
- ASSERT_EQ(default_count + 1, extension_service_.extensions()->size());
+ EXPECT_EQ(default_count + 1, extension_service_.extensions()->size());
}
TEST_F(ComponentLoaderTest, EnterpriseWebStore) {
@@ -240,7 +241,7 @@ TEST_F(ComponentLoaderTest, EnterpriseWebStore) {
extension_service_.set_ready(true);
prefs_.SetUserPref(prefs::kEnterpriseWebStoreURL,
Value::CreateStringValue("http://www.google.com"));
- ASSERT_EQ(default_count + 1, extension_service_.extensions()->size());
+ EXPECT_EQ(default_count + 1, extension_service_.extensions()->size());
// Now that the pref is set, check if it's added by default.
extension_service_.set_ready(false);
@@ -248,16 +249,16 @@ TEST_F(ComponentLoaderTest, EnterpriseWebStore) {
component_loader_.ClearAllRegistered();
component_loader_.AddDefaultComponentExtensions();
component_loader_.LoadAll();
- ASSERT_EQ(default_count + 1, extension_service_.extensions()->size());
+ EXPECT_EQ(default_count + 1, extension_service_.extensions()->size());
// Number of loaded extensions should be the same after changing the pref.
prefs_.SetUserPref(prefs::kEnterpriseWebStoreURL,
Value::CreateStringValue("http://www.google.de"));
- ASSERT_EQ(default_count + 1, extension_service_.extensions()->size());
+ EXPECT_EQ(default_count + 1, extension_service_.extensions()->size());
}
TEST_F(ComponentLoaderTest, AddOrReplace) {
- ASSERT_EQ(0u, component_loader_.registered_extensions_count());
+ EXPECT_EQ(0u, component_loader_.registered_extensions_count());
component_loader_.AddDefaultComponentExtensions();
size_t const default_count = component_loader_.registered_extensions_count();
FilePath known_extension = GetBasePath()
@@ -266,24 +267,24 @@ TEST_F(ComponentLoaderTest, AddOrReplace) {
// Replace a default component extension.
component_loader_.AddOrReplace(known_extension);
- ASSERT_EQ(default_count,
+ EXPECT_EQ(default_count,
component_loader_.registered_extensions_count());
// Add a new component extension.
component_loader_.AddOrReplace(unknow_extension);
- ASSERT_EQ(default_count + 1,
+ EXPECT_EQ(default_count + 1,
component_loader_.registered_extensions_count());
extension_service_.set_ready(true);
component_loader_.LoadAll();
- ASSERT_EQ(default_count + 1, extension_service_.extensions()->size());
- ASSERT_EQ(0u, extension_service_.unloaded_count());
+ EXPECT_EQ(default_count + 1, extension_service_.extensions()->size());
+ EXPECT_EQ(0u, extension_service_.unloaded_count());
// replace loaded component extension.
component_loader_.AddOrReplace(known_extension);
- ASSERT_EQ(default_count + 1, extension_service_.extensions()->size());
- ASSERT_EQ(1u, extension_service_.unloaded_count());
+ EXPECT_EQ(default_count + 1, extension_service_.extensions()->size());
+ EXPECT_EQ(1u, extension_service_.unloaded_count());
}
} // namespace extensions
diff --git a/chrome/browser/extensions/extension_browsertest.cc b/chrome/browser/extensions/extension_browsertest.cc
index 7432b42..5e0fa7e 100644
--- a/chrome/browser/extensions/extension_browsertest.cc
+++ b/chrome/browser/extensions/extension_browsertest.cc
@@ -187,8 +187,8 @@ const Extension* ExtensionBrowserTest::LoadExtensionAsComponent(
&manifest))
return NULL;
- const Extension* extension =
- service->component_loader()->Add(manifest, path);
+ std::string extension_id = service->component_loader()->Add(manifest, path);
+ const Extension* extension = service->extensions()->GetByID(extension_id);
if (!extension)
return NULL;
last_loaded_extension_id_ = extension->id();
diff --git a/chrome/browser/extensions/script_bubble_controller.cc b/chrome/browser/extensions/script_bubble_controller.cc
new file mode 100644
index 0000000..f0ec9132
--- /dev/null
+++ b/chrome/browser/extensions/script_bubble_controller.cc
@@ -0,0 +1,53 @@
+// Copyright (c) 2012 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/script_bubble_controller.h"
+
+#include "base/string_number_conversions.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/extensions/component_loader.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/extension_system.h"
+#include "chrome/browser/extensions/extension_tab_util.h"
+#include "chrome/browser/extensions/location_bar_controller.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_action.h"
+#include "third_party/skia/include/core/SkColor.h"
+
+namespace extensions {
+
+namespace {
+
+const SkColor kBadgeBackgroundColor = 0xEEEEDD00;
+
+} // namespace
+
+ScriptBubbleController::ScriptBubbleController(TabHelper* tab_helper)
+ : TabHelper::ContentScriptObserver(tab_helper) {
+}
+
+void ScriptBubbleController::OnContentScriptsExecuting(
+ const content::WebContents* web_contents,
+ const ExecutingScriptsMap& extension_ids,
+ int32 page_id,
+ const GURL& on_url) {
+ Profile* profile =
+ Profile::FromBrowserContext(web_contents->GetBrowserContext());
+ ComponentLoader* loader =
+ ExtensionSystem::Get(profile)->extension_service()->component_loader();
+ const Extension* extension = loader->GetScriptBubble();
+ if (!extension)
+ return;
+
+ int tab_id = ExtensionTabUtil::GetTabId(web_contents);
+ ExtensionAction* page_action = extension->page_action();
+
+ page_action->SetAppearance(tab_id, ExtensionAction::ACTIVE);
+ page_action->SetBadgeText(tab_id, base::UintToString(extension_ids.size()));
+ page_action->SetBadgeBackgroundColor(tab_id, kBadgeBackgroundColor);
+
+ tab_helper_->location_bar_controller()->NotifyChange();
+}
+
+} // namespace extensions
diff --git a/chrome/browser/extensions/script_bubble_controller.h b/chrome/browser/extensions/script_bubble_controller.h
new file mode 100644
index 0000000..868f45c
--- /dev/null
+++ b/chrome/browser/extensions/script_bubble_controller.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_SCRIPT_BUBBLE_CONTROLLER_H_
+#define CHROME_BROWSER_EXTENSIONS_SCRIPT_BUBBLE_CONTROLLER_H_
+
+#include "base/basictypes.h"
+#include "chrome/browser/extensions/tab_helper.h"
+
+namespace extensions {
+
+// Controls the script bubble in the omnibox, which displays information about
+// extensions which are interacting with the current tab.
+class ScriptBubbleController : public TabHelper::ContentScriptObserver {
+ public:
+ // |tab_helper| must outlive the created ScriptBadgeController.
+ explicit ScriptBubbleController(TabHelper* tab_helper);
+
+ // TabHelper::ContentScriptObserver implementation
+ virtual void OnContentScriptsExecuting(
+ const content::WebContents* web_contents,
+ const ExecutingScriptsMap& extension_ids,
+ int32 page_id,
+ const GURL& on_url) OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ScriptBubbleController);
+};
+
+} // namespace extensions
+
+#endif // CHROME_BROWSER_EXTENSIONS_SCRIPT_BUBBLE_CONTROLLER_H_
diff --git a/chrome/browser/extensions/script_bubble_controller_unittest.cc b/chrome/browser/extensions/script_bubble_controller_unittest.cc
new file mode 100644
index 0000000..9a9c993
--- /dev/null
+++ b/chrome/browser/extensions/script_bubble_controller_unittest.cc
@@ -0,0 +1,117 @@
+// Copyright (c) 2012 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 <string>
+
+#include "base/command_line.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop.h"
+#include "chrome/browser/extensions/component_loader.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/extension_tab_util.h"
+#include "chrome/browser/extensions/script_bubble_controller.h"
+#include "chrome/browser/extensions/test_extension_system.h"
+#include "chrome/browser/ui/tab_contents/tab_contents.h"
+#include "chrome/browser/ui/tab_contents/test_tab_contents.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/feature_switch.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/navigation_entry.h"
+#include "content/public/test/test_browser_thread.h"
+
+using content::BrowserThread;
+
+namespace extensions {
+namespace {
+
+class ScriptBubbleControllerTest : public TabContentsTestHarness {
+ public:
+ ScriptBubbleControllerTest()
+ : ui_thread_(BrowserThread::UI, MessageLoop::current()),
+ file_thread_(BrowserThread::FILE, MessageLoop::current()),
+ enable_script_bubble_(FeatureSwitch::GetScriptBubble(), true) {
+ }
+
+ virtual void SetUp() OVERRIDE {
+ TabContentsTestHarness::SetUp();
+ CommandLine command_line(CommandLine::NO_PROGRAM);
+ extension_service_ =
+ static_cast<TestExtensionSystem*>(
+ ExtensionSystem::Get(tab_contents()->profile()))->
+ CreateExtensionService(
+ &command_line, FilePath(), false);
+ extension_service_->component_loader()->AddScriptBubble();
+ extension_service_->Init();
+
+ TabHelper::CreateForWebContents(web_contents());
+
+ script_bubble_controller_ =
+ TabHelper::FromWebContents(web_contents())->script_bubble_controller();
+ }
+
+ protected:
+ int tab_id() {
+ return ExtensionTabUtil::GetTabId(web_contents());
+ }
+
+ ExtensionService* extension_service_;
+ ScriptBubbleController* script_bubble_controller_;
+
+ private:
+ content::TestBrowserThread ui_thread_;
+ content::TestBrowserThread file_thread_;
+ FeatureSwitch::ScopedOverride enable_script_bubble_;
+};
+
+TEST_F(ScriptBubbleControllerTest, Basics) {
+ ExtensionAction* script_bubble_action =
+ extension_service_->component_loader()->GetScriptBubble()->
+ page_action();
+ ASSERT_TRUE(script_bubble_action);
+
+ // By default, the bubble should be invisible.
+ NavigateAndCommit(GURL("http://www.google.com"));
+ EXPECT_FALSE(script_bubble_action->GetIsVisible(tab_id()));
+ EXPECT_EQ("", script_bubble_action->GetBadgeText(tab_id()));
+
+ // Running a script on the tab causes the bubble to be visible.
+ TabHelper::ContentScriptObserver::ExecutingScriptsMap executing_scripts;
+ executing_scripts["id1"].insert("script1");
+ script_bubble_controller_->OnContentScriptsExecuting(
+ web_contents(),
+ executing_scripts,
+ web_contents()->GetController().GetActiveEntry()->GetPageID(),
+ web_contents()->GetController().GetActiveEntry()->GetURL());
+ EXPECT_TRUE(script_bubble_action->GetIsVisible(tab_id()));
+ EXPECT_EQ("1", script_bubble_action->GetBadgeText(tab_id()));
+
+ // Running a script from another extension increments the count.
+ executing_scripts["id2"].insert("script2");
+ script_bubble_controller_->OnContentScriptsExecuting(
+ web_contents(),
+ executing_scripts,
+ web_contents()->GetController().GetActiveEntry()->GetPageID(),
+ web_contents()->GetController().GetActiveEntry()->GetURL());
+ EXPECT_TRUE(script_bubble_action->GetIsVisible(tab_id()));
+ EXPECT_EQ("2", script_bubble_action->GetBadgeText(tab_id()));
+
+ // Running another script from an already-seen extension does not affect
+ // count.
+ executing_scripts["id2"].insert("script3");
+ script_bubble_controller_->OnContentScriptsExecuting(
+ web_contents(),
+ executing_scripts,
+ web_contents()->GetController().GetActiveEntry()->GetPageID(),
+ web_contents()->GetController().GetActiveEntry()->GetURL());
+ EXPECT_TRUE(script_bubble_action->GetIsVisible(tab_id()));
+ EXPECT_EQ("2", script_bubble_action->GetBadgeText(tab_id()));
+
+ // Navigating away resets the badge.
+ NavigateAndCommit(GURL("http://www.google.com"));
+ EXPECT_FALSE(script_bubble_action->GetIsVisible(tab_id()));
+ EXPECT_EQ("", script_bubble_action->GetBadgeText(tab_id()));
+};
+
+} // namespace
+} // namespace extensions
diff --git a/chrome/browser/extensions/tab_helper.cc b/chrome/browser/extensions/tab_helper.cc
index bd0a74f..8f91fe6 100644
--- a/chrome/browser/extensions/tab_helper.cc
+++ b/chrome/browser/extensions/tab_helper.cc
@@ -11,6 +11,7 @@
#include "chrome/browser/extensions/extension_tab_util.h"
#include "chrome/browser/extensions/page_action_controller.h"
#include "chrome/browser/extensions/script_badge_controller.h"
+#include "chrome/browser/extensions/script_bubble_controller.h"
#include "chrome/browser/extensions/webstore_inline_installer.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sessions/session_id.h"
@@ -26,6 +27,7 @@
#include "chrome/common/extensions/extension_messages.h"
#include "chrome/common/extensions/extension_resource.h"
#include "chrome/common/extensions/extension_switch_utils.h"
+#include "chrome/common/extensions/feature_switch.h"
#include "content/public/browser/invalidate_type.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_details.h"
@@ -92,6 +94,9 @@ TabHelper::TabHelper(content::WebContents* web_contents)
new PageActionController(web_contents));
}
+ if (FeatureSwitch::GetScriptBubble()->IsEnabled())
+ script_bubble_controller_.reset(new ScriptBubbleController(this));
+
// If more classes need to listen to global content script activity, then
// a separate routing class with an observer interface should be written.
AddContentScriptObserver(ActivityLog::GetInstance());
diff --git a/chrome/browser/extensions/tab_helper.h b/chrome/browser/extensions/tab_helper.h
index ed5563f..30b8dcc 100644
--- a/chrome/browser/extensions/tab_helper.h
+++ b/chrome/browser/extensions/tab_helper.h
@@ -31,6 +31,7 @@ namespace extensions {
class Extension;
class LocationBarController;
class ScriptBadgeController;
+class ScriptBubbleController;
// Per-tab extension helper. Also handles non-extension apps.
class TabHelper : public content::WebContentsObserver,
@@ -70,7 +71,6 @@ class TabHelper : public content::WebContentsObserver,
protected:
virtual ~ContentScriptObserver();
- private:
TabHelper* tab_helper_;
};
@@ -139,6 +139,10 @@ class TabHelper : public content::WebContentsObserver,
return active_tab_permission_granter_.get();
}
+ ScriptBubbleController* script_bubble_controller() {
+ return script_bubble_controller_.get();
+ }
+
// Sets a non-extension app icon associated with WebContents and fires an
// INVALIDATE_TYPE_TITLE navigation state change to trigger repaint of title.
void SetAppIcon(const SkBitmap& app_icon);
@@ -254,6 +258,8 @@ class TabHelper : public content::WebContentsObserver,
scoped_ptr<ActiveTabPermissionGranter> active_tab_permission_granter_;
+ scoped_ptr<ScriptBubbleController> script_bubble_controller_;
+
DISALLOW_COPY_AND_ASSIGN(TabHelper);
};
diff --git a/chrome/browser/resources/script_bubble/16.png b/chrome/browser/resources/script_bubble/16.png
new file mode 100644
index 0000000..7f531fc
--- /dev/null
+++ b/chrome/browser/resources/script_bubble/16.png
Binary files differ
diff --git a/chrome/browser/resources/script_bubble/manifest.json b/chrome/browser/resources/script_bubble/manifest.json
new file mode 100644
index 0000000..43ddd58
--- /dev/null
+++ b/chrome/browser/resources/script_bubble/manifest.json
@@ -0,0 +1,10 @@
+{
+ "manifest_version": 2,
+ "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDO1EY113vutkoYrTsgmPtKDV4QvKk1MEm8ROEHZOQo14xNMefo8+7Lpph1diC9ke97n00cWAkyYmcCEFfbRE56eUJhFVjLzlKAiR/10B0bWQ1xBTB94G8M8Huuv8Om6/0mTwTKny2XdW/XewMq1wOkL/13uAHnivbYRA0aYlNnzQIDAQAB",
+ "name": "Script Bubble",
+ "description": "Shows the number of extensions running content script on the current tab.",
+ "version": "1",
+ "page_action": {
+ "default_icon": "16.png"
+ }
+}
diff --git a/chrome/chrome_browser_extensions.gypi b/chrome/chrome_browser_extensions.gypi
index ea1505c..18712da 100644
--- a/chrome/chrome_browser_extensions.gypi
+++ b/chrome/chrome_browser_extensions.gypi
@@ -530,6 +530,8 @@
'browser/extensions/sandboxed_unpacker.h',
'browser/extensions/script_badge_controller.cc',
'browser/extensions/script_badge_controller.h',
+ 'browser/extensions/script_bubble_controller.cc',
+ 'browser/extensions/script_bubble_controller.h',
'browser/extensions/script_executor.cc',
'browser/extensions/script_executor.h',
'browser/extensions/settings/leveldb_settings_storage_factory.cc',
diff --git a/chrome/chrome_common.gypi b/chrome/chrome_common.gypi
index 332f1fa..64baa03 100644
--- a/chrome/chrome_common.gypi
+++ b/chrome/chrome_common.gypi
@@ -153,6 +153,8 @@
'common/extensions/extension_set.h',
'common/extensions/extension_switch_utils.cc',
'common/extensions/extension_switch_utils.h',
+ 'common/extensions/feature_switch.cc',
+ 'common/extensions/feature_switch.h',
'common/extensions/features/feature.cc',
'common/extensions/features/feature.h',
'common/extensions/features/feature_provider.h',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index 6ba3849..42e286d 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -1304,6 +1304,7 @@
'browser/extensions/process_map_unittest.cc',
'browser/extensions/sandboxed_unpacker_unittest.cc',
'browser/extensions/script_badge_controller_unittest.cc',
+ 'browser/extensions/script_bubble_controller_unittest.cc',
'browser/extensions/settings/settings_frontend_unittest.cc',
'browser/extensions/settings/settings_quota_unittest.cc',
'browser/extensions/settings/settings_sync_unittest.cc',
@@ -1982,6 +1983,7 @@
'common/extensions/extension_test_util.h',
'common/extensions/extension_test_util.cc',
'common/extensions/extension_unittest.cc',
+ 'common/extensions/feature_switch_unittest.cc',
'common/extensions/features/feature_unittest.cc',
'common/extensions/features/simple_feature_provider_unittest.cc',
'common/extensions/manifest_tests/extension_manifest_test.cc',
diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc
index b6af103..99253c2 100644
--- a/chrome/common/chrome_switches.cc
+++ b/chrome/common/chrome_switches.cc
@@ -1148,6 +1148,10 @@ const char kSavePageAsMHTML[] = "save-page-as-mhtml";
// malware.
const char kSbURLPrefix[] = "safebrowsing-url-prefix";
+// Enable an icon in the URL bar that tells you how many extensions are running
+// scripts on a page.
+const char kScriptBubbleEnabled[] = "script-bubble-enabled";
+
// If present, safebrowsing only performs update when
// SafeBrowsingProtocolManager::ForceScheduleNextUpdate() is explicitly called.
// This is used for testing only.
diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h
index dab2ded..80a7df4 100644
--- a/chrome/common/chrome_switches.h
+++ b/chrome/common/chrome_switches.h
@@ -305,6 +305,7 @@ extern const char kSavePageAsMHTML[];
extern const char kSbURLPrefix[];
extern const char kSbDisableAutoUpdate[];
extern const char kSbDisableDownloadProtection[];
+extern const char kScriptBubbleEnabled[];
extern const char kSearchInOmniboxHint[];
extern const char kServiceAccountLsid[];
extern const char kSetToken[];
diff --git a/chrome/common/extensions/feature_switch.cc b/chrome/common/extensions/feature_switch.cc
new file mode 100644
index 0000000..7db6385
--- /dev/null
+++ b/chrome/common/extensions/feature_switch.cc
@@ -0,0 +1,78 @@
+// Copyright (c) 2012 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/common/extensions/feature_switch.h"
+
+#include "base/command_line.h"
+#include "base/lazy_instance.h"
+#include "base/string_util.h"
+#include "chrome/common/chrome_switches.h"
+
+namespace extensions {
+
+namespace {
+
+class CommonSwitches {
+ public:
+ CommonSwitches()
+ : script_bubble(CommandLine::ForCurrentProcess(),
+ switches::kScriptBubbleEnabled,
+ FeatureSwitch::DEFAULT_DISABLED) {
+ }
+ FeatureSwitch script_bubble;
+};
+
+base::LazyInstance<CommonSwitches> g_common_switches =
+ LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+
+FeatureSwitch* FeatureSwitch::GetScriptBubble() {
+ return &g_common_switches.Get().script_bubble;
+}
+
+FeatureSwitch::ScopedOverride::ScopedOverride(FeatureSwitch* feature,
+ bool override_value)
+ : feature_(feature) {
+ feature_->SetOverrideValue(
+ override_value ? OVERRIDE_ENABLED : OVERRIDE_DISABLED);
+}
+
+FeatureSwitch::ScopedOverride::~ScopedOverride() {
+ feature_->SetOverrideValue(OVERRIDE_NONE);
+}
+
+FeatureSwitch::FeatureSwitch(const CommandLine* command_line,
+ const char* switch_name,
+ DefaultValue default_value)
+ : command_line_(command_line),
+ switch_name_(switch_name),
+ default_value_(default_value == DEFAULT_ENABLED),
+ override_value_(OVERRIDE_NONE) {
+}
+
+bool FeatureSwitch::IsEnabled() const {
+ if (override_value_ != OVERRIDE_NONE)
+ return override_value_ == OVERRIDE_ENABLED;
+
+ // TODO(aa): Consider supporting other values.
+ std::string temp = command_line_->GetSwitchValueASCII(switch_name_);
+ std::string switch_value;
+ TrimWhitespaceASCII(temp, TRIM_ALL, &switch_value);
+ if (switch_value == "1")
+ return true;
+ if (switch_value == "0")
+ return false;
+ return default_value_;
+}
+
+void FeatureSwitch::SetOverrideValue(OverrideValue override_value) {
+ if (override_value_ != OVERRIDE_NONE)
+ CHECK_EQ(OVERRIDE_NONE, override_value);
+
+ override_value_ = override_value;
+}
+
+} // namespace extensions
diff --git a/chrome/common/extensions/feature_switch.h b/chrome/common/extensions/feature_switch.h
new file mode 100644
index 0000000..b668b77
--- /dev/null
+++ b/chrome/common/extensions/feature_switch.h
@@ -0,0 +1,62 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_FEATURE_SWITCH_H_
+#define CHROME_COMMON_EXTENSIONS_FEATURE_SWITCH_H_
+
+#include "base/basictypes.h"
+
+class CommandLine;
+
+namespace extensions {
+
+// A switch that can turn a feature on or off. Typically controlled via
+// command-line switches but can be overridden, e.g., for testing.
+class FeatureSwitch {
+ public:
+ static FeatureSwitch* GetScriptBubble();
+
+ // A temporary override for the switch value.
+ class ScopedOverride {
+ public:
+ ScopedOverride(FeatureSwitch* feature, bool override_value);
+ ~ScopedOverride();
+ private:
+ friend FeatureSwitch;
+ FeatureSwitch* feature_;
+ bool previous_value_;
+ DISALLOW_COPY_AND_ASSIGN(ScopedOverride);
+ };
+
+ enum DefaultValue {
+ DEFAULT_ENABLED,
+ DEFAULT_DISABLED
+ };
+
+ FeatureSwitch(const CommandLine* command_line,
+ const char* switch_name,
+ DefaultValue default_value);
+
+ bool IsEnabled() const;
+
+ private:
+ enum OverrideValue {
+ OVERRIDE_NONE,
+ OVERRIDE_ENABLED,
+ OVERRIDE_DISABLED
+ };
+
+ void SetOverrideValue(OverrideValue value);
+
+ const CommandLine* command_line_;
+ const char* switch_name_;
+ bool default_value_;
+ OverrideValue override_value_;
+
+ DISALLOW_COPY_AND_ASSIGN(FeatureSwitch);
+};
+
+} // namespace extensions
+
+#endif // CHROME_COMMON_EXTENSIONS_FEATURE_SWITCH_H_
diff --git a/chrome/common/extensions/feature_switch_unittest.cc b/chrome/common/extensions/feature_switch_unittest.cc
new file mode 100644
index 0000000..4c7e204
--- /dev/null
+++ b/chrome/common/extensions/feature_switch_unittest.cc
@@ -0,0 +1,134 @@
+// Copyright (c) 2012 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/common/extensions/feature_switch.h"
+
+#include "base/command_line.h"
+#include "base/memory/scoped_ptr.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using extensions::FeatureSwitch;
+
+namespace {
+
+const char kSwitchName[] = "test-switch";
+
+template<FeatureSwitch::DefaultValue T>
+class FeatureSwitchTest : public testing::Test {
+ public:
+ FeatureSwitchTest()
+ : command_line_(CommandLine::NO_PROGRAM),
+ feature_(&command_line_, kSwitchName, T) {
+ }
+ protected:
+ CommandLine command_line_;
+ FeatureSwitch feature_;
+};
+
+typedef FeatureSwitchTest<FeatureSwitch::DEFAULT_DISABLED>
+ FeatureSwitchDisabledTest;
+typedef FeatureSwitchTest<FeatureSwitch::DEFAULT_ENABLED>
+ FeatureSwitchEnabledTest;
+
+} // namespace
+
+TEST_F(FeatureSwitchDisabledTest, NoSwitchValue) {
+ EXPECT_FALSE(feature_.IsEnabled());
+}
+
+TEST_F(FeatureSwitchDisabledTest, FalseSwitchValue) {
+ command_line_.AppendSwitchASCII(kSwitchName, "0");
+ EXPECT_FALSE(feature_.IsEnabled());
+}
+
+TEST_F(FeatureSwitchDisabledTest, GibberishSwitchValue) {
+ command_line_.AppendSwitchASCII(kSwitchName, "monkey");
+ EXPECT_FALSE(feature_.IsEnabled());
+}
+
+TEST_F(FeatureSwitchDisabledTest, Override) {
+ {
+ FeatureSwitch::ScopedOverride override(&feature_, false);
+ EXPECT_FALSE(feature_.IsEnabled());
+ }
+ EXPECT_FALSE(feature_.IsEnabled());
+
+ {
+ FeatureSwitch::ScopedOverride override(&feature_, true);
+ EXPECT_TRUE(feature_.IsEnabled());
+ }
+ EXPECT_FALSE(feature_.IsEnabled());
+}
+
+TEST_F(FeatureSwitchDisabledTest, TrueSwitchValue) {
+ command_line_.AppendSwitchASCII(kSwitchName, "1");
+ EXPECT_TRUE(feature_.IsEnabled());
+
+ {
+ FeatureSwitch::ScopedOverride override(&feature_, false);
+ EXPECT_FALSE(feature_.IsEnabled());
+ }
+ EXPECT_TRUE(feature_.IsEnabled());
+
+ {
+ FeatureSwitch::ScopedOverride override(&feature_, true);
+ EXPECT_TRUE(feature_.IsEnabled());
+ }
+ EXPECT_TRUE(feature_.IsEnabled());
+}
+
+TEST_F(FeatureSwitchDisabledTest, TrimSwitchValue) {
+ command_line_.AppendSwitchASCII(kSwitchName, " \t 1\n ");
+ EXPECT_TRUE(feature_.IsEnabled());
+}
+
+TEST_F(FeatureSwitchEnabledTest, NoSwitchValue) {
+ EXPECT_TRUE(feature_.IsEnabled());
+}
+
+TEST_F(FeatureSwitchEnabledTest, TrueSwitchValue) {
+ command_line_.AppendSwitchASCII(kSwitchName, "1");
+ EXPECT_TRUE(feature_.IsEnabled());
+}
+
+TEST_F(FeatureSwitchEnabledTest, GibberishSwitchValue) {
+ command_line_.AppendSwitchASCII(kSwitchName, "monkey");
+ EXPECT_TRUE(feature_.IsEnabled());
+}
+
+TEST_F(FeatureSwitchEnabledTest, Override) {
+ {
+ FeatureSwitch::ScopedOverride override(&feature_, true);
+ EXPECT_TRUE(feature_.IsEnabled());
+ }
+ EXPECT_TRUE(feature_.IsEnabled());
+
+ {
+ FeatureSwitch::ScopedOverride override(&feature_, false);
+ EXPECT_FALSE(feature_.IsEnabled());
+ }
+ EXPECT_TRUE(feature_.IsEnabled());
+}
+
+TEST_F(FeatureSwitchEnabledTest, FalseSwitchValue) {
+ command_line_.AppendSwitchASCII(kSwitchName, "0");
+ EXPECT_FALSE(feature_.IsEnabled());
+
+ {
+ FeatureSwitch::ScopedOverride override(&feature_, true);
+ EXPECT_TRUE(feature_.IsEnabled());
+ }
+ EXPECT_FALSE(feature_.IsEnabled());
+
+ {
+ FeatureSwitch::ScopedOverride override(&feature_, false);
+ EXPECT_FALSE(feature_.IsEnabled());
+ }
+ EXPECT_FALSE(feature_.IsEnabled());
+}
+
+TEST_F(FeatureSwitchEnabledTest, TrimSwitchValue) {
+ command_line_.AppendSwitchASCII(kSwitchName, "\t\t 0 \n");
+ EXPECT_FALSE(feature_.IsEnabled());
+}