diff options
author | rdevlin.cronin <rdevlin.cronin@chromium.org> | 2015-10-29 10:05:56 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-10-29 17:06:54 +0000 |
commit | b2e7ac04ba48e24f04034aed81f0140505d09259 (patch) | |
tree | 61c57162b8a5acf74852375df0d83a3f7b16f36a | |
parent | cfd8141a4f2b970a8c9ba6d70a541a4490011284 (diff) | |
download | chromium_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}
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 |