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