Elim cr-checkbox
[chromium-blink-merge.git] / chrome / browser / extensions / extension_context_menu_model.cc
blob676d2383f034b896f1b9c3a8ca044685cb39a2e6
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/prefs/pref_service.h"
8 #include "base/strings/utf_string_conversions.h"
9 #include "chrome/app/chrome_command_ids.h"
10 #include "chrome/browser/extensions/active_script_controller.h"
11 #include "chrome/browser/extensions/api/extension_action/extension_action_api.h"
12 #include "chrome/browser/extensions/context_menu_matcher.h"
13 #include "chrome/browser/extensions/extension_action.h"
14 #include "chrome/browser/extensions/extension_action_manager.h"
15 #include "chrome/browser/extensions/extension_tab_util.h"
16 #include "chrome/browser/extensions/extension_uninstall_dialog.h"
17 #include "chrome/browser/extensions/menu_manager.h"
18 #include "chrome/browser/profiles/profile.h"
19 #include "chrome/browser/sessions/session_tab_helper.h"
20 #include "chrome/browser/ui/browser.h"
21 #include "chrome/browser/ui/browser_window.h"
22 #include "chrome/browser/ui/chrome_pages.h"
23 #include "chrome/browser/ui/tabs/tab_strip_model.h"
24 #include "chrome/common/extensions/extension_constants.h"
25 #include "chrome/common/pref_names.h"
26 #include "chrome/common/url_constants.h"
27 #include "chrome/grit/chromium_strings.h"
28 #include "chrome/grit/generated_resources.h"
29 #include "chrome/grit/theme_resources.h"
30 #include "content/public/browser/web_contents.h"
31 #include "content/public/common/context_menu_params.h"
32 #include "extensions/browser/extension_registry.h"
33 #include "extensions/browser/extension_system.h"
34 #include "extensions/browser/management_policy.h"
35 #include "extensions/browser/uninstall_reason.h"
36 #include "extensions/common/extension.h"
37 #include "extensions/common/feature_switch.h"
38 #include "extensions/common/manifest_handlers/options_page_info.h"
39 #include "extensions/common/manifest_url_handlers.h"
40 #include "ui/base/l10n/l10n_util.h"
41 #include "ui/base/resource/resource_bundle.h"
42 #include "ui/gfx/image/image.h"
44 namespace extensions {
46 namespace {
48 // Returns true if the given |item| is of the given |type|.
49 bool MenuItemMatchesAction(ExtensionContextMenuModel::ActionType type,
50 const MenuItem* item) {
51 if (type == ExtensionContextMenuModel::NO_ACTION)
52 return false;
54 const MenuItem::ContextList& contexts = item->contexts();
56 if (contexts.Contains(MenuItem::ALL))
57 return true;
58 if (contexts.Contains(MenuItem::PAGE_ACTION) &&
59 (type == ExtensionContextMenuModel::PAGE_ACTION))
60 return true;
61 if (contexts.Contains(MenuItem::BROWSER_ACTION) &&
62 (type == ExtensionContextMenuModel::BROWSER_ACTION))
63 return true;
65 return false;
68 // Returns the id for the visibility command for the given |extension|, or -1
69 // if none should be shown.
70 int GetVisibilityStringId(
71 Profile* profile,
72 const Extension* extension,
73 ExtensionContextMenuModel::ButtonVisibility button_visibility) {
74 DCHECK(profile);
75 int string_id = -1;
76 if (!FeatureSwitch::extension_action_redesign()->IsEnabled()) {
77 // Without the toolbar redesign, we only show the visibility toggle for
78 // browser actions, and only give the option to hide.
79 if (ExtensionActionManager::Get(profile)->GetBrowserAction(*extension)) {
80 string_id = IDS_EXTENSIONS_HIDE_BUTTON;
82 } else {
83 // With the redesign, we display "show" or "hide" based on the icon's
84 // visibility, and can have "transitively shown" buttons that are shown
85 // only while the button has a popup or menu visible.
86 switch (button_visibility) {
87 case (ExtensionContextMenuModel::VISIBLE):
88 string_id = IDS_EXTENSIONS_HIDE_BUTTON_IN_MENU;
89 break;
90 case (ExtensionContextMenuModel::TRANSITIVELY_VISIBLE):
91 string_id = IDS_EXTENSIONS_KEEP_BUTTON_IN_TOOLBAR;
92 break;
93 case (ExtensionContextMenuModel::OVERFLOWED):
94 string_id = IDS_EXTENSIONS_SHOW_BUTTON_IN_TOOLBAR;
95 break;
98 return string_id;
101 // Returns true if the given |extension| is required to remain installed by
102 // policy.
103 bool IsExtensionRequiredByPolicy(const Extension* extension,
104 Profile* profile) {
105 ManagementPolicy* policy = ExtensionSystem::Get(profile)->management_policy();
106 return !policy->UserMayModifySettings(extension, nullptr) ||
107 policy->MustRemainInstalled(extension, nullptr);
110 // A stub for the uninstall dialog.
111 // TODO(devlin): Ideally, we would just have the uninstall dialog take a
112 // base::Callback, but that's a bunch of churn.
113 class UninstallDialogHelper : public ExtensionUninstallDialog::Delegate {
114 public:
115 // Kicks off the asynchronous process to confirm and uninstall the given
116 // |extension|.
117 static void UninstallExtension(Browser* browser, const Extension* extension) {
118 UninstallDialogHelper* helper = new UninstallDialogHelper();
119 helper->BeginUninstall(browser, extension);
122 private:
123 // This class handles its own lifetime.
124 UninstallDialogHelper() {}
125 ~UninstallDialogHelper() override {}
127 void BeginUninstall(Browser* browser, const Extension* extension) {
128 uninstall_dialog_.reset(ExtensionUninstallDialog::Create(
129 browser->profile(), browser->window()->GetNativeWindow(), this));
130 uninstall_dialog_->ConfirmUninstall(extension,
131 UNINSTALL_REASON_USER_INITIATED,
132 UNINSTALL_SOURCE_TOOLBAR_CONTEXT_MENU);
135 // ExtensionUninstallDialog::Delegate:
136 void OnExtensionUninstallDialogClosed(bool did_start_uninstall,
137 const base::string16& error) override {
138 delete this;
141 scoped_ptr<ExtensionUninstallDialog> uninstall_dialog_;
143 DISALLOW_COPY_AND_ASSIGN(UninstallDialogHelper);
146 } // namespace
148 ExtensionContextMenuModel::ExtensionContextMenuModel(
149 const Extension* extension,
150 Browser* browser,
151 ButtonVisibility button_visibility,
152 PopupDelegate* delegate)
153 : SimpleMenuModel(this),
154 extension_id_(extension->id()),
155 is_component_(Manifest::IsComponentLocation(extension->location())),
156 browser_(browser),
157 profile_(browser->profile()),
158 delegate_(delegate),
159 action_type_(NO_ACTION),
160 extension_items_count_(0) {
161 InitMenu(extension, button_visibility);
163 if (profile_->GetPrefs()->GetBoolean(prefs::kExtensionsUIDeveloperMode) &&
164 delegate_ &&
165 !is_component_) {
166 AddSeparator(ui::NORMAL_SEPARATOR);
167 AddItemWithStringId(INSPECT_POPUP, IDS_EXTENSION_ACTION_INSPECT_POPUP);
171 ExtensionContextMenuModel::ExtensionContextMenuModel(const Extension* extension,
172 Browser* browser)
173 : ExtensionContextMenuModel(extension, browser, VISIBLE, nullptr) {
176 bool ExtensionContextMenuModel::IsCommandIdChecked(int command_id) const {
177 if (command_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST &&
178 command_id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST)
179 return extension_items_->IsCommandIdChecked(command_id);
180 return false;
183 bool ExtensionContextMenuModel::IsCommandIdEnabled(int command_id) const {
184 const Extension* extension = GetExtension();
185 if (!extension)
186 return false;
188 if (command_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST &&
189 command_id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) {
190 return extension_items_->IsCommandIdEnabled(command_id);
191 } else if (command_id == CONFIGURE) {
192 return OptionsPageInfo::HasOptionsPage(extension);
193 } else if (command_id == NAME) {
194 // The NAME links to the Homepage URL. If the extension doesn't have a
195 // homepage, we just disable this menu item. We also disable for component
196 // extensions, because it doesn't make sense to link to a webstore page or
197 // chrome://extensions.
198 return ManifestURL::GetHomepageURL(extension).is_valid() && !is_component_;
199 } else if (command_id == INSPECT_POPUP) {
200 content::WebContents* web_contents = GetActiveWebContents();
201 if (!web_contents)
202 return false;
204 return extension_action_ &&
205 extension_action_->HasPopup(SessionTabHelper::IdForTab(web_contents));
206 } else if (command_id == UNINSTALL) {
207 return !IsExtensionRequiredByPolicy(extension, profile_);
209 return true;
212 bool ExtensionContextMenuModel::GetAcceleratorForCommandId(
213 int command_id, ui::Accelerator* accelerator) {
214 return false;
217 void ExtensionContextMenuModel::ExecuteCommand(int command_id,
218 int event_flags) {
219 const Extension* extension = GetExtension();
220 if (!extension)
221 return;
223 if (command_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST &&
224 command_id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) {
225 content::WebContents* web_contents =
226 browser_->tab_strip_model()->GetActiveWebContents();
227 DCHECK(extension_items_);
228 extension_items_->ExecuteCommand(
229 command_id, web_contents, content::ContextMenuParams());
230 return;
233 switch (command_id) {
234 case NAME: {
235 content::OpenURLParams params(ManifestURL::GetHomepageURL(extension),
236 content::Referrer(), NEW_FOREGROUND_TAB,
237 ui::PAGE_TRANSITION_LINK, false);
238 browser_->OpenURL(params);
239 break;
241 case ALWAYS_RUN: {
242 content::WebContents* web_contents = GetActiveWebContents();
243 if (web_contents) {
244 ActiveScriptController::GetForWebContents(web_contents)
245 ->AlwaysRunOnVisibleOrigin(extension);
247 break;
249 case CONFIGURE:
250 DCHECK(OptionsPageInfo::HasOptionsPage(extension));
251 ExtensionTabUtil::OpenOptionsPage(extension, browser_);
252 break;
253 case TOGGLE_VISIBILITY: {
254 ExtensionActionAPI* api = ExtensionActionAPI::Get(profile_);
255 bool visible = api->GetBrowserActionVisibility(extension->id());
256 api->SetBrowserActionVisibility(extension->id(), !visible);
257 break;
259 case UNINSTALL: {
260 UninstallDialogHelper::UninstallExtension(browser_, extension);
261 break;
263 case MANAGE: {
264 chrome::ShowExtensions(browser_, extension->id());
265 break;
267 case INSPECT_POPUP: {
268 delegate_->InspectPopup();
269 break;
271 default:
272 NOTREACHED() << "Unknown option";
273 break;
277 ExtensionContextMenuModel::~ExtensionContextMenuModel() {}
279 void ExtensionContextMenuModel::InitMenu(const Extension* extension,
280 ButtonVisibility button_visibility) {
281 DCHECK(extension);
283 ExtensionActionManager* extension_action_manager =
284 ExtensionActionManager::Get(profile_);
285 extension_action_ = extension_action_manager->GetBrowserAction(*extension);
286 if (!extension_action_) {
287 extension_action_ = extension_action_manager->GetPageAction(*extension);
288 if (extension_action_)
289 action_type_ = PAGE_ACTION;
290 } else {
291 action_type_ = BROWSER_ACTION;
294 extension_items_.reset(new ContextMenuMatcher(
295 profile_, this, this, base::Bind(MenuItemMatchesAction, action_type_)));
297 std::string extension_name = extension->name();
298 // Ampersands need to be escaped to avoid being treated like
299 // mnemonics in the menu.
300 base::ReplaceChars(extension_name, "&", "&&", &extension_name);
301 AddItem(NAME, base::UTF8ToUTF16(extension_name));
302 AppendExtensionItems();
303 AddSeparator(ui::NORMAL_SEPARATOR);
305 // Add the "Always Allow" item for adding persisted permissions for script
306 // injections if there is an active action for this extension. Note that this
307 // will add it to *all* extension action context menus, not just the one
308 // attached to the script injection request icon, but that's okay.
309 content::WebContents* web_contents = GetActiveWebContents();
310 if (web_contents &&
311 ActiveScriptController::GetForWebContents(web_contents)
312 ->WantsToRun(extension)) {
313 AddItemWithStringId(ALWAYS_RUN, IDS_EXTENSIONS_ALWAYS_RUN);
316 if (!is_component_ || OptionsPageInfo::HasOptionsPage(extension))
317 AddItemWithStringId(CONFIGURE, IDS_EXTENSIONS_OPTIONS_MENU_ITEM);
319 if (!is_component_) {
320 bool is_required_by_policy =
321 IsExtensionRequiredByPolicy(extension, profile_);
322 int message_id = is_required_by_policy ?
323 IDS_EXTENSIONS_INSTALLED_BY_ADMIN : IDS_EXTENSIONS_UNINSTALL;
324 AddItem(UNINSTALL, l10n_util::GetStringUTF16(message_id));
325 if (is_required_by_policy) {
326 int uninstall_index = GetIndexOfCommandId(UNINSTALL);
327 SetIcon(uninstall_index,
328 ui::ResourceBundle::GetSharedInstance().GetImageNamed(
329 IDR_OMNIBOX_HTTPS_POLICY_WARNING));
333 // Add a toggle visibility (show/hide) if the extension icon is shown on the
334 // toolbar.
335 int visibility_string_id =
336 GetVisibilityStringId(profile_, extension, button_visibility);
337 if (visibility_string_id != -1)
338 AddItemWithStringId(TOGGLE_VISIBILITY, visibility_string_id);
340 if (!is_component_) {
341 AddSeparator(ui::NORMAL_SEPARATOR);
342 AddItemWithStringId(MANAGE, IDS_MANAGE_EXTENSION);
346 const Extension* ExtensionContextMenuModel::GetExtension() const {
347 return ExtensionRegistry::Get(profile_)->enabled_extensions().GetByID(
348 extension_id_);
351 void ExtensionContextMenuModel::AppendExtensionItems() {
352 extension_items_->Clear();
354 MenuManager* menu_manager = MenuManager::Get(profile_);
355 if (!menu_manager ||
356 !menu_manager->MenuItems(MenuItem::ExtensionKey(extension_id_)))
357 return;
359 AddSeparator(ui::NORMAL_SEPARATOR);
361 extension_items_count_ = 0;
362 extension_items_->AppendExtensionItems(MenuItem::ExtensionKey(extension_id_),
363 base::string16(),
364 &extension_items_count_,
365 true); // is_action_menu
368 content::WebContents* ExtensionContextMenuModel::GetActiveWebContents() const {
369 return browser_->tab_strip_model()->GetActiveWebContents();
372 } // namespace extensions