app_context_menu.cc revision eb525c5499e34cc9c4b825d6d9e75bb07cc06ace
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_->CanDoCreateShortcutsFlow(is_platform_app_)) {
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#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      }
221      menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
222      menu_model_->AddItemWithStringId(OPTIONS, IDS_NEW_TAB_APP_OPTIONS);
223    }
224
225    menu_model_->AddItemWithStringId(DETAILS, IDS_NEW_TAB_APP_DETAILS);
226    menu_model_->AddItemWithStringId(
227        UNINSTALL,
228        is_platform_app_ ? IDS_APP_LIST_UNINSTALL_ITEM
229                         : IDS_EXTENSIONS_UNINSTALL);
230  }
231
232  return menu_model_.get();
233}
234
235const Extension* AppContextMenu::GetExtension() const {
236  const ExtensionService* service =
237      extensions::ExtensionSystem::Get(profile_)->extension_service();
238  const Extension* extension = service->GetInstalledExtension(app_id_);
239  return extension;
240}
241
242void AppContextMenu::ShowExtensionOptions() {
243  const Extension* extension = GetExtension();
244  if (!extension)
245    return;
246
247  chrome::NavigateParams params(
248      profile_,
249      extensions::ManifestURL::GetOptionsPage(extension),
250      content::PAGE_TRANSITION_LINK);
251  chrome::Navigate(&params);
252}
253
254void AppContextMenu::ShowExtensionDetails() {
255  const Extension* extension = GetExtension();
256  if (!extension)
257    return;
258
259  chrome::NavigateParams params(
260      profile_,
261      extensions::ManifestURL::GetDetailsURL(extension),
262      content::PAGE_TRANSITION_LINK);
263  chrome::Navigate(&params);
264}
265
266void AppContextMenu::StartExtensionUninstall() {
267  // ExtensionUninstall deletes itself when done or aborted.
268  ExtensionUninstaller* uninstaller = new ExtensionUninstaller(profile_,
269                                                               app_id_,
270                                                               controller_);
271  uninstaller->Run();
272}
273
274bool AppContextMenu::IsItemForCommandIdDynamic(int command_id) const {
275  return command_id == TOGGLE_PIN || command_id == LAUNCH_NEW;
276}
277
278string16 AppContextMenu::GetLabelForCommandId(int command_id) const {
279  if (command_id == TOGGLE_PIN) {
280    return controller_->IsAppPinned(app_id_) ?
281        l10n_util::GetStringUTF16(IDS_APP_LIST_CONTEXT_MENU_UNPIN) :
282        l10n_util::GetStringUTF16(IDS_APP_LIST_CONTEXT_MENU_PIN);
283  } else if (command_id == LAUNCH_NEW) {
284    if (IsCommandIdChecked(LAUNCH_TYPE_PINNED_TAB) ||
285        IsCommandIdChecked(LAUNCH_TYPE_REGULAR_TAB)) {
286      return l10n_util::GetStringUTF16(IDS_APP_LIST_CONTEXT_MENU_NEW_TAB);
287    } else {
288      return l10n_util::GetStringUTF16(IDS_APP_LIST_CONTEXT_MENU_NEW_WINDOW);
289    }
290  } else {
291    NOTREACHED();
292    return string16();
293  }
294}
295
296bool AppContextMenu::IsCommandIdChecked(int command_id) const {
297  if (command_id >= LAUNCH_TYPE_START && command_id < LAUNCH_TYPE_LAST) {
298    return static_cast<int>(GetExtensionLaunchType(profile_, GetExtension())) +
299        LAUNCH_TYPE_START == command_id;
300  } else if (command_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST &&
301             command_id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) {
302    return extension_menu_items_->IsCommandIdChecked(command_id);
303  }
304  return false;
305}
306
307bool AppContextMenu::IsCommandIdEnabled(int command_id) const {
308  if (command_id == TOGGLE_PIN) {
309    return controller_->CanPin();
310  } else if (command_id == OPTIONS) {
311    const ExtensionService* service =
312        extensions::ExtensionSystem::Get(profile_)->extension_service();
313    const Extension* extension = GetExtension();
314    return service->IsExtensionEnabledForLauncher(app_id_) &&
315           extension &&
316           !extensions::ManifestURL::GetOptionsPage(extension).is_empty();
317  } else if (command_id == UNINSTALL) {
318    const Extension* extension = GetExtension();
319    const extensions::ManagementPolicy* policy =
320        extensions::ExtensionSystem::Get(profile_)->management_policy();
321    return extension &&
322           policy->UserMayModifySettings(extension, NULL);
323  } else if (command_id == DETAILS) {
324    const Extension* extension = GetExtension();
325    return extension && extension->from_webstore();
326  } else if (command_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST &&
327             command_id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) {
328    return extension_menu_items_->IsCommandIdEnabled(command_id);
329  } else if (command_id == MENU_NEW_WINDOW) {
330    // "Normal" windows are not allowed when incognito is enforced.
331    return IncognitoModePrefs::GetAvailability(profile_->GetPrefs()) !=
332        IncognitoModePrefs::FORCED;
333  } else if (command_id == MENU_NEW_INCOGNITO_WINDOW) {
334    // Incognito windows are not allowed when incognito is disabled.
335    return IncognitoModePrefs::GetAvailability(profile_->GetPrefs()) !=
336        IncognitoModePrefs::DISABLED;
337  }
338  return true;
339}
340
341bool AppContextMenu::GetAcceleratorForCommandId(
342    int command_id,
343    ui::Accelerator* acclelrator) {
344  return false;
345}
346
347void AppContextMenu::ExecuteCommand(int command_id, int event_flags) {
348  if (command_id == LAUNCH_NEW) {
349    delegate_->ExecuteLaunchCommand(event_flags);
350  } else if (command_id == TOGGLE_PIN && controller_->CanPin()) {
351    if (controller_->IsAppPinned(app_id_))
352      controller_->UnpinApp(app_id_);
353    else
354      controller_->PinApp(app_id_);
355  } else if (command_id == CREATE_SHORTCUTS) {
356    controller_->DoCreateShortcutsFlow(profile_, app_id_);
357  } else if (command_id >= LAUNCH_TYPE_START &&
358             command_id < LAUNCH_TYPE_LAST) {
359    SetExtensionLaunchType(profile_,
360                           app_id_,
361                           static_cast<extensions::ExtensionPrefs::LaunchType>(
362                               command_id - LAUNCH_TYPE_START));
363  } else if (command_id == OPTIONS) {
364    ShowExtensionOptions();
365  } else if (command_id == UNINSTALL) {
366    StartExtensionUninstall();
367  } else if (command_id == DETAILS) {
368    ShowExtensionDetails();
369  } else if (command_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST &&
370             command_id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) {
371    extension_menu_items_->ExecuteCommand(command_id, NULL,
372                                          content::ContextMenuParams());
373  } else if (command_id == MENU_NEW_WINDOW) {
374    controller_->CreateNewWindow(profile_, false);
375  } else if (command_id == MENU_NEW_INCOGNITO_WINDOW) {
376    controller_->CreateNewWindow(profile_, true);
377  }
378}
379
380}  // namespace app_list
381