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 "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.h"
25 #include "extensions/common/manifest_constants.h"
26 #include "extensions/common/value_builder.h"
27 #include "testing/gtest/include/gtest/gtest.h"
28 #include "ui/base/l10n/l10n_util.h"
30 namespace extensions
{
34 // Build an extension to pass to the menu constructor, with the an action
35 // specified by |action_key|.
36 scoped_refptr
<const Extension
> BuildExtension(const std::string
& name
,
37 const char* action_key
,
38 Manifest::Location location
) {
39 return ExtensionBuilder()
40 .SetManifest(DictionaryBuilder()
43 .Set("manifest_version", 2)
44 .Set(action_key
, DictionaryBuilder().Pass()))
45 .SetID(crx_file::id_util::GenerateId(name
))
46 .SetLocation(location
)
50 // Create a Browser for the ExtensionContextMenuModel to use.
51 scoped_ptr
<Browser
> CreateBrowser(Profile
* profile
) {
52 Browser::CreateParams
params(profile
, chrome::GetActiveDesktop());
53 TestBrowserWindow test_window
;
54 params
.window
= &test_window
;
55 return scoped_ptr
<Browser
>(new Browser(params
));
58 // Returns the index of the given |command_id| in the given |menu|, or -1 if it
60 int GetCommandIndex(const scoped_refptr
<ExtensionContextMenuModel
> menu
,
62 int item_count
= menu
->GetItemCount();
63 for (int i
= 0; i
< item_count
; ++i
) {
64 if (menu
->GetCommandIdAt(i
) == command_id
)
72 class ExtensionContextMenuModelTest
: public ExtensionServiceTestBase
{
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 int CountExtensionItems(ExtensionContextMenuModel
* model
);
93 ExtensionContextMenuModelTest::ExtensionContextMenuModelTest() : cur_id_(0) {
97 void ExtensionContextMenuModelTest::AddContextItemAndRefreshModel(
99 const Extension
* extension
,
100 MenuItem::Context context
,
101 ExtensionContextMenuModel
* model
) {
102 MenuItem::Type type
= MenuItem::NORMAL
;
103 MenuItem::ContextList
contexts(context
);
104 const MenuItem::ExtensionKey
key(extension
->id());
105 MenuItem::Id
id(false, key
);
107 manager
->AddContextItem(extension
,
117 void ExtensionContextMenuModelTest::RefreshMenu(
118 ExtensionContextMenuModel
* model
) {
119 model
->InitMenu(model
->GetExtension());
122 int ExtensionContextMenuModelTest::CountExtensionItems(
123 ExtensionContextMenuModel
* model
) {
124 return model
->extension_items_count_
;
127 // Tests that applicable menu items are disabled when a ManagementPolicy
129 TEST_F(ExtensionContextMenuModelTest
, RequiredInstallationsDisablesItems
) {
130 InitializeEmptyExtensionService();
132 // First, test that a component extension cannot be uninstalled by the
133 // standard management policy.
134 scoped_refptr
<const Extension
> extension
=
135 BuildExtension("component",
136 manifest_keys::kBrowserAction
,
137 Manifest::COMPONENT
);
138 ASSERT_TRUE(extension
.get());
139 service()->AddExtension(extension
.get());
141 scoped_ptr
<Browser
> browser
= CreateBrowser(profile());
143 scoped_refptr
<ExtensionContextMenuModel
> menu(
144 new ExtensionContextMenuModel(extension
.get(), browser
.get()));
146 // Uninstallation should be disabled.
147 EXPECT_FALSE(menu
->IsCommandIdEnabled(ExtensionContextMenuModel::UNINSTALL
));
149 // Also test that management policy can determine whether or not
150 // policy-installed extensions can be installed/uninstalled.
151 extension
= BuildExtension("extension",
152 manifest_keys::kPageAction
,
154 ASSERT_TRUE(extension
.get());
155 service()->AddExtension(extension
.get());
157 menu
= new ExtensionContextMenuModel(extension
.get(), browser
.get());
159 ExtensionSystem
* system
= ExtensionSystem::Get(profile());
160 system
->management_policy()->UnregisterAllProviders();
162 // Actions should be enabled.
163 ASSERT_TRUE(menu
->IsCommandIdEnabled(ExtensionContextMenuModel::UNINSTALL
));
165 TestManagementPolicyProvider
policy_provider(
166 TestManagementPolicyProvider::PROHIBIT_MODIFY_STATUS
);
167 system
->management_policy()->RegisterProvider(&policy_provider
);
169 // Now the actions are disabled.
170 ASSERT_FALSE(menu
->IsCommandIdEnabled(ExtensionContextMenuModel::UNINSTALL
));
172 // Don't leave |policy_provider| dangling.
173 system
->management_policy()->UnregisterProvider(&policy_provider
);
176 TEST_F(ExtensionContextMenuModelTest
, ExtensionItemTest
) {
177 InitializeEmptyExtensionService();
178 scoped_refptr
<const Extension
> extension
=
179 BuildExtension("extension",
180 manifest_keys::kPageAction
,
182 ASSERT_TRUE(extension
.get());
183 service()->AddExtension(extension
.get());
185 scoped_ptr
<Browser
> browser
= CreateBrowser(profile());
187 // Create a MenuManager for adding context items.
188 MenuManager
* manager
= static_cast<MenuManager
*>(
189 (MenuManagerFactory::GetInstance()->SetTestingFactoryAndUse(
191 &MenuManagerFactory::BuildServiceInstanceForTesting
)));
192 ASSERT_TRUE(manager
);
194 scoped_refptr
<ExtensionContextMenuModel
> menu(
195 new ExtensionContextMenuModel(extension
.get(), browser
.get()));
197 // There should be no extension items yet.
198 EXPECT_EQ(0, CountExtensionItems(menu
.get()));
200 // Add a browser action menu item for |extension| to |manager|.
201 AddContextItemAndRefreshModel(
202 manager
, extension
.get(), MenuItem::BROWSER_ACTION
, menu
.get());
204 // Since |extension| has a page action, the browser action menu item should
206 EXPECT_EQ(0, CountExtensionItems(menu
.get()));
208 // Add a page action menu item and reset the context menu.
209 AddContextItemAndRefreshModel(
210 manager
, extension
.get(), MenuItem::PAGE_ACTION
, menu
.get());
212 // The page action item should be present because |extension| has a page
214 EXPECT_EQ(1, CountExtensionItems(menu
.get()));
216 // Create more page action items to test top level menu item limitations.
217 for (int i
= 0; i
< api::context_menus::ACTION_MENU_TOP_LEVEL_LIMIT
; ++i
)
218 AddContextItemAndRefreshModel(
219 manager
, extension
.get(), MenuItem::PAGE_ACTION
, menu
.get());
221 // The menu should only have a limited number of extension items, since they
222 // are all top level items, and we limit the number of top level extension
224 EXPECT_EQ(api::context_menus::ACTION_MENU_TOP_LEVEL_LIMIT
,
225 CountExtensionItems(menu
.get()));
227 AddContextItemAndRefreshModel(
228 manager
, extension
.get(), MenuItem::PAGE_ACTION
, menu
.get());
230 // Adding another top level item should not increase the count.
231 EXPECT_EQ(api::context_menus::ACTION_MENU_TOP_LEVEL_LIMIT
,
232 CountExtensionItems(menu
.get()));
235 // Test that the "show" and "hide" menu items appear correctly in the extension
237 TEST_F(ExtensionContextMenuModelTest
, ExtensionContextMenuShowAndHide
) {
238 InitializeEmptyExtensionService();
239 scoped_refptr
<const Extension
> page_action
=
240 BuildExtension("page_action_extension",
241 manifest_keys::kPageAction
,
243 ASSERT_TRUE(page_action
.get());
244 scoped_refptr
<const Extension
> browser_action
=
245 BuildExtension("browser_action_extension",
246 manifest_keys::kBrowserAction
,
248 ASSERT_TRUE(browser_action
.get());
250 service()->AddExtension(page_action
.get());
251 service()->AddExtension(browser_action
.get());
253 scoped_ptr
<Browser
> browser
= CreateBrowser(profile());
255 scoped_refptr
<ExtensionContextMenuModel
> menu(
256 new ExtensionContextMenuModel(page_action
.get(), browser
.get()));
259 const ExtensionContextMenuModel::MenuEntries visibility_command
=
260 ExtensionContextMenuModel::TOGGLE_VISIBILITY
;
261 base::string16 hide_string
=
262 l10n_util::GetStringUTF16(IDS_EXTENSIONS_HIDE_BUTTON
);
263 base::string16 show_string
=
264 l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_BUTTON
);
265 ExtensionPrefs
* prefs
= ExtensionPrefs::Get(profile());
267 int index
= GetCommandIndex(menu
, visibility_command
);
268 // Without the toolbar redesign switch, page action menus shouldn't have a
269 // visibility option.
270 EXPECT_EQ(-1, index
);
272 menu
= new ExtensionContextMenuModel(browser_action
.get(), browser
.get());
273 index
= GetCommandIndex(menu
, visibility_command
);
274 // Browser actions should have the visibility option.
275 EXPECT_NE(-1, index
);
277 // Enabling the toolbar redesign switch should give page actions the button.
278 FeatureSwitch::ScopedOverride
enable_toolbar_redesign(
279 FeatureSwitch::extension_action_redesign(), true);
280 menu
= new ExtensionContextMenuModel(page_action
.get(), browser
.get());
281 index
= GetCommandIndex(menu
, visibility_command
);
282 EXPECT_NE(-1, index
);
284 // Next, we test the command label.
285 menu
= new ExtensionContextMenuModel(browser_action
.get(), browser
.get());
286 index
= GetCommandIndex(menu
, visibility_command
);
287 // By default, browser actions should be visible (and therefore the button
288 // should be to hide).
289 EXPECT_TRUE(ExtensionActionAPI::GetBrowserActionVisibility(
290 prefs
, browser_action
->id()));
291 EXPECT_EQ(hide_string
, menu
->GetLabelAt(index
));
293 // Hide the browser action. This should mean the string is "show".
294 ExtensionActionAPI::SetBrowserActionVisibility(
295 prefs
, browser_action
->id(), false);
296 menu
= new ExtensionContextMenuModel(browser_action
.get(), browser
.get());
297 index
= GetCommandIndex(menu
, visibility_command
);
298 EXPECT_NE(-1, index
);
299 EXPECT_EQ(show_string
, menu
->GetLabelAt(index
));
302 } // namespace extensions