Elim cr-checkbox
[chromium-blink-merge.git] / chrome / browser / extensions / extension_context_menu_model_unittest.cc
blob6fe1a3459203a3562a54f93cd3c01219088b3c5a
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_dialog_auto_confirm.h"
22 #include "extensions/browser/extension_registry.h"
23 #include "extensions/browser/extension_system.h"
24 #include "extensions/browser/test_extension_registry_observer.h"
25 #include "extensions/browser/test_management_policy.h"
26 #include "extensions/common/extension_builder.h"
27 #include "extensions/common/feature_switch.h"
28 #include "extensions/common/manifest.h"
29 #include "extensions/common/manifest_constants.h"
30 #include "extensions/common/manifest_handlers/options_page_info.h"
31 #include "extensions/common/value_builder.h"
32 #include "testing/gtest/include/gtest/gtest.h"
33 #include "ui/base/l10n/l10n_util.h"
34 #include "ui/gfx/image/image.h"
36 namespace extensions {
38 namespace {
40 // Label for test extension menu item.
41 const char* kTestExtensionItemLabel = "test-ext-item";
43 // Build an extension to pass to the menu constructor, with the an action
44 // specified by |action_key|.
45 scoped_refptr<const Extension> BuildExtension(const std::string& name,
46 const char* action_key,
47 Manifest::Location location) {
48 return ExtensionBuilder()
49 .SetManifest(DictionaryBuilder()
50 .Set("name", name)
51 .Set("version", "1")
52 .Set("manifest_version", 2)
53 .Set(action_key, DictionaryBuilder().Pass()))
54 .SetID(crx_file::id_util::GenerateId(name))
55 .SetLocation(location)
56 .Build();
59 // Returns the index of the given |command_id| in the given |menu|, or -1 if it
60 // is not found.
61 int GetCommandIndex(const ExtensionContextMenuModel& menu, int command_id) {
62 int item_count = menu.GetItemCount();
63 for (int i = 0; i < item_count; ++i) {
64 if (menu.GetCommandIdAt(i) == command_id)
65 return i;
67 return -1;
70 } // namespace
72 class ExtensionContextMenuModelTest : public ExtensionServiceTestBase {
73 public:
74 ExtensionContextMenuModelTest();
76 // Creates an extension menu item for |extension| with the given |context|
77 // and adds it to |manager|. Refreshes |model| to show new item.
78 void AddContextItemAndRefreshModel(MenuManager* manager,
79 const Extension* extension,
80 MenuItem::Context context,
81 ExtensionContextMenuModel* model);
83 // Reinitializes the given |model|.
84 void RefreshMenu(ExtensionContextMenuModel* model);
86 // Returns the number of extension menu items that show up in |model|.
87 // For this test, all the extension items have samel label
88 // |kTestExtensionItemLabel|.
89 int CountExtensionItems(const ExtensionContextMenuModel& model);
91 Browser* GetBrowser();
93 private:
94 void CreateBrowser();
96 int cur_id_;
98 scoped_ptr<TestBrowserWindow> test_window_;
99 scoped_ptr<Browser> browser_;
101 DISALLOW_COPY_AND_ASSIGN(ExtensionContextMenuModelTest);
104 ExtensionContextMenuModelTest::ExtensionContextMenuModelTest() : cur_id_(0) {
107 void ExtensionContextMenuModelTest::AddContextItemAndRefreshModel(
108 MenuManager* manager,
109 const Extension* extension,
110 MenuItem::Context context,
111 ExtensionContextMenuModel* model) {
112 MenuItem::Type type = MenuItem::NORMAL;
113 MenuItem::ContextList contexts(context);
114 const MenuItem::ExtensionKey key(extension->id());
115 MenuItem::Id id(false, key);
116 id.uid = ++cur_id_;
117 manager->AddContextItem(extension, new MenuItem(id, kTestExtensionItemLabel,
118 false, // checked
119 true, // enabled
120 type, contexts));
121 RefreshMenu(model);
124 void ExtensionContextMenuModelTest::RefreshMenu(
125 ExtensionContextMenuModel* model) {
126 model->Clear();
127 model->InitMenu(model->GetExtension(), ExtensionContextMenuModel::VISIBLE);
130 int ExtensionContextMenuModelTest::CountExtensionItems(
131 const ExtensionContextMenuModel& model) {
132 base::string16 expected_label = base::ASCIIToUTF16(kTestExtensionItemLabel);
133 int num_items_found = 0;
134 for (int i = 0; i < model.GetItemCount(); ++i) {
135 if (expected_label == model.GetLabelAt(i))
136 ++num_items_found;
138 EXPECT_EQ(num_items_found, model.extension_items_count_);
139 return num_items_found;
142 Browser* ExtensionContextMenuModelTest::GetBrowser() {
143 if (!browser_)
144 CreateBrowser();
145 CHECK(browser_);
146 return browser_.get();
149 void ExtensionContextMenuModelTest::CreateBrowser() {
150 Browser::CreateParams params(profile(), chrome::GetActiveDesktop());
151 test_window_.reset(new TestBrowserWindow());
152 params.window = test_window_.get();
153 browser_.reset(new Browser(params));
156 // Tests that applicable menu items are disabled when a ManagementPolicy
157 // prohibits them.
158 TEST_F(ExtensionContextMenuModelTest, RequiredInstallationsDisablesItems) {
159 InitializeEmptyExtensionService();
161 // Test that management policy can determine whether or not policy-installed
162 // extensions can be installed/uninstalled.
163 scoped_refptr<const Extension> extension =
164 BuildExtension("extension",
165 manifest_keys::kPageAction,
166 Manifest::EXTERNAL_POLICY);
167 ASSERT_TRUE(extension.get());
168 service()->AddExtension(extension.get());
170 ExtensionContextMenuModel menu(extension.get(), GetBrowser());
172 ExtensionSystem* system = ExtensionSystem::Get(profile());
173 system->management_policy()->UnregisterAllProviders();
175 // Uninstallation should be, by default, enabled.
176 EXPECT_TRUE(menu.IsCommandIdEnabled(ExtensionContextMenuModel::UNINSTALL));
178 TestManagementPolicyProvider policy_provider(
179 TestManagementPolicyProvider::PROHIBIT_MODIFY_STATUS);
180 system->management_policy()->RegisterProvider(&policy_provider);
182 // If there's a policy provider that requires the extension stay enabled, then
183 // uninstallation should be disabled.
184 EXPECT_FALSE(menu.IsCommandIdEnabled(ExtensionContextMenuModel::UNINSTALL));
185 int uninstall_index =
186 menu.GetIndexOfCommandId(ExtensionContextMenuModel::UNINSTALL);
187 // There should also be an icon to visually indicate why uninstallation is
188 // forbidden.
189 gfx::Image icon;
190 EXPECT_TRUE(menu.GetIconAt(uninstall_index, &icon));
191 EXPECT_FALSE(icon.IsEmpty());
193 // Don't leave |policy_provider| dangling.
194 system->management_policy()->UnregisterProvider(&policy_provider);
197 // Tests the context menu for a component extension.
198 TEST_F(ExtensionContextMenuModelTest, ComponentExtensionContextMenu) {
199 InitializeEmptyExtensionService();
201 std::string name("component");
202 scoped_ptr<base::DictionaryValue> manifest =
203 DictionaryBuilder().Set("name", name)
204 .Set("version", "1")
205 .Set("manifest_version", 2)
206 .Set("browser_action", DictionaryBuilder().Pass())
207 .Build();
210 scoped_refptr<const Extension> extension =
211 ExtensionBuilder().SetManifest(make_scoped_ptr(manifest->DeepCopy()))
212 .SetID(crx_file::id_util::GenerateId("component"))
213 .SetLocation(Manifest::COMPONENT)
214 .Build();
215 service()->AddExtension(extension.get());
217 ExtensionContextMenuModel menu(extension.get(), GetBrowser());
219 // A component extension's context menu should not include options for
220 // managing extensions or removing it, and should only include an option for
221 // the options page if the extension has one (which this one doesn't).
222 EXPECT_EQ(-1, menu.GetIndexOfCommandId(ExtensionContextMenuModel::CONFIGURE));
223 EXPECT_EQ(-1, menu.GetIndexOfCommandId(ExtensionContextMenuModel::UNINSTALL));
224 EXPECT_EQ(-1, menu.GetIndexOfCommandId(ExtensionContextMenuModel::MANAGE));
225 // The "name" option should be present, but not enabled for component
226 // extensions.
227 EXPECT_NE(-1, menu.GetIndexOfCommandId(ExtensionContextMenuModel::NAME));
228 EXPECT_FALSE(menu.IsCommandIdEnabled(ExtensionContextMenuModel::NAME));
232 // Check that a component extension with an options page does have the options
233 // menu item, and it is enabled.
234 manifest->SetString("options_page", "options_page.html");
235 scoped_refptr<const Extension> extension =
236 ExtensionBuilder()
237 .SetManifest(manifest.Pass())
238 .SetID(crx_file::id_util::GenerateId("component_opts"))
239 .SetLocation(Manifest::COMPONENT)
240 .Build();
241 ExtensionContextMenuModel menu(extension.get(), GetBrowser());
242 service()->AddExtension(extension.get());
243 EXPECT_TRUE(extensions::OptionsPageInfo::HasOptionsPage(extension.get()));
244 EXPECT_NE(-1, menu.GetIndexOfCommandId(ExtensionContextMenuModel::CONFIGURE));
245 EXPECT_TRUE(menu.IsCommandIdEnabled(ExtensionContextMenuModel::CONFIGURE));
249 TEST_F(ExtensionContextMenuModelTest, ExtensionItemTest) {
250 InitializeEmptyExtensionService();
251 scoped_refptr<const Extension> extension =
252 BuildExtension("extension",
253 manifest_keys::kPageAction,
254 Manifest::INTERNAL);
255 ASSERT_TRUE(extension.get());
256 service()->AddExtension(extension.get());
258 // Create a MenuManager for adding context items.
259 MenuManager* manager = static_cast<MenuManager*>(
260 (MenuManagerFactory::GetInstance()->SetTestingFactoryAndUse(
261 profile(),
262 &MenuManagerFactory::BuildServiceInstanceForTesting)));
263 ASSERT_TRUE(manager);
265 ExtensionContextMenuModel menu(extension.get(), GetBrowser());
267 // There should be no extension items yet.
268 EXPECT_EQ(0, CountExtensionItems(menu));
270 // Add a browser action menu item for |extension| to |manager|.
271 AddContextItemAndRefreshModel(manager, extension.get(),
272 MenuItem::BROWSER_ACTION, &menu);
274 // Since |extension| has a page action, the browser action menu item should
275 // not be present.
276 EXPECT_EQ(0, CountExtensionItems(menu));
278 // Add a page action menu item and reset the context menu.
279 AddContextItemAndRefreshModel(manager, extension.get(), MenuItem::PAGE_ACTION,
280 &menu);
282 // The page action item should be present because |extension| has a page
283 // action.
284 EXPECT_EQ(1, CountExtensionItems(menu));
286 // Create more page action items to test top level menu item limitations.
287 for (int i = 0; i < api::context_menus::ACTION_MENU_TOP_LEVEL_LIMIT; ++i)
288 AddContextItemAndRefreshModel(manager, extension.get(),
289 MenuItem::PAGE_ACTION, &menu);
291 // The menu should only have a limited number of extension items, since they
292 // are all top level items, and we limit the number of top level extension
293 // items.
294 EXPECT_EQ(api::context_menus::ACTION_MENU_TOP_LEVEL_LIMIT,
295 CountExtensionItems(menu));
297 AddContextItemAndRefreshModel(manager, extension.get(), MenuItem::PAGE_ACTION,
298 &menu);
300 // Adding another top level item should not increase the count.
301 EXPECT_EQ(api::context_menus::ACTION_MENU_TOP_LEVEL_LIMIT,
302 CountExtensionItems(menu));
305 // Test that the "show" and "hide" menu items appear correctly in the extension
306 // context menu.
307 TEST_F(ExtensionContextMenuModelTest, ExtensionContextMenuShowAndHide) {
308 InitializeEmptyExtensionService();
309 scoped_refptr<const Extension> page_action =
310 BuildExtension("page_action_extension",
311 manifest_keys::kPageAction,
312 Manifest::INTERNAL);
313 ASSERT_TRUE(page_action.get());
314 scoped_refptr<const Extension> browser_action =
315 BuildExtension("browser_action_extension",
316 manifest_keys::kBrowserAction,
317 Manifest::INTERNAL);
318 ASSERT_TRUE(browser_action.get());
320 service()->AddExtension(page_action.get());
321 service()->AddExtension(browser_action.get());
323 // For laziness.
324 const ExtensionContextMenuModel::MenuEntries visibility_command =
325 ExtensionContextMenuModel::TOGGLE_VISIBILITY;
326 base::string16 hide_string =
327 l10n_util::GetStringUTF16(IDS_EXTENSIONS_HIDE_BUTTON);
328 base::string16 redesign_hide_string =
329 l10n_util::GetStringUTF16(IDS_EXTENSIONS_HIDE_BUTTON_IN_MENU);
330 base::string16 redesign_show_string =
331 l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_BUTTON_IN_TOOLBAR);
332 base::string16 redesign_keep_string =
333 l10n_util::GetStringUTF16(IDS_EXTENSIONS_KEEP_BUTTON_IN_TOOLBAR);
336 ExtensionContextMenuModel menu(page_action.get(), GetBrowser(),
337 ExtensionContextMenuModel::VISIBLE, nullptr);
339 int index = GetCommandIndex(menu, visibility_command);
340 // Without the toolbar redesign switch, page action menus shouldn't have a
341 // visibility option.
342 EXPECT_EQ(-1, index);
346 ExtensionContextMenuModel menu(browser_action.get(), GetBrowser(),
347 ExtensionContextMenuModel::VISIBLE, nullptr);
348 int index = GetCommandIndex(menu, visibility_command);
349 // Browser actions should have the visibility option.
350 EXPECT_NE(-1, index);
351 // Since the action is currently visible, it should have the option to hide
352 // it.
353 EXPECT_EQ(hide_string, menu.GetLabelAt(index));
356 // Enabling the toolbar redesign switch should give page actions the button.
357 FeatureSwitch::ScopedOverride enable_toolbar_redesign(
358 FeatureSwitch::extension_action_redesign(), true);
360 ExtensionContextMenuModel menu(page_action.get(), GetBrowser(),
361 ExtensionContextMenuModel::VISIBLE, nullptr);
362 int index = GetCommandIndex(menu, visibility_command);
363 EXPECT_NE(-1, index);
364 EXPECT_EQ(redesign_hide_string, menu.GetLabelAt(index));
368 // If the action is overflowed, it should have the "Show button in toolbar"
369 // string.
370 ExtensionContextMenuModel menu(browser_action.get(), GetBrowser(),
371 ExtensionContextMenuModel::OVERFLOWED,
372 nullptr);
373 int index = GetCommandIndex(menu, visibility_command);
374 EXPECT_NE(-1, index);
375 EXPECT_EQ(redesign_show_string, menu.GetLabelAt(index));
379 // If the action is transitively visible, as happens when it is showing a
380 // popup, we should use a "Keep button in toolbar" string.
381 ExtensionContextMenuModel menu(
382 browser_action.get(), GetBrowser(),
383 ExtensionContextMenuModel::TRANSITIVELY_VISIBLE, nullptr);
384 int index = GetCommandIndex(menu, visibility_command);
385 EXPECT_NE(-1, index);
386 EXPECT_EQ(redesign_keep_string, menu.GetLabelAt(index));
390 TEST_F(ExtensionContextMenuModelTest, ExtensionContextUninstall) {
391 InitializeEmptyExtensionService();
393 scoped_refptr<const Extension> extension = BuildExtension(
394 "extension", manifest_keys::kBrowserAction, Manifest::INTERNAL);
395 ASSERT_TRUE(extension.get());
396 service()->AddExtension(extension.get());
397 const std::string extension_id = extension->id();
398 ASSERT_TRUE(registry()->enabled_extensions().GetByID(extension_id));
400 ScopedTestDialogAutoConfirm auto_confirm(ScopedTestDialogAutoConfirm::ACCEPT);
401 TestExtensionRegistryObserver uninstalled_observer(registry());
403 // Scope the menu so that it's destroyed during the uninstall process. This
404 // reflects what normally happens (Chrome closes the menu when the uninstall
405 // dialog shows up).
406 ExtensionContextMenuModel menu(extension.get(), GetBrowser(),
407 ExtensionContextMenuModel::VISIBLE, nullptr);
408 menu.ExecuteCommand(ExtensionContextMenuModel::UNINSTALL, 0);
410 uninstalled_observer.WaitForExtensionUninstalled();
411 EXPECT_FALSE(registry()->GetExtensionById(extension_id,
412 ExtensionRegistry::EVERYTHING));
415 } // namespace extensions