Home | History | Annotate | Download | only in extensions
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/extensions/extension_context_menu_model.h"
      6 
      7 #include "chrome/browser/extensions/api/extension_action/extension_action_api.h"
      8 #include "chrome/browser/extensions/extension_service.h"
      9 #include "chrome/browser/extensions/extension_service_test_base.h"
     10 #include "chrome/browser/extensions/menu_manager.h"
     11 #include "chrome/browser/extensions/menu_manager_factory.h"
     12 #include "chrome/browser/ui/browser.h"
     13 #include "chrome/browser/ui/host_desktop.h"
     14 #include "chrome/common/extensions/api/context_menus.h"
     15 #include "chrome/grit/generated_resources.h"
     16 #include "chrome/test/base/test_browser_window.h"
     17 #include "chrome/test/base/testing_profile.h"
     18 #include "components/crx_file/id_util.h"
     19 #include "extensions/browser/extension_prefs.h"
     20 #include "extensions/browser/extension_system.h"
     21 #include "extensions/browser/test_management_policy.h"
     22 #include "extensions/common/extension_builder.h"
     23 #include "extensions/common/feature_switch.h"
     24 #include "extensions/common/manifest_constants.h"
     25 #include "extensions/common/value_builder.h"
     26 #include "testing/gtest/include/gtest/gtest.h"
     27 #include "ui/base/l10n/l10n_util.h"
     28 
     29 namespace extensions {
     30 
     31 namespace {
     32 
     33 // Build an extension to pass to the menu constructor, with the an action
     34 // specified by |action_key|.
     35 scoped_refptr<const Extension> BuildExtension(const std::string& name,
     36                                               const char* action_key) {
     37   return ExtensionBuilder()
     38       .SetManifest(DictionaryBuilder()
     39                        .Set("name", name)
     40                        .Set("version", "1")
     41                        .Set("manifest_version", 2)
     42                        .Set(action_key, DictionaryBuilder().Pass()))
     43       .SetID(crx_file::id_util::GenerateId(name))
     44       .Build();
     45 }
     46 
     47 // Create a Browser for the ExtensionContextMenuModel to use.
     48 scoped_ptr<Browser> CreateBrowser(Profile* profile) {
     49   Browser::CreateParams params(profile, chrome::GetActiveDesktop());
     50   TestBrowserWindow test_window;
     51   params.window = &test_window;
     52   return scoped_ptr<Browser>(new Browser(params));
     53 }
     54 
     55 // Returns the index of the given |command_id| in the given |menu|, or -1 if it
     56 // is not found.
     57 int GetCommandIndex(const scoped_refptr<ExtensionContextMenuModel> menu,
     58                          int command_id) {
     59   int item_count = menu->GetItemCount();
     60   for (int i = 0; i < item_count; ++i) {
     61     if (menu->GetCommandIdAt(i) == command_id)
     62       return i;
     63   }
     64   return -1;
     65 }
     66 
     67 }  // namespace
     68 
     69 class ExtensionContextMenuModelTest : public ExtensionServiceTestBase {
     70  public:
     71   ExtensionContextMenuModelTest();
     72 
     73   // Creates an extension menu item for |extension| with the given |context|
     74   // and adds it to |manager|. Refreshes |model| to show new item.
     75   void AddContextItemAndRefreshModel(MenuManager* manager,
     76                                      const Extension* extension,
     77                                      MenuItem::Context context,
     78                                      ExtensionContextMenuModel* model);
     79 
     80   // Reinitializes the given |model|.
     81   void RefreshMenu(ExtensionContextMenuModel* model);
     82 
     83   // Returns the number of extension menu items that show up in |model|.
     84   int CountExtensionItems(ExtensionContextMenuModel* model);
     85 
     86  private:
     87   int cur_id_;
     88 };
     89 
     90 ExtensionContextMenuModelTest::ExtensionContextMenuModelTest() : cur_id_(0) {
     91 }
     92 
     93 
     94 void ExtensionContextMenuModelTest::AddContextItemAndRefreshModel(
     95     MenuManager* manager,
     96     const Extension* extension,
     97     MenuItem::Context context,
     98     ExtensionContextMenuModel* model) {
     99   MenuItem::Type type = MenuItem::NORMAL;
    100   MenuItem::ContextList contexts(context);
    101   const MenuItem::ExtensionKey key(extension->id());
    102   MenuItem::Id id(false, key);
    103   id.uid = ++cur_id_;
    104   manager->AddContextItem(extension,
    105                           new MenuItem(id,
    106                                        "test",
    107                                        false,  // checked
    108                                        true,   // enabled
    109                                        type,
    110                                        contexts));
    111   RefreshMenu(model);
    112 }
    113 
    114 void ExtensionContextMenuModelTest::RefreshMenu(
    115     ExtensionContextMenuModel* model) {
    116   model->InitMenu(model->GetExtension());
    117 }
    118 
    119 int ExtensionContextMenuModelTest::CountExtensionItems(
    120     ExtensionContextMenuModel* model) {
    121   return model->extension_items_count_;
    122 }
    123 
    124 // Tests that applicable menu items are disabled when a ManagementPolicy
    125 // prohibits them.
    126 TEST_F(ExtensionContextMenuModelTest, PolicyDisablesItems) {
    127   InitializeEmptyExtensionService();
    128   scoped_refptr<const Extension> extension =
    129       BuildExtension("extension", manifest_keys::kPageAction);
    130   ASSERT_TRUE(extension.get());
    131   service()->AddExtension(extension.get());
    132 
    133   scoped_ptr<Browser> browser = CreateBrowser(profile());
    134 
    135   scoped_refptr<ExtensionContextMenuModel> menu(
    136       new ExtensionContextMenuModel(extension.get(), browser.get()));
    137 
    138   ExtensionSystem* system = ExtensionSystem::Get(profile());
    139   system->management_policy()->UnregisterAllProviders();
    140 
    141   // Actions should be enabled.
    142   ASSERT_TRUE(menu->IsCommandIdEnabled(ExtensionContextMenuModel::UNINSTALL));
    143 
    144   TestManagementPolicyProvider policy_provider(
    145       TestManagementPolicyProvider::PROHIBIT_MODIFY_STATUS);
    146   system->management_policy()->RegisterProvider(&policy_provider);
    147 
    148   // Now the actions are disabled.
    149   ASSERT_FALSE(menu->IsCommandIdEnabled(ExtensionContextMenuModel::UNINSTALL));
    150 
    151   // Don't leave |policy_provider| dangling.
    152   system->management_policy()->UnregisterAllProviders();
    153 }
    154 
    155 TEST_F(ExtensionContextMenuModelTest, ExtensionItemTest) {
    156   InitializeEmptyExtensionService();
    157   scoped_refptr<const Extension> extension =
    158       BuildExtension("extension", manifest_keys::kPageAction);
    159   ASSERT_TRUE(extension.get());
    160   service()->AddExtension(extension.get());
    161 
    162   scoped_ptr<Browser> browser = CreateBrowser(profile());
    163 
    164   // Create a MenuManager for adding context items.
    165   MenuManager* manager = static_cast<MenuManager*>(
    166       (MenuManagerFactory::GetInstance()->SetTestingFactoryAndUse(
    167           profile(),
    168           &MenuManagerFactory::BuildServiceInstanceForTesting)));
    169   ASSERT_TRUE(manager);
    170 
    171   scoped_refptr<ExtensionContextMenuModel> menu(
    172       new ExtensionContextMenuModel(extension.get(), browser.get()));
    173 
    174   // There should be no extension items yet.
    175   EXPECT_EQ(0, CountExtensionItems(menu.get()));
    176 
    177   // Add a browser action menu item for |extension| to |manager|.
    178   AddContextItemAndRefreshModel(
    179       manager, extension.get(), MenuItem::BROWSER_ACTION, menu.get());
    180 
    181   // Since |extension| has a page action, the browser action menu item should
    182   // not be present.
    183   EXPECT_EQ(0, CountExtensionItems(menu.get()));
    184 
    185   // Add a page action menu item and reset the context menu.
    186   AddContextItemAndRefreshModel(
    187       manager, extension.get(), MenuItem::PAGE_ACTION, menu.get());
    188 
    189   // The page action item should be present because |extension| has a page
    190   // action.
    191   EXPECT_EQ(1, CountExtensionItems(menu.get()));
    192 
    193   // Create more page action items to test top level menu item limitations.
    194   for (int i = 0; i < api::context_menus::ACTION_MENU_TOP_LEVEL_LIMIT; ++i)
    195     AddContextItemAndRefreshModel(
    196         manager, extension.get(), MenuItem::PAGE_ACTION, menu.get());
    197 
    198   // The menu should only have a limited number of extension items, since they
    199   // are all top level items, and we limit the number of top level extension
    200   // items.
    201   EXPECT_EQ(api::context_menus::ACTION_MENU_TOP_LEVEL_LIMIT,
    202             CountExtensionItems(menu.get()));
    203 
    204   AddContextItemAndRefreshModel(
    205       manager, extension.get(), MenuItem::PAGE_ACTION, menu.get());
    206 
    207   // Adding another top level item should not increase the count.
    208   EXPECT_EQ(api::context_menus::ACTION_MENU_TOP_LEVEL_LIMIT,
    209             CountExtensionItems(menu.get()));
    210 }
    211 
    212 // Test that the "show" and "hide" menu items appear correctly in the extension
    213 // context menu.
    214 TEST_F(ExtensionContextMenuModelTest, ExtensionContextMenuShowAndHide) {
    215   InitializeEmptyExtensionService();
    216   scoped_refptr<const Extension> page_action =
    217       BuildExtension("page_action_extension", manifest_keys::kPageAction);
    218   ASSERT_TRUE(page_action.get());
    219   scoped_refptr<const Extension> browser_action =
    220       BuildExtension("browser_action_extension", manifest_keys::kBrowserAction);
    221   ASSERT_TRUE(browser_action.get());
    222 
    223   service()->AddExtension(page_action.get());
    224   service()->AddExtension(browser_action.get());
    225 
    226   scoped_ptr<Browser> browser = CreateBrowser(profile());
    227 
    228   scoped_refptr<ExtensionContextMenuModel> menu(
    229       new ExtensionContextMenuModel(page_action.get(), browser.get()));
    230 
    231   // For laziness.
    232   const ExtensionContextMenuModel::MenuEntries visibility_command =
    233       ExtensionContextMenuModel::TOGGLE_VISIBILITY;
    234   base::string16 hide_string =
    235       l10n_util::GetStringUTF16(IDS_EXTENSIONS_HIDE_BUTTON);
    236   base::string16 show_string =
    237       l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_BUTTON);
    238   ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());
    239 
    240   int index = GetCommandIndex(menu, visibility_command);
    241   // Without the toolbar redesign switch, page action menus shouldn't have a
    242   // visibility option.
    243   EXPECT_EQ(-1, index);
    244 
    245   menu = new ExtensionContextMenuModel(browser_action.get(), browser.get());
    246   index = GetCommandIndex(menu, visibility_command);
    247   // Browser actions should have the visibility option.
    248   EXPECT_NE(-1, index);
    249 
    250   // Enabling the toolbar redesign switch should give page actions the button.
    251   FeatureSwitch::ScopedOverride enable_toolbar_redesign(
    252       FeatureSwitch::extension_action_redesign(), true);
    253   menu = new ExtensionContextMenuModel(page_action.get(), browser.get());
    254   index = GetCommandIndex(menu, visibility_command);
    255   EXPECT_NE(-1, index);
    256 
    257   // Next, we test the command label.
    258   menu = new ExtensionContextMenuModel(browser_action.get(), browser.get());
    259   index = GetCommandIndex(menu, visibility_command);
    260   // By default, browser actions should be visible (and therefore the button
    261   // should be to hide).
    262   EXPECT_TRUE(ExtensionActionAPI::GetBrowserActionVisibility(
    263                   prefs, browser_action->id()));
    264   EXPECT_EQ(hide_string, menu->GetLabelAt(index));
    265 
    266   // Hide the browser action. This should mean the string is "show".
    267   ExtensionActionAPI::SetBrowserActionVisibility(
    268       prefs, browser_action->id(), false);
    269   menu = new ExtensionContextMenuModel(browser_action.get(), browser.get());
    270   index = GetCommandIndex(menu, visibility_command);
    271   EXPECT_NE(-1, index);
    272   EXPECT_EQ(show_string, menu->GetLabelAt(index));
    273 }
    274 
    275 }  // namespace extensions
    276