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
29namespace extensions {
30
31namespace {
32
33// Build an extension to pass to the menu constructor, with the an action
34// specified by |action_key|.
35scoped_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.
48scoped_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.
57int 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
69class 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
90ExtensionContextMenuModelTest::ExtensionContextMenuModelTest() : cur_id_(0) {
91}
92
93
94void 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
114void ExtensionContextMenuModelTest::RefreshMenu(
115    ExtensionContextMenuModel* model) {
116  model->InitMenu(model->GetExtension());
117}
118
119int 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.
126TEST_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
155TEST_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.
214TEST_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