diff options
Diffstat (limited to 'chrome')
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 Binary files differnew file mode 100644 index 0000000..7f531fc --- /dev/null +++ b/chrome/browser/resources/script_bubble/16.png 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()); +} |