summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrdevlin.cronin <rdevlin.cronin@chromium.org>2015-10-29 10:05:56 -0700
committerCommit bot <commit-bot@chromium.org>2015-10-29 17:06:54 +0000
commitb2e7ac04ba48e24f04034aed81f0140505d09259 (patch)
tree61c57162b8a5acf74852375df0d83a3f7b16f36a
parentcfd8141a4f2b970a8c9ba6d70a541a4490011284 (diff)
downloadchromium_src-b2e7ac04ba48e24f04034aed81f0140505d09259.zip
chromium_src-b2e7ac04ba48e24f04034aed81f0140505d09259.tar.gz
chromium_src-b2e7ac04ba48e24f04034aed81f0140505d09259.tar.bz2
[Extensions] Expand the click-to-script context menu item
This replaces the vaguely-named "always run" context menu item with a submenu for page access, including always running on the current site, always running on all sites, and waiting for user action to run. BUG=TBD Review URL: https://codereview.chromium.org/1420113003 Cr-Commit-Position: refs/heads/master@{#356872}
-rw-r--r--chrome/app/generated_resources.grd26
-rw-r--r--chrome/browser/extensions/extension_context_menu_model.cc135
-rw-r--r--chrome/browser/extensions/extension_context_menu_model.h23
-rw-r--r--chrome/browser/extensions/extension_context_menu_model_unittest.cc378
4 files changed, 425 insertions, 137 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index efcc234..46f3d12 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -4635,8 +4635,17 @@ Even if you have downloaded files from this website before, the website might ha
Keyboard shortcuts
</message>
<if expr="not use_titlecase">
- <message name="IDS_EXTENSIONS_ALWAYS_RUN" desc="The text for the 'always run' item in context menus (sentence case).">
- Always run
+ <message name="IDS_EXTENSIONS_CONTEXT_MENU_PAGE_ACCESS" desc="The label in an extension's context menu for the submenu specifying whether or not the extension can run on the current page (sentence case).">
+ Page access
+ </message>
+ <message name="IDS_EXTENSIONS_CONTEXT_MENU_PAGE_ACCESS_RUN_ON_CLICK" desc="The label in an extension's context menu to allow the extension access to the page only when the user clicks on the extension action (sentence case).">
+ Run on click
+ </message>
+ <message name="IDS_EXTENSIONS_CONTEXT_MENU_PAGE_ACCESS_RUN_ON_SITE" desc="The label in an extension's context menu to always allow the extension access to the current origin (sentence case).">
+ Always run on <ph name="ORIGIN">$1<ex>google.com</ex></ph>
+ </message>
+ <message name="IDS_EXTENSIONS_CONTEXT_MENU_PAGE_ACCESS_RUN_ON_ALL_SITES" desc="The label in an extension's context menu to always allow the extension access on all sites (sentence case).">
+ Always run on all sites
</message>
<message name="IDS_EXTENSIONS_OPTIONS_MENU_ITEM" desc="The text for the options menu item in context menus (sentence case).">
Options
@@ -4664,8 +4673,17 @@ Even if you have downloaded files from this website before, the website might ha
</message>
</if>
<if expr="use_titlecase">
- <message name="IDS_EXTENSIONS_ALWAYS_RUN" desc="The text for the 'always run' item in context menus (title case).">
- Always Run
+ <message name="IDS_EXTENSIONS_CONTEXT_MENU_PAGE_ACCESS" desc="The label in an extension's context menu for the submenu specifying whether or not the extension can run on the current page (sentence case).">
+ Page Access
+ </message>
+ <message name="IDS_EXTENSIONS_CONTEXT_MENU_PAGE_ACCESS_RUN_ON_CLICK" desc="The label in an extension's context menu to allow the extension access to the page only when the user clicks on the extension action (sentence case).">
+ Run on Click
+ </message>
+ <message name="IDS_EXTENSIONS_CONTEXT_MENU_PAGE_ACCESS_RUN_ON_SITE" desc="The label in an extension's context menu to always allow the extension access to the current origin (sentence case).">
+ Always Run on <ph name="ORIGIN">$1<ex>google.com</ex></ph>
+ </message>
+ <message name="IDS_EXTENSIONS_CONTEXT_MENU_PAGE_ACCESS_RUN_ON_ALL_SITES" desc="The label in an extension's context menu to always allow the extension access on all sites (sentence case).">
+ Always Run on All Sites
</message>
<message name="IDS_EXTENSIONS_OPTIONS_MENU_ITEM" desc="The text for the options menu item in context menus (title case).">
Options
diff --git a/chrome/browser/extensions/extension_context_menu_model.cc b/chrome/browser/extensions/extension_context_menu_model.cc
index 27ef17b..605a53c 100644
--- a/chrome/browser/extensions/extension_context_menu_model.cc
+++ b/chrome/browser/extensions/extension_context_menu_model.cc
@@ -13,6 +13,7 @@
#include "chrome/browser/extensions/extension_action_manager.h"
#include "chrome/browser/extensions/extension_tab_util.h"
#include "chrome/browser/extensions/extension_uninstall_dialog.h"
+#include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/extensions/menu_manager.h"
#include "chrome/browser/extensions/scripting_permissions_modifier.h"
#include "chrome/browser/profiles/profile.h"
@@ -162,15 +163,23 @@ ExtensionContextMenuModel::ExtensionContextMenuModel(
InitMenu(extension, button_visibility);
}
-ExtensionContextMenuModel::ExtensionContextMenuModel(const Extension* extension,
- Browser* browser)
- : ExtensionContextMenuModel(extension, browser, VISIBLE, nullptr) {
-}
-
bool ExtensionContextMenuModel::IsCommandIdChecked(int command_id) const {
+ const Extension* extension = GetExtension();
+ if (!extension)
+ return false;
+
if (command_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST &&
command_id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST)
return extension_items_->IsCommandIdChecked(command_id);
+
+ if (command_id == PAGE_ACCESS_RUN_ON_CLICK ||
+ command_id == PAGE_ACCESS_RUN_ON_SITE ||
+ command_id == PAGE_ACCESS_RUN_ON_ALL_SITES) {
+ content::WebContents* web_contents = GetActiveWebContents();
+ return web_contents &&
+ GetCurrentPageAccess(extension, web_contents) == command_id;
+ }
+
return false;
}
@@ -205,7 +214,10 @@ bool ExtensionContextMenuModel::IsCommandIdEnabled(int command_id) const {
// The following, if they are present, are always enabled.
case TOGGLE_VISIBILITY:
case MANAGE:
- case ALWAYS_RUN:
+ case PAGE_ACCESS_SUBMENU:
+ case PAGE_ACCESS_RUN_ON_CLICK:
+ case PAGE_ACCESS_RUN_ON_SITE:
+ case PAGE_ACCESS_RUN_ON_ALL_SITES:
return true;
default:
NOTREACHED() << "Unknown command" << command_id;
@@ -240,16 +252,6 @@ void ExtensionContextMenuModel::ExecuteCommand(int command_id,
browser_->OpenURL(params);
break;
}
- case ALWAYS_RUN: {
- content::WebContents* web_contents = GetActiveWebContents();
- if (web_contents) {
- ScriptingPermissionsModifier(profile_, extension)
- .GrantHostPermission(web_contents->GetLastCommittedURL());
- ActiveScriptController::GetForWebContents(web_contents)
- ->OnClicked(extension);
- }
- break;
- }
case CONFIGURE:
DCHECK(OptionsPageInfo::HasOptionsPage(extension));
ExtensionTabUtil::OpenOptionsPage(extension, browser_);
@@ -277,6 +279,11 @@ void ExtensionContextMenuModel::ExecuteCommand(int command_id,
delegate_->InspectPopup();
break;
}
+ case PAGE_ACCESS_RUN_ON_CLICK:
+ case PAGE_ACCESS_RUN_ON_SITE:
+ case PAGE_ACCESS_RUN_ON_ALL_SITES:
+ HandlePageAccessCommand(command_id, extension);
+ break;
default:
NOTREACHED() << "Unknown option";
break;
@@ -308,16 +315,7 @@ void ExtensionContextMenuModel::InitMenu(const Extension* extension,
AppendExtensionItems();
AddSeparator(ui::NORMAL_SEPARATOR);
- // Add the "Always Allow" item for adding persisted permissions for script
- // injections if there is an active action for this extension. Note that this
- // will add it to *all* extension action context menus, not just the one
- // attached to the script injection request icon, but that's okay.
- content::WebContents* web_contents = GetActiveWebContents();
- if (web_contents &&
- ActiveScriptController::GetForWebContents(web_contents)
- ->WantsToRun(extension)) {
- AddItemWithStringId(ALWAYS_RUN, IDS_EXTENSIONS_ALWAYS_RUN);
- }
+ CreatePageAccessSubmenu(extension);
if (!is_component_ || OptionsPageInfo::HasOptionsPage(extension))
AddItemWithStringId(CONFIGURE, IDS_EXTENSIONS_OPTIONS_MENU_ITEM);
@@ -374,6 +372,91 @@ void ExtensionContextMenuModel::AppendExtensionItems() {
true); // is_action_menu
}
+ExtensionContextMenuModel::MenuEntries
+ExtensionContextMenuModel::GetCurrentPageAccess(
+ const Extension* extension,
+ content::WebContents* web_contents) const {
+ ScriptingPermissionsModifier modifier(profile_, extension);
+ DCHECK(modifier.HasAffectedExtension());
+ if (util::AllowedScriptingOnAllUrls(extension->id(), profile_))
+ return PAGE_ACCESS_RUN_ON_ALL_SITES;
+ if (modifier.HasGrantedHostPermission(
+ GetActiveWebContents()->GetLastCommittedURL()))
+ return PAGE_ACCESS_RUN_ON_SITE;
+ return PAGE_ACCESS_RUN_ON_CLICK;
+}
+
+void ExtensionContextMenuModel::CreatePageAccessSubmenu(
+ const Extension* extension) {
+ content::WebContents* web_contents = GetActiveWebContents();
+ if (!web_contents ||
+ !ScriptingPermissionsModifier(profile_, extension)
+ .HasAffectedExtension()) {
+ return;
+ }
+ page_access_submenu_.reset(new ui::SimpleMenuModel(this));
+ const int kRadioGroup = 0;
+ page_access_submenu_->AddRadioItemWithStringId(
+ PAGE_ACCESS_RUN_ON_CLICK,
+ IDS_EXTENSIONS_CONTEXT_MENU_PAGE_ACCESS_RUN_ON_CLICK, kRadioGroup);
+ page_access_submenu_->AddRadioItem(
+ PAGE_ACCESS_RUN_ON_SITE,
+ l10n_util::GetStringFUTF16(
+ IDS_EXTENSIONS_CONTEXT_MENU_PAGE_ACCESS_RUN_ON_SITE,
+ base::UTF8ToUTF16(
+ web_contents->GetLastCommittedURL().GetOrigin().spec())),
+ kRadioGroup);
+ page_access_submenu_->AddRadioItemWithStringId(
+ PAGE_ACCESS_RUN_ON_ALL_SITES,
+ IDS_EXTENSIONS_CONTEXT_MENU_PAGE_ACCESS_RUN_ON_ALL_SITES, kRadioGroup);
+
+ AddSubMenuWithStringId(PAGE_ACCESS_SUBMENU,
+ IDS_EXTENSIONS_CONTEXT_MENU_PAGE_ACCESS,
+ page_access_submenu_.get());
+}
+
+void ExtensionContextMenuModel::HandlePageAccessCommand(
+ int command_id,
+ const Extension* extension) const {
+ content::WebContents* web_contents = GetActiveWebContents();
+ if (!web_contents)
+ return;
+
+ MenuEntries current_access = GetCurrentPageAccess(extension, web_contents);
+ if (command_id == current_access)
+ return;
+
+ const GURL& url = web_contents->GetLastCommittedURL();
+ ScriptingPermissionsModifier modifier(profile_, extension);
+ switch (command_id) {
+ case PAGE_ACCESS_RUN_ON_CLICK:
+ if (current_access == PAGE_ACCESS_RUN_ON_ALL_SITES)
+ util::SetAllowedScriptingOnAllUrls(extension->id(), profile_, false);
+ if (modifier.HasGrantedHostPermission(url))
+ modifier.RemoveGrantedHostPermission(url);
+ break;
+ case PAGE_ACCESS_RUN_ON_SITE:
+ if (current_access == PAGE_ACCESS_RUN_ON_ALL_SITES)
+ util::SetAllowedScriptingOnAllUrls(extension->id(), profile_, false);
+ if (!modifier.HasGrantedHostPermission(url))
+ modifier.GrantHostPermission(url);
+ break;
+ case PAGE_ACCESS_RUN_ON_ALL_SITES:
+ util::SetAllowedScriptingOnAllUrls(extension->id(), profile_, true);
+ break;
+ default:
+ NOTREACHED();
+ }
+
+ if (command_id == PAGE_ACCESS_RUN_ON_SITE ||
+ command_id == PAGE_ACCESS_RUN_ON_ALL_SITES) {
+ ActiveScriptController* controller =
+ ActiveScriptController::GetForWebContents(web_contents);
+ if (controller && controller->WantsToRun(extension))
+ controller->OnClicked(extension);
+ }
+}
+
content::WebContents* ExtensionContextMenuModel::GetActiveWebContents() const {
return browser_->tab_strip_model()->GetActiveWebContents();
}
diff --git a/chrome/browser/extensions/extension_context_menu_model.h b/chrome/browser/extensions/extension_context_menu_model.h
index 3fa55f4..1a099f9 100644
--- a/chrome/browser/extensions/extension_context_menu_model.h
+++ b/chrome/browser/extensions/extension_context_menu_model.h
@@ -34,7 +34,10 @@ class ExtensionContextMenuModel : public ui::SimpleMenuModel,
UNINSTALL,
MANAGE,
INSPECT_POPUP,
- ALWAYS_RUN
+ PAGE_ACCESS_SUBMENU,
+ PAGE_ACCESS_RUN_ON_CLICK,
+ PAGE_ACCESS_RUN_ON_SITE,
+ PAGE_ACCESS_RUN_ON_ALL_SITES,
};
// Type of action the extension icon represents.
@@ -73,10 +76,6 @@ class ExtensionContextMenuModel : public ui::SimpleMenuModel,
PopupDelegate* delegate);
~ExtensionContextMenuModel() override;
- // Create a menu model for the given extension, without support
- // for the "Inspect Popup" command.
- ExtensionContextMenuModel(const Extension* extension, Browser* browser);
-
// SimpleMenuModel::Delegate:
bool IsCommandIdChecked(int command_id) const override;
bool IsCommandIdEnabled(int command_id) const override;
@@ -84,9 +83,21 @@ class ExtensionContextMenuModel : public ui::SimpleMenuModel,
ui::Accelerator* accelerator) override;
void ExecuteCommand(int command_id, int event_flags) override;
+ ui::SimpleMenuModel* page_access_submenu_for_testing() {
+ return page_access_submenu_.get();
+ }
+
private:
void InitMenu(const Extension* extension, ButtonVisibility button_visibility);
+ void CreatePageAccessSubmenu(const Extension* extension);
+
+ MenuEntries GetCurrentPageAccess(const Extension* extension,
+ content::WebContents* web_contents) const;
+
+ void HandlePageAccessCommand(int command_id,
+ const Extension* extension) const;
+
// Gets the extension we are displaying the menu for. Returns NULL if the
// extension has been uninstalled and no longer exists.
const Extension* GetExtension() const;
@@ -123,6 +134,8 @@ class ExtensionContextMenuModel : public ui::SimpleMenuModel,
// Menu matcher for context menu items specified by the extension.
scoped_ptr<ContextMenuMatcher> extension_items_;
+ scoped_ptr<ui::SimpleMenuModel> page_access_submenu_;
+
DISALLOW_COPY_AND_ASSIGN(ExtensionContextMenuModel);
};
diff --git a/chrome/browser/extensions/extension_context_menu_model_unittest.cc b/chrome/browser/extensions/extension_context_menu_model_unittest.cc
index ecdd3ba..c1469db 100644
--- a/chrome/browser/extensions/extension_context_menu_model_unittest.cc
+++ b/chrome/browser/extensions/extension_context_menu_model_unittest.cc
@@ -6,20 +6,25 @@
#include "base/strings/utf_string_conversions.h"
#include "chrome/app/chrome_command_ids.h"
+#include "chrome/browser/extensions/active_script_controller.h"
#include "chrome/browser/extensions/api/extension_action/extension_action_api.h"
#include "chrome/browser/extensions/extension_action_test_util.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_service_test_base.h"
#include "chrome/browser/extensions/menu_manager.h"
#include "chrome/browser/extensions/menu_manager_factory.h"
+#include "chrome/browser/extensions/scripting_permissions_modifier.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/host_desktop.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/extensions/api/context_menus.h"
#include "chrome/grit/chromium_strings.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/test/base/test_browser_window.h"
#include "chrome/test/base/testing_profile.h"
#include "components/crx_file/id_util.h"
+#include "content/public/test/test_web_contents_factory.h"
+#include "content/public/test/web_contents_tester.h"
#include "extensions/browser/extension_dialog_auto_confirm.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
@@ -30,6 +35,7 @@
#include "extensions/common/manifest.h"
#include "extensions/common/manifest_constants.h"
#include "extensions/common/manifest_handlers/options_page_info.h"
+#include "extensions/common/permissions/permissions_data.h"
#include "extensions/common/value_builder.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/l10n/l10n_util.h"
@@ -39,25 +45,14 @@ namespace extensions {
namespace {
+void Increment(int* i) {
+ CHECK(i);
+ ++(*i);
+}
+
// Label for test extension menu item.
const char* kTestExtensionItemLabel = "test-ext-item";
-// Build an extension to pass to the menu constructor, with the an action
-// specified by |action_key|.
-scoped_refptr<const Extension> BuildExtension(const std::string& name,
- const char* action_key,
- Manifest::Location location) {
- return ExtensionBuilder()
- .SetManifest(DictionaryBuilder()
- .Set("name", name)
- .Set("version", "1")
- .Set("manifest_version", 2)
- .Set(action_key, DictionaryBuilder().Pass()))
- .SetID(crx_file::id_util::GenerateId(name))
- .SetLocation(location)
- .Build();
-}
-
class MenuBuilder {
public:
MenuBuilder(scoped_refptr<const Extension> extension,
@@ -70,8 +65,9 @@ class MenuBuilder {
~MenuBuilder() {}
scoped_ptr<ExtensionContextMenuModel> BuildMenu() {
- return make_scoped_ptr(
- new ExtensionContextMenuModel(extension_.get(), browser_));
+ return make_scoped_ptr(new ExtensionContextMenuModel(
+ extension_.get(), browser_, ExtensionContextMenuModel::VISIBLE,
+ nullptr));
}
void AddContextItem(MenuItem::Context context) {
@@ -95,15 +91,25 @@ class MenuBuilder {
DISALLOW_COPY_AND_ASSIGN(MenuBuilder);
};
-// Returns the index of the given |command_id| in the given |menu|, or -1 if it
-// is not found.
-int GetCommandIndex(const ExtensionContextMenuModel& menu, int command_id) {
- int item_count = menu.GetItemCount();
- for (int i = 0; i < item_count; ++i) {
- if (menu.GetCommandIdAt(i) == command_id)
- return i;
+// Returns the number of extension menu items that show up in |model|.
+// For this test, all the extension items have same label
+// |kTestExtensionItemLabel|.
+int CountExtensionItems(const ExtensionContextMenuModel& model) {
+ base::string16 expected_label = base::ASCIIToUTF16(kTestExtensionItemLabel);
+ int num_items_found = 0;
+ int num_custom_found = 0;
+ for (int i = 0; i < model.GetItemCount(); ++i) {
+ if (expected_label == model.GetLabelAt(i))
+ ++num_items_found;
+ int command_id = model.GetCommandIdAt(i);
+ if (command_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST &&
+ command_id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST)
+ ++num_custom_found;
}
- return -1;
+ // The only custom extension items present on the menu should be those we
+ // added in the test.
+ EXPECT_EQ(num_items_found, num_custom_found);
+ return num_items_found;
}
} // namespace
@@ -112,10 +118,16 @@ class ExtensionContextMenuModelTest : public ExtensionServiceTestBase {
public:
ExtensionContextMenuModelTest();
- // Returns the number of extension menu items that show up in |model|.
- // For this test, all the extension items have samel label
- // |kTestExtensionItemLabel|.
- int CountExtensionItems(const ExtensionContextMenuModel& model);
+ // Build an extension to pass to the menu constructor, with the action
+ // specified by |action_key|.
+ const Extension* AddExtension(const std::string& name,
+ const char* action_key,
+ Manifest::Location location);
+ const Extension* AddExtensionWithHostPermission(
+ const std::string& name,
+ const char* action_key,
+ Manifest::Location location,
+ const std::string& host_permission);
Browser* GetBrowser();
@@ -128,23 +140,36 @@ class ExtensionContextMenuModelTest : public ExtensionServiceTestBase {
ExtensionContextMenuModelTest::ExtensionContextMenuModelTest() {}
-int ExtensionContextMenuModelTest::CountExtensionItems(
- const ExtensionContextMenuModel& model) {
- base::string16 expected_label = base::ASCIIToUTF16(kTestExtensionItemLabel);
- int num_items_found = 0;
- int num_custom_found = 0;
- for (int i = 0; i < model.GetItemCount(); ++i) {
- if (expected_label == model.GetLabelAt(i))
- ++num_items_found;
- int command_id = model.GetCommandIdAt(i);
- if (command_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST &&
- command_id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST)
- ++num_custom_found;
- }
- // The only custom extension items present on the menu should be those we
- // added in the test.
- EXPECT_EQ(num_items_found, num_custom_found);
- return num_items_found;
+const Extension* ExtensionContextMenuModelTest::AddExtension(
+ const std::string& name,
+ const char* action_key,
+ Manifest::Location location) {
+ return AddExtensionWithHostPermission(name, action_key, location,
+ std::string());
+}
+
+const Extension* ExtensionContextMenuModelTest::AddExtensionWithHostPermission(
+ const std::string& name,
+ const char* action_key,
+ Manifest::Location location,
+ const std::string& host_permission) {
+ DictionaryBuilder manifest;
+ manifest.Set("name", name)
+ .Set("version", "1")
+ .Set("manifest_version", 2)
+ .Set(action_key, DictionaryBuilder().Pass());
+ if (!host_permission.empty())
+ manifest.Set("permissions", ListBuilder().Append(host_permission));
+ scoped_refptr<const Extension> extension =
+ ExtensionBuilder()
+ .SetManifest(manifest.Pass())
+ .SetID(crx_file::id_util::GenerateId(name))
+ .SetLocation(location)
+ .Build();
+ if (!extension.get())
+ ADD_FAILURE();
+ service()->AddExtension(extension.get());
+ return extension.get();
}
Browser* ExtensionContextMenuModelTest::GetBrowser() {
@@ -164,14 +189,11 @@ TEST_F(ExtensionContextMenuModelTest, RequiredInstallationsDisablesItems) {
// Test that management policy can determine whether or not policy-installed
// extensions can be installed/uninstalled.
- scoped_refptr<const Extension> extension =
- BuildExtension("extension",
- manifest_keys::kPageAction,
- Manifest::EXTERNAL_POLICY);
- ASSERT_TRUE(extension.get());
- service()->AddExtension(extension.get());
+ const Extension* extension = AddExtension(
+ "extension", manifest_keys::kPageAction, Manifest::EXTERNAL_POLICY);
- ExtensionContextMenuModel menu(extension.get(), GetBrowser());
+ ExtensionContextMenuModel menu(extension, GetBrowser(),
+ ExtensionContextMenuModel::VISIBLE, nullptr);
ExtensionSystem* system = ExtensionSystem::Get(profile());
system->management_policy()->UnregisterAllProviders();
@@ -219,7 +241,8 @@ TEST_F(ExtensionContextMenuModelTest, ComponentExtensionContextMenu) {
.Build();
service()->AddExtension(extension.get());
- ExtensionContextMenuModel menu(extension.get(), GetBrowser());
+ ExtensionContextMenuModel menu(extension.get(), GetBrowser(),
+ ExtensionContextMenuModel::VISIBLE, nullptr);
// A component extension's context menu should not include options for
// managing extensions or removing it, and should only include an option for
@@ -246,7 +269,8 @@ TEST_F(ExtensionContextMenuModelTest, ComponentExtensionContextMenu) {
.SetID(crx_file::id_util::GenerateId("component_opts"))
.SetLocation(Manifest::COMPONENT)
.Build();
- ExtensionContextMenuModel menu(extension.get(), GetBrowser());
+ ExtensionContextMenuModel menu(extension.get(), GetBrowser(),
+ ExtensionContextMenuModel::VISIBLE, nullptr);
service()->AddExtension(extension.get());
EXPECT_TRUE(extensions::OptionsPageInfo::HasOptionsPage(extension.get()));
EXPECT_NE(-1,
@@ -257,12 +281,8 @@ TEST_F(ExtensionContextMenuModelTest, ComponentExtensionContextMenu) {
TEST_F(ExtensionContextMenuModelTest, ExtensionItemTest) {
InitializeEmptyExtensionService();
- scoped_refptr<const Extension> extension =
- BuildExtension("extension",
- manifest_keys::kPageAction,
- Manifest::INTERNAL);
- ASSERT_TRUE(extension.get());
- service()->AddExtension(extension.get());
+ const Extension* extension =
+ AddExtension("extension", manifest_keys::kPageAction, Manifest::INTERNAL);
// Create a MenuManager for adding context items.
MenuManager* manager = static_cast<MenuManager*>(
@@ -308,19 +328,12 @@ TEST_F(ExtensionContextMenuModelTest, ExtensionContextMenuShowAndHideLegacy) {
InitializeEmptyExtensionService();
Browser* browser = GetBrowser();
extension_action_test_util::CreateToolbarModelForProfile(profile());
- scoped_refptr<const Extension> page_action =
- BuildExtension("page_action_extension",
- manifest_keys::kPageAction,
- Manifest::INTERNAL);
- ASSERT_TRUE(page_action.get());
- scoped_refptr<const Extension> browser_action =
- BuildExtension("browser_action_extension",
- manifest_keys::kBrowserAction,
- Manifest::INTERNAL);
- ASSERT_TRUE(browser_action.get());
- service()->AddExtension(page_action.get());
- service()->AddExtension(browser_action.get());
+ const Extension* page_action = AddExtension(
+ "page_action_extension", manifest_keys::kPageAction, Manifest::INTERNAL);
+ const Extension* browser_action =
+ AddExtension("browser_action_extension", manifest_keys::kBrowserAction,
+ Manifest::INTERNAL);
// For laziness.
const ExtensionContextMenuModel::MenuEntries visibility_command =
@@ -329,19 +342,18 @@ TEST_F(ExtensionContextMenuModelTest, ExtensionContextMenuShowAndHideLegacy) {
l10n_util::GetStringUTF16(IDS_EXTENSIONS_HIDE_BUTTON);
{
- ExtensionContextMenuModel menu(page_action.get(), browser,
+ ExtensionContextMenuModel menu(page_action, browser,
ExtensionContextMenuModel::VISIBLE, nullptr);
- int index = GetCommandIndex(menu, visibility_command);
// Without the toolbar redesign switch, page action menus shouldn't have a
// visibility option.
- EXPECT_EQ(-1, index);
+ EXPECT_EQ(-1, menu.GetIndexOfCommandId(visibility_command));
}
{
- ExtensionContextMenuModel menu(browser_action.get(), browser,
+ ExtensionContextMenuModel menu(browser_action, browser,
ExtensionContextMenuModel::VISIBLE, nullptr);
- int index = GetCommandIndex(menu, visibility_command);
+ int index = menu.GetIndexOfCommandId(visibility_command);
// Browser actions should have the visibility option.
EXPECT_NE(-1, index);
// Since the action is currently visible, it should have the option to hide
@@ -350,10 +362,10 @@ TEST_F(ExtensionContextMenuModelTest, ExtensionContextMenuShowAndHideLegacy) {
}
{
- ExtensionContextMenuModel menu(browser_action.get(), browser,
+ ExtensionContextMenuModel menu(browser_action, browser,
ExtensionContextMenuModel::OVERFLOWED,
nullptr);
- int index = GetCommandIndex(menu, visibility_command);
+ int index = menu.GetIndexOfCommandId(visibility_command);
EXPECT_NE(-1, index);
// Without the redesign, 'hiding' refers to removing the action from the
// toolbar entirely, so even with the action overflowed, the string should
@@ -383,19 +395,14 @@ TEST_F(ExtensionContextMenuModelTest, ExtensionContextMenuShowAndHideRedesign) {
InitializeEmptyExtensionService();
Browser* browser = GetBrowser();
extension_action_test_util::CreateToolbarModelForProfile(profile());
- scoped_refptr<const Extension> page_action =
- BuildExtension("page_action_extension",
+ const Extension* page_action =
+ AddExtension("page_action_extension",
manifest_keys::kPageAction,
Manifest::INTERNAL);
- ASSERT_TRUE(page_action.get());
- scoped_refptr<const Extension> browser_action =
- BuildExtension("browser_action_extension",
+ const Extension* browser_action =
+ AddExtension("browser_action_extension",
manifest_keys::kBrowserAction,
Manifest::INTERNAL);
- ASSERT_TRUE(browser_action.get());
-
- service()->AddExtension(page_action.get());
- service()->AddExtension(browser_action.get());
// For laziness.
const ExtensionContextMenuModel::MenuEntries visibility_command =
@@ -409,17 +416,17 @@ TEST_F(ExtensionContextMenuModelTest, ExtensionContextMenuShowAndHideRedesign) {
{
// Even page actions should have a visibility option with the redesign on.
- ExtensionContextMenuModel menu(page_action.get(), browser,
+ ExtensionContextMenuModel menu(page_action, browser,
ExtensionContextMenuModel::VISIBLE, nullptr);
- int index = GetCommandIndex(menu, visibility_command);
+ int index = menu.GetIndexOfCommandId(visibility_command);
EXPECT_NE(-1, index);
EXPECT_EQ(redesign_hide_string, menu.GetLabelAt(index));
}
{
- ExtensionContextMenuModel menu(browser_action.get(), browser,
+ ExtensionContextMenuModel menu(browser_action, browser,
ExtensionContextMenuModel::VISIBLE, nullptr);
- int index = GetCommandIndex(menu, visibility_command);
+ int index = menu.GetIndexOfCommandId(visibility_command);
EXPECT_NE(-1, index);
EXPECT_EQ(redesign_hide_string, menu.GetLabelAt(index));
@@ -434,10 +441,10 @@ TEST_F(ExtensionContextMenuModelTest, ExtensionContextMenuShowAndHideRedesign) {
{
// If the action is overflowed, it should have the "Show button in toolbar"
// string.
- ExtensionContextMenuModel menu(browser_action.get(), browser,
+ ExtensionContextMenuModel menu(browser_action, browser,
ExtensionContextMenuModel::OVERFLOWED,
nullptr);
- int index = GetCommandIndex(menu, visibility_command);
+ int index = menu.GetIndexOfCommandId(visibility_command);
EXPECT_NE(-1, index);
EXPECT_EQ(redesign_show_string, menu.GetLabelAt(index));
}
@@ -446,9 +453,9 @@ TEST_F(ExtensionContextMenuModelTest, ExtensionContextMenuShowAndHideRedesign) {
// If the action is transitively visible, as happens when it is showing a
// popup, we should use a "Keep button in toolbar" string.
ExtensionContextMenuModel menu(
- browser_action.get(), browser,
+ browser_action, browser,
ExtensionContextMenuModel::TRANSITIVELY_VISIBLE, nullptr);
- int index = GetCommandIndex(menu, visibility_command);
+ int index = menu.GetIndexOfCommandId(visibility_command);
EXPECT_NE(-1, index);
EXPECT_EQ(redesign_keep_string, menu.GetLabelAt(index));
}
@@ -457,10 +464,8 @@ TEST_F(ExtensionContextMenuModelTest, ExtensionContextMenuShowAndHideRedesign) {
TEST_F(ExtensionContextMenuModelTest, ExtensionContextUninstall) {
InitializeEmptyExtensionService();
- scoped_refptr<const Extension> extension = BuildExtension(
+ const Extension* extension = AddExtension(
"extension", manifest_keys::kBrowserAction, Manifest::INTERNAL);
- ASSERT_TRUE(extension.get());
- service()->AddExtension(extension.get());
const std::string extension_id = extension->id();
ASSERT_TRUE(registry()->enabled_extensions().GetByID(extension_id));
@@ -470,7 +475,7 @@ TEST_F(ExtensionContextMenuModelTest, ExtensionContextUninstall) {
// Scope the menu so that it's destroyed during the uninstall process. This
// reflects what normally happens (Chrome closes the menu when the uninstall
// dialog shows up).
- ExtensionContextMenuModel menu(extension.get(), GetBrowser(),
+ ExtensionContextMenuModel menu(extension, GetBrowser(),
ExtensionContextMenuModel::VISIBLE, nullptr);
menu.ExecuteCommand(ExtensionContextMenuModel::UNINSTALL, 0);
}
@@ -479,4 +484,173 @@ TEST_F(ExtensionContextMenuModelTest, ExtensionContextUninstall) {
ExtensionRegistry::EVERYTHING));
}
+TEST_F(ExtensionContextMenuModelTest, TestPageAccessSubmenu) {
+ // This test relies on the click-to-script feature.
+ scoped_ptr<FeatureSwitch::ScopedOverride> enable_scripts_require_action(
+ new FeatureSwitch::ScopedOverride(FeatureSwitch::scripts_require_action(),
+ true));
+ InitializeEmptyExtensionService();
+
+ // Add an extension with all urls.
+ const Extension* extension =
+ AddExtensionWithHostPermission("extension", manifest_keys::kBrowserAction,
+ Manifest::INTERNAL, "*://*/*");
+
+ const GURL kActiveUrl("http://www.example.com/");
+ const GURL kOtherUrl("http://www.google.com/");
+
+ // Add a web contents to the browser.
+ content::TestWebContentsFactory factory;
+ content::WebContents* contents = factory.CreateWebContents(profile());
+ Browser* browser = GetBrowser();
+ browser->tab_strip_model()->AppendWebContents(contents, true);
+ EXPECT_EQ(browser->tab_strip_model()->GetActiveWebContents(), contents);
+ content::WebContentsTester* web_contents_tester =
+ content::WebContentsTester::For(contents);
+ web_contents_tester->NavigateAndCommit(kActiveUrl);
+
+ ActiveScriptController* active_script_controller =
+ ActiveScriptController::GetForWebContents(contents);
+ ASSERT_TRUE(active_script_controller);
+
+ // Pretend the extension wants to run.
+ int run_count = 0;
+ base::Closure increment_run_count(base::Bind(&Increment, &run_count));
+ active_script_controller->RequestScriptInjectionForTesting(
+ extension, increment_run_count);
+
+ ExtensionContextMenuModel menu(extension, GetBrowser(),
+ ExtensionContextMenuModel::VISIBLE, nullptr);
+
+ EXPECT_NE(-1, menu.GetIndexOfCommandId(
+ ExtensionContextMenuModel::PAGE_ACCESS_SUBMENU));
+
+ // For laziness.
+ const ExtensionContextMenuModel::MenuEntries kRunOnClick =
+ ExtensionContextMenuModel::PAGE_ACCESS_RUN_ON_CLICK;
+ const ExtensionContextMenuModel::MenuEntries kRunOnSite =
+ ExtensionContextMenuModel::PAGE_ACCESS_RUN_ON_SITE;
+ const ExtensionContextMenuModel::MenuEntries kRunOnAllSites =
+ ExtensionContextMenuModel::PAGE_ACCESS_RUN_ON_ALL_SITES;
+
+ // Initial state: The extension should be in "run on click" mode.
+ EXPECT_TRUE(menu.IsCommandIdChecked(kRunOnClick));
+ EXPECT_FALSE(menu.IsCommandIdChecked(kRunOnSite));
+ EXPECT_FALSE(menu.IsCommandIdChecked(kRunOnAllSites));
+
+ // Initial state: The extension should have all permissions withheld, so
+ // shouldn't be allowed to run on the active url or another arbitrary url, and
+ // should have withheld permissions.
+ ScriptingPermissionsModifier permissions_modifier(profile(), extension);
+ EXPECT_FALSE(permissions_modifier.HasGrantedHostPermission(kActiveUrl));
+ EXPECT_FALSE(permissions_modifier.HasGrantedHostPermission(kOtherUrl));
+ const PermissionsData* permissions = extension->permissions_data();
+ EXPECT_FALSE(permissions->withheld_permissions().IsEmpty());
+
+ // Change the mode to be "Run on site".
+ menu.ExecuteCommand(kRunOnSite, 0);
+ EXPECT_FALSE(menu.IsCommandIdChecked(kRunOnClick));
+ EXPECT_TRUE(menu.IsCommandIdChecked(kRunOnSite));
+ EXPECT_FALSE(menu.IsCommandIdChecked(kRunOnAllSites));
+
+ // The extension should have access to the active url, but not to another
+ // arbitrary url, and the extension should still have withheld permissions.
+ EXPECT_TRUE(permissions_modifier.HasGrantedHostPermission(kActiveUrl));
+ EXPECT_FALSE(permissions_modifier.HasGrantedHostPermission(kOtherUrl));
+ EXPECT_FALSE(permissions->withheld_permissions().IsEmpty());
+
+ // Since the extension has permission, it should have ran.
+ EXPECT_EQ(1, run_count);
+ EXPECT_FALSE(active_script_controller->WantsToRun(extension));
+
+ // On another url, the mode should still be run on click.
+ web_contents_tester->NavigateAndCommit(kOtherUrl);
+ EXPECT_TRUE(menu.IsCommandIdChecked(kRunOnClick));
+ EXPECT_FALSE(menu.IsCommandIdChecked(kRunOnSite));
+ EXPECT_FALSE(menu.IsCommandIdChecked(kRunOnAllSites));
+
+ // And returning to the first url should return the mode to run on site.
+ web_contents_tester->NavigateAndCommit(kActiveUrl);
+ EXPECT_FALSE(menu.IsCommandIdChecked(kRunOnClick));
+ EXPECT_TRUE(menu.IsCommandIdChecked(kRunOnSite));
+ EXPECT_FALSE(menu.IsCommandIdChecked(kRunOnAllSites));
+
+ // Request another run.
+ active_script_controller->RequestScriptInjectionForTesting(
+ extension, increment_run_count);
+
+ // Change the mode to be "Run on all sites".
+ menu.ExecuteCommand(kRunOnAllSites, 0);
+ EXPECT_FALSE(menu.IsCommandIdChecked(kRunOnClick));
+ EXPECT_FALSE(menu.IsCommandIdChecked(kRunOnSite));
+ EXPECT_TRUE(menu.IsCommandIdChecked(kRunOnAllSites));
+
+ // The extension should be able to run on any url, and shouldn't have any
+ // withheld permissions.
+ EXPECT_TRUE(permissions_modifier.HasGrantedHostPermission(kActiveUrl));
+ EXPECT_TRUE(permissions_modifier.HasGrantedHostPermission(kOtherUrl));
+ EXPECT_TRUE(permissions->withheld_permissions().IsEmpty());
+
+ // It should have ran again.
+ EXPECT_EQ(2, run_count);
+ EXPECT_FALSE(active_script_controller->WantsToRun(extension));
+
+ // On another url, the mode should also be run on all sites.
+ web_contents_tester->NavigateAndCommit(kOtherUrl);
+ EXPECT_FALSE(menu.IsCommandIdChecked(kRunOnClick));
+ EXPECT_FALSE(menu.IsCommandIdChecked(kRunOnSite));
+ EXPECT_TRUE(menu.IsCommandIdChecked(kRunOnAllSites));
+
+ web_contents_tester->NavigateAndCommit(kActiveUrl);
+ EXPECT_FALSE(menu.IsCommandIdChecked(kRunOnClick));
+ EXPECT_FALSE(menu.IsCommandIdChecked(kRunOnSite));
+ EXPECT_TRUE(menu.IsCommandIdChecked(kRunOnAllSites));
+
+ active_script_controller->RequestScriptInjectionForTesting(
+ extension, increment_run_count);
+
+ // Return the mode to "Run on click".
+ menu.ExecuteCommand(kRunOnClick, 0);
+ EXPECT_TRUE(menu.IsCommandIdChecked(kRunOnClick));
+ EXPECT_FALSE(menu.IsCommandIdChecked(kRunOnSite));
+ EXPECT_FALSE(menu.IsCommandIdChecked(kRunOnAllSites));
+
+ // We should return to the initial state - no access.
+ EXPECT_FALSE(permissions_modifier.HasGrantedHostPermission(kActiveUrl));
+ EXPECT_FALSE(permissions_modifier.HasGrantedHostPermission(kOtherUrl));
+ EXPECT_FALSE(permissions->withheld_permissions().IsEmpty());
+
+ // And the extension shouldn't have ran.
+ EXPECT_EQ(2, run_count);
+ EXPECT_TRUE(active_script_controller->WantsToRun(extension));
+
+ // Install an extension requesting only a single host. Since the extension
+ // doesn't request all hosts, it shouldn't have withheld permissions, and
+ // thus shouldn't have the page access submenu.
+ const Extension* single_host_extension = AddExtensionWithHostPermission(
+ "single_host_extension", manifest_keys::kBrowserAction,
+ Manifest::INTERNAL, "http://www.google.com/*");
+ ExtensionContextMenuModel single_host_menu(
+ single_host_extension, GetBrowser(), ExtensionContextMenuModel::VISIBLE,
+ nullptr);
+ EXPECT_EQ(-1, single_host_menu.GetIndexOfCommandId(
+ ExtensionContextMenuModel::PAGE_ACCESS_SUBMENU));
+
+ // Disable the click-to-script feature, and install a new extension requiring
+ // all hosts. Since the feature isn't on, it shouldn't have the page access
+ // submenu either.
+ enable_scripts_require_action.reset();
+ enable_scripts_require_action.reset(
+ new FeatureSwitch::ScopedOverride(FeatureSwitch::scripts_require_action(),
+ false));
+ const Extension* feature_disabled_extension = AddExtensionWithHostPermission(
+ "feature_disabled_extension", manifest_keys::kBrowserAction,
+ Manifest::INTERNAL, "http://www.google.com/*");
+ ExtensionContextMenuModel feature_disabled_menu(
+ feature_disabled_extension, GetBrowser(),
+ ExtensionContextMenuModel::VISIBLE, nullptr);
+ EXPECT_EQ(-1, feature_disabled_menu.GetIndexOfCommandId(
+ ExtensionContextMenuModel::PAGE_ACCESS_SUBMENU));
+}
+
} // namespace extensions