Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / chrome / browser / extensions / extension_context_menu_model_unittest.cc
blobac634bd11f4832ae5610bf5d9ca3a20db50f414a
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.
5 #include "chrome/browser/extensions/extension_context_menu_model.h"
7 #include "base/strings/utf_string_conversions.h"
8 #include "chrome/browser/extensions/api/extension_action/extension_action_api.h"
9 #include "chrome/browser/extensions/extension_service.h"
10 #include "chrome/browser/extensions/extension_service_test_base.h"
11 #include "chrome/browser/extensions/menu_manager.h"
12 #include "chrome/browser/extensions/menu_manager_factory.h"
13 #include "chrome/browser/ui/browser.h"
14 #include "chrome/browser/ui/host_desktop.h"
15 #include "chrome/common/extensions/api/context_menus.h"
16 #include "chrome/grit/chromium_strings.h"
17 #include "chrome/grit/generated_resources.h"
18 #include "chrome/test/base/test_browser_window.h"
19 #include "chrome/test/base/testing_profile.h"
20 #include "components/crx_file/id_util.h"
21 #include "extensions/browser/extension_system.h"
22 #include "extensions/browser/test_management_policy.h"
23 #include "extensions/common/extension_builder.h"
24 #include "extensions/common/feature_switch.h"
25 #include "extensions/common/manifest.h"
26 #include "extensions/common/manifest_constants.h"
27 #include "extensions/common/manifest_handlers/options_page_info.h"
28 #include "extensions/common/value_builder.h"
29 #include "testing/gtest/include/gtest/gtest.h"
30 #include "ui/base/l10n/l10n_util.h"
31 #include "ui/gfx/image/image.h"
33 namespace extensions {
35 namespace {
37 // Label for test extension menu item.
38 const char* kTestExtensionItemLabel = "test-ext-item";
40 // Build an extension to pass to the menu constructor, with the an action
41 // specified by |action_key|.
42 scoped_refptr<const Extension> BuildExtension(const std::string& name,
43 const char* action_key,
44 Manifest::Location location) {
45 return ExtensionBuilder()
46 .SetManifest(DictionaryBuilder()
47 .Set("name", name)
48 .Set("version", "1")
49 .Set("manifest_version", 2)
50 .Set(action_key, DictionaryBuilder().Pass()))
51 .SetID(crx_file::id_util::GenerateId(name))
52 .SetLocation(location)
53 .Build();
56 // Create a Browser for the ExtensionContextMenuModel to use.
57 scoped_ptr<Browser> CreateBrowser(Profile* profile) {
58 Browser::CreateParams params(profile, chrome::GetActiveDesktop());
59 TestBrowserWindow test_window;
60 params.window = &test_window;
61 return scoped_ptr<Browser>(new Browser(params));
64 // Returns the index of the given |command_id| in the given |menu|, or -1 if it
65 // is not found.
66 int GetCommandIndex(const scoped_refptr<ExtensionContextMenuModel> menu,
67 int command_id) {
68 int item_count = menu->GetItemCount();
69 for (int i = 0; i < item_count; ++i) {
70 if (menu->GetCommandIdAt(i) == command_id)
71 return i;
73 return -1;
76 } // namespace
78 class ExtensionContextMenuModelTest : public ExtensionServiceTestBase {
79 public:
80 ExtensionContextMenuModelTest();
82 // Creates an extension menu item for |extension| with the given |context|
83 // and adds it to |manager|. Refreshes |model| to show new item.
84 void AddContextItemAndRefreshModel(MenuManager* manager,
85 const Extension* extension,
86 MenuItem::Context context,
87 ExtensionContextMenuModel* model);
89 // Reinitializes the given |model|.
90 void RefreshMenu(ExtensionContextMenuModel* model);
92 // Returns the number of extension menu items that show up in |model|.
93 // For this test, all the extension items have samel label
94 // |kTestExtensionItemLabel|.
95 int CountExtensionItems(ExtensionContextMenuModel* model);
97 private:
98 int cur_id_;
101 ExtensionContextMenuModelTest::ExtensionContextMenuModelTest() : cur_id_(0) {
104 void ExtensionContextMenuModelTest::AddContextItemAndRefreshModel(
105 MenuManager* manager,
106 const Extension* extension,
107 MenuItem::Context context,
108 ExtensionContextMenuModel* model) {
109 MenuItem::Type type = MenuItem::NORMAL;
110 MenuItem::ContextList contexts(context);
111 const MenuItem::ExtensionKey key(extension->id());
112 MenuItem::Id id(false, key);
113 id.uid = ++cur_id_;
114 manager->AddContextItem(extension, new MenuItem(id, kTestExtensionItemLabel,
115 false, // checked
116 true, // enabled
117 type, contexts));
118 RefreshMenu(model);
121 void ExtensionContextMenuModelTest::RefreshMenu(
122 ExtensionContextMenuModel* model) {
123 model->Clear();
124 model->InitMenu(model->GetExtension(), ExtensionContextMenuModel::VISIBLE);
127 int ExtensionContextMenuModelTest::CountExtensionItems(
128 ExtensionContextMenuModel* model) {
129 base::string16 expected_label = base::ASCIIToUTF16(kTestExtensionItemLabel);
130 int num_items_found = 0;
131 for (int i = 0; i < model->GetItemCount(); ++i) {
132 if (expected_label == model->GetLabelAt(i))
133 ++num_items_found;
135 EXPECT_EQ(num_items_found, model->extension_items_count_);
136 return num_items_found;
139 // Tests that applicable menu items are disabled when a ManagementPolicy
140 // prohibits them.
141 TEST_F(ExtensionContextMenuModelTest, RequiredInstallationsDisablesItems) {
142 InitializeEmptyExtensionService();
144 // Test that management policy can determine whether or not policy-installed
145 // extensions can be installed/uninstalled.
146 scoped_refptr<const Extension> extension =
147 BuildExtension("extension",
148 manifest_keys::kPageAction,
149 Manifest::EXTERNAL_POLICY);
150 ASSERT_TRUE(extension.get());
151 service()->AddExtension(extension.get());
153 scoped_ptr<Browser> browser = CreateBrowser(profile());
154 scoped_refptr<ExtensionContextMenuModel> menu(
155 new ExtensionContextMenuModel(extension.get(), browser.get()));
157 ExtensionSystem* system = ExtensionSystem::Get(profile());
158 system->management_policy()->UnregisterAllProviders();
160 // Uninstallation should be, by default, enabled.
161 EXPECT_TRUE(menu->IsCommandIdEnabled(ExtensionContextMenuModel::UNINSTALL));
163 TestManagementPolicyProvider policy_provider(
164 TestManagementPolicyProvider::PROHIBIT_MODIFY_STATUS);
165 system->management_policy()->RegisterProvider(&policy_provider);
167 // If there's a policy provider that requires the extension stay enabled, then
168 // uninstallation should be disabled.
169 EXPECT_FALSE(menu->IsCommandIdEnabled(ExtensionContextMenuModel::UNINSTALL));
170 int uninstall_index =
171 menu->GetIndexOfCommandId(ExtensionContextMenuModel::UNINSTALL);
172 // There should also be an icon to visually indicate why uninstallation is
173 // forbidden.
174 gfx::Image icon;
175 EXPECT_TRUE(menu->GetIconAt(uninstall_index, &icon));
176 EXPECT_FALSE(icon.IsEmpty());
178 // Don't leave |policy_provider| dangling.
179 system->management_policy()->UnregisterProvider(&policy_provider);
182 // Tests the context menu for a component extension.
183 TEST_F(ExtensionContextMenuModelTest, ComponentExtensionContextMenu) {
184 InitializeEmptyExtensionService();
186 std::string name("component");
187 scoped_ptr<base::DictionaryValue> manifest =
188 DictionaryBuilder().Set("name", name)
189 .Set("version", "1")
190 .Set("manifest_version", 2)
191 .Set("browser_action", DictionaryBuilder().Pass())
192 .Build();
194 scoped_refptr<const Extension> extension =
195 ExtensionBuilder().SetManifest(make_scoped_ptr(manifest->DeepCopy()))
196 .SetID(crx_file::id_util::GenerateId("component"))
197 .SetLocation(Manifest::COMPONENT)
198 .Build();
199 service()->AddExtension(extension.get());
201 scoped_ptr<Browser> browser = CreateBrowser(profile());
203 scoped_refptr<ExtensionContextMenuModel> menu(
204 new ExtensionContextMenuModel(extension.get(), browser.get()));
206 // A component extension's context menu should not include options for
207 // managing extensions or removing it, and should only include an option for
208 // the options page if the extension has one (which this one doesn't).
209 EXPECT_EQ(-1,
210 menu->GetIndexOfCommandId(ExtensionContextMenuModel::CONFIGURE));
211 EXPECT_EQ(-1,
212 menu->GetIndexOfCommandId(ExtensionContextMenuModel::UNINSTALL));
213 EXPECT_EQ(-1, menu->GetIndexOfCommandId(ExtensionContextMenuModel::MANAGE));
214 // The "name" option should be present, but not enabled for component
215 // extensions.
216 EXPECT_NE(-1, menu->GetIndexOfCommandId(ExtensionContextMenuModel::NAME));
217 EXPECT_FALSE(menu->IsCommandIdEnabled(ExtensionContextMenuModel::NAME));
219 // Check that a component extension with an options page does have the options
220 // menu item, and it is enabled.
221 manifest->SetString("options_page", "options_page.html");
222 extension =
223 ExtensionBuilder().SetManifest(manifest.Pass())
224 .SetID(crx_file::id_util::GenerateId("component_opts"))
225 .SetLocation(Manifest::COMPONENT)
226 .Build();
227 menu = new ExtensionContextMenuModel(extension.get(), browser.get());
228 service()->AddExtension(extension.get());
229 EXPECT_TRUE(extensions::OptionsPageInfo::HasOptionsPage(extension.get()));
230 EXPECT_NE(-1,
231 menu->GetIndexOfCommandId(ExtensionContextMenuModel::CONFIGURE));
232 EXPECT_TRUE(menu->IsCommandIdEnabled(ExtensionContextMenuModel::CONFIGURE));
235 TEST_F(ExtensionContextMenuModelTest, ExtensionItemTest) {
236 InitializeEmptyExtensionService();
237 scoped_refptr<const Extension> extension =
238 BuildExtension("extension",
239 manifest_keys::kPageAction,
240 Manifest::INTERNAL);
241 ASSERT_TRUE(extension.get());
242 service()->AddExtension(extension.get());
244 scoped_ptr<Browser> browser = CreateBrowser(profile());
246 // Create a MenuManager for adding context items.
247 MenuManager* manager = static_cast<MenuManager*>(
248 (MenuManagerFactory::GetInstance()->SetTestingFactoryAndUse(
249 profile(),
250 &MenuManagerFactory::BuildServiceInstanceForTesting)));
251 ASSERT_TRUE(manager);
253 scoped_refptr<ExtensionContextMenuModel> menu(
254 new ExtensionContextMenuModel(extension.get(), browser.get()));
256 // There should be no extension items yet.
257 EXPECT_EQ(0, CountExtensionItems(menu.get()));
259 // Add a browser action menu item for |extension| to |manager|.
260 AddContextItemAndRefreshModel(
261 manager, extension.get(), MenuItem::BROWSER_ACTION, menu.get());
263 // Since |extension| has a page action, the browser action menu item should
264 // not be present.
265 EXPECT_EQ(0, CountExtensionItems(menu.get()));
267 // Add a page action menu item and reset the context menu.
268 AddContextItemAndRefreshModel(
269 manager, extension.get(), MenuItem::PAGE_ACTION, menu.get());
271 // The page action item should be present because |extension| has a page
272 // action.
273 EXPECT_EQ(1, CountExtensionItems(menu.get()));
275 // Create more page action items to test top level menu item limitations.
276 for (int i = 0; i < api::context_menus::ACTION_MENU_TOP_LEVEL_LIMIT; ++i)
277 AddContextItemAndRefreshModel(
278 manager, extension.get(), MenuItem::PAGE_ACTION, menu.get());
280 // The menu should only have a limited number of extension items, since they
281 // are all top level items, and we limit the number of top level extension
282 // items.
283 EXPECT_EQ(api::context_menus::ACTION_MENU_TOP_LEVEL_LIMIT,
284 CountExtensionItems(menu.get()));
286 AddContextItemAndRefreshModel(
287 manager, extension.get(), MenuItem::PAGE_ACTION, menu.get());
289 // Adding another top level item should not increase the count.
290 EXPECT_EQ(api::context_menus::ACTION_MENU_TOP_LEVEL_LIMIT,
291 CountExtensionItems(menu.get()));
294 // Test that the "show" and "hide" menu items appear correctly in the extension
295 // context menu.
296 TEST_F(ExtensionContextMenuModelTest, ExtensionContextMenuShowAndHide) {
297 InitializeEmptyExtensionService();
298 scoped_refptr<const Extension> page_action =
299 BuildExtension("page_action_extension",
300 manifest_keys::kPageAction,
301 Manifest::INTERNAL);
302 ASSERT_TRUE(page_action.get());
303 scoped_refptr<const Extension> browser_action =
304 BuildExtension("browser_action_extension",
305 manifest_keys::kBrowserAction,
306 Manifest::INTERNAL);
307 ASSERT_TRUE(browser_action.get());
309 service()->AddExtension(page_action.get());
310 service()->AddExtension(browser_action.get());
312 scoped_ptr<Browser> browser = CreateBrowser(profile());
314 scoped_refptr<ExtensionContextMenuModel> menu(
315 new ExtensionContextMenuModel(page_action.get(),
316 browser.get(),
317 ExtensionContextMenuModel::VISIBLE,
318 nullptr));
320 // For laziness.
321 const ExtensionContextMenuModel::MenuEntries visibility_command =
322 ExtensionContextMenuModel::TOGGLE_VISIBILITY;
323 base::string16 hide_string =
324 l10n_util::GetStringUTF16(IDS_EXTENSIONS_HIDE_BUTTON);
325 base::string16 redesign_hide_string =
326 l10n_util::GetStringUTF16(IDS_EXTENSIONS_HIDE_BUTTON_IN_MENU);
327 base::string16 redesign_show_string =
328 l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_BUTTON_IN_TOOLBAR);
329 base::string16 redesign_keep_string =
330 l10n_util::GetStringUTF16(IDS_EXTENSIONS_KEEP_BUTTON_IN_TOOLBAR);
332 int index = GetCommandIndex(menu, visibility_command);
333 // Without the toolbar redesign switch, page action menus shouldn't have a
334 // visibility option.
335 EXPECT_EQ(-1, index);
337 menu = new ExtensionContextMenuModel(browser_action.get(),
338 browser.get(),
339 ExtensionContextMenuModel::VISIBLE,
340 nullptr);
341 index = GetCommandIndex(menu, visibility_command);
342 // Browser actions should have the visibility option.
343 EXPECT_NE(-1, index);
344 // Since the action is currently visible, it should have the option to hide
345 // it.
346 EXPECT_EQ(hide_string, menu->GetLabelAt(index));
348 // Enabling the toolbar redesign switch should give page actions the button.
349 FeatureSwitch::ScopedOverride enable_toolbar_redesign(
350 FeatureSwitch::extension_action_redesign(), true);
351 menu = new ExtensionContextMenuModel(page_action.get(),
352 browser.get(),
353 ExtensionContextMenuModel::VISIBLE,
354 nullptr);
355 index = GetCommandIndex(menu, visibility_command);
356 EXPECT_NE(-1, index);
357 EXPECT_EQ(redesign_hide_string, menu->GetLabelAt(index));
359 // If the action is overflowed, it should have the "Show button in toolbar"
360 // string.
361 menu = new ExtensionContextMenuModel(browser_action.get(),
362 browser.get(),
363 ExtensionContextMenuModel::OVERFLOWED,
364 nullptr);
365 index = GetCommandIndex(menu, visibility_command);
366 EXPECT_NE(-1, index);
367 EXPECT_EQ(redesign_show_string, menu->GetLabelAt(index));
369 // If the action is transitively visible, as happens when it is showing a
370 // popup, we should use a "Keep button in toolbar" string.
371 menu = new ExtensionContextMenuModel(
372 browser_action.get(),
373 browser.get(),
374 ExtensionContextMenuModel::TRANSITIVELY_VISIBLE,
375 nullptr);
376 index = GetCommandIndex(menu, visibility_command);
377 EXPECT_NE(-1, index);
378 EXPECT_EQ(redesign_keep_string, menu->GetLabelAt(index));
381 } // namespace extensions