app_context_menu.cc revision 90dce4d38c5ff5333bea97d859d4e484e27edf0c
1// Copyright 2013 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/ui/app_list/app_context_menu.h"
6
7#include "chrome/app/chrome_command_ids.h"
8#include "chrome/browser/extensions/context_menu_matcher.h"
9#include "chrome/browser/extensions/extension_service.h"
10#include "chrome/browser/extensions/extension_system.h"
11#include "chrome/browser/extensions/extension_uninstall_dialog.h"
12#include "chrome/browser/extensions/management_policy.h"
13#include "chrome/browser/prefs/incognito_mode_prefs.h"
14#include "chrome/browser/profiles/profile.h"
15#include "chrome/browser/ui/app_list/app_context_menu_delegate.h"
16#include "chrome/browser/ui/app_list/app_list_controller_delegate.h"
17#include "chrome/browser/ui/browser_navigator.h"
18#include "chrome/common/extensions/manifest_url_handler.h"
19#include "content/public/common/context_menu_params.h"
20#include "grit/chromium_strings.h"
21#include "grit/generated_resources.h"
22#include "ui/base/l10n/l10n_util.h"
23
24#if defined(USE_ASH)
25#include "ash/shell.h"
26#endif
27
28using extensions::Extension;
29
30namespace app_list {
31
32namespace {
33
34enum CommandId {
35  LAUNCH_NEW = 100,
36  TOGGLE_PIN,
37  CREATE_SHORTCUTS,
38  OPTIONS,
39  UNINSTALL,
40  DETAILS,
41  MENU_NEW_WINDOW,
42  MENU_NEW_INCOGNITO_WINDOW,
43  // Order matters in LAUNCHER_TYPE_xxxx and must match LaunchType.
44  LAUNCH_TYPE_START = 200,
45  LAUNCH_TYPE_PINNED_TAB = LAUNCH_TYPE_START,
46  LAUNCH_TYPE_REGULAR_TAB,
47  LAUNCH_TYPE_FULLSCREEN,
48  LAUNCH_TYPE_WINDOW,
49  LAUNCH_TYPE_LAST,
50};
51
52// ExtensionUninstaller runs the extension uninstall flow. It shows the
53// extension uninstall dialog and wait for user to confirm or cancel the
54// uninstall.
55class ExtensionUninstaller : public ExtensionUninstallDialog::Delegate {
56 public:
57  ExtensionUninstaller(Profile* profile,
58                       const std::string& extension_id,
59                       AppListControllerDelegate* controller)
60      : profile_(profile),
61        app_id_(extension_id),
62        controller_(controller) {
63  }
64
65  void Run() {
66    const Extension* extension =
67        extensions::ExtensionSystem::Get(profile_)->extension_service()->
68            GetExtensionById(app_id_, true);
69    if (!extension) {
70      CleanUp();
71      return;
72    }
73    controller_->OnShowExtensionPrompt();
74    dialog_.reset(ExtensionUninstallDialog::Create(profile_, NULL, this));
75    dialog_->ConfirmUninstall(extension);
76  }
77
78 private:
79  // Overridden from ExtensionUninstallDialog::Delegate:
80  virtual void ExtensionUninstallAccepted() OVERRIDE {
81    ExtensionService* service =
82        extensions::ExtensionSystem::Get(profile_)->extension_service();
83    const Extension* extension = service->GetInstalledExtension(app_id_);
84    if (extension) {
85      service->UninstallExtension(app_id_,
86                                  false, /* external_uninstall*/
87                                  NULL);
88    }
89    controller_->OnCloseExtensionPrompt();
90    CleanUp();
91  }
92
93  virtual void ExtensionUninstallCanceled() OVERRIDE {
94    controller_->OnCloseExtensionPrompt();
95    CleanUp();
96  }
97
98  void CleanUp() {
99    delete this;
100  }
101
102  Profile* profile_;
103  std::string app_id_;
104  AppListControllerDelegate* controller_;
105  scoped_ptr<ExtensionUninstallDialog> dialog_;
106
107  DISALLOW_COPY_AND_ASSIGN(ExtensionUninstaller);
108};
109
110extensions::ExtensionPrefs::LaunchType GetExtensionLaunchType(
111    Profile* profile,
112    const Extension* extension) {
113  ExtensionService* service =
114      extensions::ExtensionSystem::Get(profile)->extension_service();
115  return service->extension_prefs()->
116      GetLaunchType(extension, extensions::ExtensionPrefs::LAUNCH_DEFAULT);
117}
118
119void SetExtensionLaunchType(
120    Profile* profile,
121    const std::string& extension_id,
122    extensions::ExtensionPrefs::LaunchType launch_type) {
123  ExtensionService* service =
124      extensions::ExtensionSystem::Get(profile)->extension_service();
125  service->extension_prefs()->SetLaunchType(extension_id, launch_type);
126}
127
128bool MenuItemHasLauncherContext(const extensions::MenuItem* item) {
129  return item->contexts().Contains(extensions::MenuItem::LAUNCHER);
130}
131
132}  // namespace
133
134AppContextMenu::AppContextMenu(AppContextMenuDelegate* delegate,
135                               Profile* profile,
136                               const std::string& app_id,
137                               AppListControllerDelegate* controller,
138                               bool is_platform_app)
139    : delegate_(delegate),
140      profile_(profile),
141      app_id_(app_id),
142      controller_(controller),
143      is_platform_app_(is_platform_app) {
144}
145
146AppContextMenu::~AppContextMenu() {}
147
148ui::MenuModel* AppContextMenu::GetMenuModel() {
149  const Extension* extension = GetExtension();
150  if (!extension)
151    return NULL;
152
153  if (menu_model_.get())
154    return menu_model_.get();
155
156  menu_model_.reset(new ui::SimpleMenuModel(this));
157
158  if (app_id_ == extension_misc::kChromeAppId) {
159    menu_model_->AddItemWithStringId(
160        MENU_NEW_WINDOW,
161        IDS_APP_LIST_NEW_WINDOW);
162    if (!profile_->IsOffTheRecord()) {
163      menu_model_->AddItemWithStringId(
164          MENU_NEW_INCOGNITO_WINDOW,
165          IDS_APP_LIST_NEW_INCOGNITO_WINDOW);
166    }
167  } else {
168    extension_menu_items_.reset(new extensions::ContextMenuMatcher(
169        profile_, this, menu_model_.get(),
170        base::Bind(MenuItemHasLauncherContext)));
171
172    if (!is_platform_app_)
173      menu_model_->AddItem(LAUNCH_NEW, string16());
174
175    int index = 0;
176    extension_menu_items_->AppendExtensionItems(app_id_, string16(),
177                                                &index);
178
179    if (controller_->CanPin()) {
180      menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
181      menu_model_->AddItemWithStringId(
182          TOGGLE_PIN,
183          controller_->IsAppPinned(app_id_) ?
184              IDS_APP_LIST_CONTEXT_MENU_UNPIN :
185              IDS_APP_LIST_CONTEXT_MENU_PIN);
186    }
187
188    if (controller_->CanShowCreateShortcutsDialog()) {
189      menu_model_->AddItemWithStringId(CREATE_SHORTCUTS,
190                                       IDS_NEW_TAB_APP_CREATE_SHORTCUT);
191    }
192
193    if (!is_platform_app_) {
194      menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
195      menu_model_->AddCheckItemWithStringId(
196          LAUNCH_TYPE_REGULAR_TAB,
197          IDS_APP_CONTEXT_MENU_OPEN_REGULAR);
198      menu_model_->AddCheckItemWithStringId(
199          LAUNCH_TYPE_PINNED_TAB,
200          IDS_APP_CONTEXT_MENU_OPEN_PINNED);
201#if defined(USE_ASH)
202      if (!ash::Shell::IsForcedMaximizeMode())
203#endif
204      {
205        menu_model_->AddCheckItemWithStringId(
206            LAUNCH_TYPE_WINDOW,
207            IDS_APP_CONTEXT_MENU_OPEN_WINDOW);
208        // Even though the launch type is Full Screen it is more accurately
209        // described as Maximized in Ash.
210        menu_model_->AddCheckItemWithStringId(
211            LAUNCH_TYPE_FULLSCREEN,
212            IDS_APP_CONTEXT_MENU_OPEN_MAXIMIZED);
213      }
214      menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
215      menu_model_->AddItemWithStringId(OPTIONS, IDS_NEW_TAB_APP_OPTIONS);
216    }
217
218    menu_model_->AddItemWithStringId(DETAILS, IDS_NEW_TAB_APP_DETAILS);
219    menu_model_->AddItemWithStringId(
220        UNINSTALL,
221        is_platform_app_ ? IDS_APP_LIST_UNINSTALL_ITEM
222                         : IDS_EXTENSIONS_UNINSTALL);
223  }
224
225  return menu_model_.get();
226}
227
228const Extension* AppContextMenu::GetExtension() const {
229  const ExtensionService* service =
230      extensions::ExtensionSystem::Get(profile_)->extension_service();
231  const Extension* extension = service->GetInstalledExtension(app_id_);
232  return extension;
233}
234
235void AppContextMenu::ShowExtensionOptions() {
236  const Extension* extension = GetExtension();
237  if (!extension)
238    return;
239
240  chrome::NavigateParams params(
241      profile_,
242      extensions::ManifestURL::GetOptionsPage(extension),
243      content::PAGE_TRANSITION_LINK);
244  chrome::Navigate(&params);
245}
246
247void AppContextMenu::ShowExtensionDetails() {
248  const Extension* extension = GetExtension();
249  if (!extension)
250    return;
251
252  chrome::NavigateParams params(
253      profile_,
254      extensions::ManifestURL::GetDetailsURL(extension),
255      content::PAGE_TRANSITION_LINK);
256  chrome::Navigate(&params);
257}
258
259void AppContextMenu::StartExtensionUninstall() {
260  // ExtensionUninstall deletes itself when done or aborted.
261  ExtensionUninstaller* uninstaller = new ExtensionUninstaller(profile_,
262                                                               app_id_,
263                                                               controller_);
264  uninstaller->Run();
265}
266
267bool AppContextMenu::IsItemForCommandIdDynamic(int command_id) const {
268  return command_id == TOGGLE_PIN || command_id == LAUNCH_NEW;
269}
270
271string16 AppContextMenu::GetLabelForCommandId(int command_id) const {
272  if (command_id == TOGGLE_PIN) {
273    return controller_->IsAppPinned(app_id_) ?
274        l10n_util::GetStringUTF16(IDS_APP_LIST_CONTEXT_MENU_UNPIN) :
275        l10n_util::GetStringUTF16(IDS_APP_LIST_CONTEXT_MENU_PIN);
276  } else if (command_id == LAUNCH_NEW) {
277    if (IsCommandIdChecked(LAUNCH_TYPE_PINNED_TAB) ||
278        IsCommandIdChecked(LAUNCH_TYPE_REGULAR_TAB)) {
279      return l10n_util::GetStringUTF16(IDS_APP_LIST_CONTEXT_MENU_NEW_TAB);
280    } else {
281      return l10n_util::GetStringUTF16(IDS_APP_LIST_CONTEXT_MENU_NEW_WINDOW);
282    }
283  } else {
284    NOTREACHED();
285    return string16();
286  }
287}
288
289bool AppContextMenu::IsCommandIdChecked(int command_id) const {
290  if (command_id >= LAUNCH_TYPE_START && command_id < LAUNCH_TYPE_LAST) {
291    return static_cast<int>(GetExtensionLaunchType(profile_, GetExtension())) +
292        LAUNCH_TYPE_START == command_id;
293  } else if (command_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST &&
294             command_id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) {
295    return extension_menu_items_->IsCommandIdChecked(command_id);
296  }
297  return false;
298}
299
300bool AppContextMenu::IsCommandIdEnabled(int command_id) const {
301  if (command_id == TOGGLE_PIN) {
302    return controller_->CanPin();
303  } else if (command_id == OPTIONS) {
304    const ExtensionService* service =
305        extensions::ExtensionSystem::Get(profile_)->extension_service();
306    const Extension* extension = GetExtension();
307    return service->IsExtensionEnabledForLauncher(app_id_) &&
308           extension &&
309           !extensions::ManifestURL::GetOptionsPage(extension).is_empty();
310  } else if (command_id == UNINSTALL) {
311    const Extension* extension = GetExtension();
312    const extensions::ManagementPolicy* policy =
313        extensions::ExtensionSystem::Get(profile_)->management_policy();
314    return extension &&
315           policy->UserMayModifySettings(extension, NULL);
316  } else if (command_id == DETAILS) {
317    const Extension* extension = GetExtension();
318    return extension && extension->from_webstore();
319  } else if (command_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST &&
320             command_id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) {
321    return extension_menu_items_->IsCommandIdEnabled(command_id);
322  } else if (command_id == MENU_NEW_WINDOW) {
323    // "Normal" windows are not allowed when incognito is enforced.
324    return IncognitoModePrefs::GetAvailability(profile_->GetPrefs()) !=
325        IncognitoModePrefs::FORCED;
326  } else if (command_id == MENU_NEW_INCOGNITO_WINDOW) {
327    // Incognito windows are not allowed when incognito is disabled.
328    return IncognitoModePrefs::GetAvailability(profile_->GetPrefs()) !=
329        IncognitoModePrefs::DISABLED;
330  }
331  return true;
332}
333
334bool AppContextMenu::GetAcceleratorForCommandId(
335    int command_id,
336    ui::Accelerator* acclelrator) {
337  return false;
338}
339
340void AppContextMenu::ExecuteCommand(int command_id, int event_flags) {
341  if (command_id == LAUNCH_NEW) {
342    delegate_->ExecuteLaunchCommand(event_flags);
343  } else if (command_id == TOGGLE_PIN && controller_->CanPin()) {
344    if (controller_->IsAppPinned(app_id_))
345      controller_->UnpinApp(app_id_);
346    else
347      controller_->PinApp(app_id_);
348  } else if (command_id == CREATE_SHORTCUTS) {
349    controller_->ShowCreateShortcutsDialog(profile_, app_id_);
350  } else if (command_id >= LAUNCH_TYPE_START &&
351             command_id < LAUNCH_TYPE_LAST) {
352    SetExtensionLaunchType(profile_,
353                           app_id_,
354                           static_cast<extensions::ExtensionPrefs::LaunchType>(
355                               command_id - LAUNCH_TYPE_START));
356  } else if (command_id == OPTIONS) {
357    ShowExtensionOptions();
358  } else if (command_id == UNINSTALL) {
359    StartExtensionUninstall();
360  } else if (command_id == DETAILS) {
361    ShowExtensionDetails();
362  } else if (command_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST &&
363             command_id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) {
364    extension_menu_items_->ExecuteCommand(command_id, NULL,
365                                          content::ContextMenuParams());
366  } else if (command_id == MENU_NEW_WINDOW) {
367    controller_->CreateNewWindow(profile_, false);
368  } else if (command_id == MENU_NEW_INCOGNITO_WINDOW) {
369    controller_->CreateNewWindow(profile_, true);
370  }
371}
372
373}  // namespace app_list
374