app_context_menu.cc revision 68043e1e95eeb07d5cae7aca370b26518b0867d6
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    if (controller_->CanPin()) {
183      menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
184      menu_model_->AddItemWithStringId(
185          TOGGLE_PIN,
186          controller_->IsAppPinned(app_id_) ?
187              IDS_APP_LIST_CONTEXT_MENU_UNPIN :
188              IDS_APP_LIST_CONTEXT_MENU_PIN);
189    }
190
191    if (controller_->CanDoCreateShortcutsFlow(is_platform_app_)) {
192      menu_model_->AddItemWithStringId(CREATE_SHORTCUTS,
193                                       IDS_NEW_TAB_APP_CREATE_SHORTCUT);
194    }
195
196    if (!is_platform_app_) {
197      menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
198      menu_model_->AddCheckItemWithStringId(
199          LAUNCH_TYPE_REGULAR_TAB,
200          IDS_APP_CONTEXT_MENU_OPEN_REGULAR);
201      menu_model_->AddCheckItemWithStringId(
202          LAUNCH_TYPE_PINNED_TAB,
203          IDS_APP_CONTEXT_MENU_OPEN_PINNED);
204#if defined(USE_ASH)
205      if (!ash::Shell::IsForcedMaximizeMode())
206#endif
207      {
208#if defined(OS_MACOSX)
209        // Mac does not support standalone web app browser windows or maximize.
210        menu_model_->AddCheckItemWithStringId(
211            LAUNCH_TYPE_FULLSCREEN,
212            IDS_APP_CONTEXT_MENU_OPEN_FULLSCREEN);
213#else
214        menu_model_->AddCheckItemWithStringId(
215            LAUNCH_TYPE_WINDOW,
216            IDS_APP_CONTEXT_MENU_OPEN_WINDOW);
217        // Even though the launch type is Full Screen it is more accurately
218        // described as Maximized in Ash.
219        menu_model_->AddCheckItemWithStringId(
220            LAUNCH_TYPE_FULLSCREEN,
221            IDS_APP_CONTEXT_MENU_OPEN_MAXIMIZED);
222#endif
223      }
224      menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
225      menu_model_->AddItemWithStringId(OPTIONS, IDS_NEW_TAB_APP_OPTIONS);
226    }
227
228    menu_model_->AddItemWithStringId(DETAILS, IDS_NEW_TAB_APP_DETAILS);
229    menu_model_->AddItemWithStringId(
230        UNINSTALL,
231        is_platform_app_ ? IDS_APP_LIST_UNINSTALL_ITEM
232                         : IDS_EXTENSIONS_UNINSTALL);
233  }
234
235  return menu_model_.get();
236}
237
238const Extension* AppContextMenu::GetExtension() const {
239  const ExtensionService* service =
240      extensions::ExtensionSystem::Get(profile_)->extension_service();
241  const Extension* extension = service->GetInstalledExtension(app_id_);
242  return extension;
243}
244
245void AppContextMenu::ShowExtensionOptions() {
246  const Extension* extension = GetExtension();
247  if (!extension)
248    return;
249
250  chrome::NavigateParams params(
251      profile_,
252      extensions::ManifestURL::GetOptionsPage(extension),
253      content::PAGE_TRANSITION_LINK);
254  chrome::Navigate(&params);
255}
256
257void AppContextMenu::ShowExtensionDetails() {
258  const Extension* extension = GetExtension();
259  if (!extension)
260    return;
261
262  const GURL url = extensions::ManifestURL::GetDetailsURL(extension);
263  DCHECK_NE(url, GURL::EmptyGURL());
264
265  const std::string source = AppListControllerDelegate::AppListSourceToString(
266      is_search_result_ ?
267          AppListControllerDelegate::LAUNCH_FROM_APP_LIST_SEARCH :
268          AppListControllerDelegate::LAUNCH_FROM_APP_LIST);
269  chrome::NavigateParams params(
270      profile_,
271      net::AppendQueryParameter(url,
272                                extension_urls::kWebstoreSourceField,
273                                source),
274      content::PAGE_TRANSITION_LINK);
275  chrome::Navigate(&params);
276}
277
278void AppContextMenu::StartExtensionUninstall() {
279  // ExtensionUninstall deletes itself when done or aborted.
280  ExtensionUninstaller* uninstaller = new ExtensionUninstaller(profile_,
281                                                               app_id_,
282                                                               controller_);
283  uninstaller->Run();
284}
285
286bool AppContextMenu::IsItemForCommandIdDynamic(int command_id) const {
287  return command_id == TOGGLE_PIN || command_id == LAUNCH_NEW;
288}
289
290string16 AppContextMenu::GetLabelForCommandId(int command_id) const {
291  if (command_id == TOGGLE_PIN) {
292    return controller_->IsAppPinned(app_id_) ?
293        l10n_util::GetStringUTF16(IDS_APP_LIST_CONTEXT_MENU_UNPIN) :
294        l10n_util::GetStringUTF16(IDS_APP_LIST_CONTEXT_MENU_PIN);
295  } else if (command_id == LAUNCH_NEW) {
296#if defined(OS_MACOSX)
297    // Even fullscreen windows launch in a browser tab on Mac.
298    const bool launches_in_tab = true;
299#else
300    const bool launches_in_tab = IsCommandIdChecked(LAUNCH_TYPE_PINNED_TAB) ||
301        IsCommandIdChecked(LAUNCH_TYPE_REGULAR_TAB);
302#endif
303    return launches_in_tab ?
304        l10n_util::GetStringUTF16(IDS_APP_LIST_CONTEXT_MENU_NEW_TAB) :
305        l10n_util::GetStringUTF16(IDS_APP_LIST_CONTEXT_MENU_NEW_WINDOW);
306  } else {
307    NOTREACHED();
308    return string16();
309  }
310}
311
312bool AppContextMenu::IsCommandIdChecked(int command_id) const {
313  if (command_id >= LAUNCH_TYPE_START && command_id < LAUNCH_TYPE_LAST) {
314    return static_cast<int>(GetExtensionLaunchType(profile_, GetExtension())) +
315        LAUNCH_TYPE_START == command_id;
316  } else if (command_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST &&
317             command_id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) {
318    return extension_menu_items_->IsCommandIdChecked(command_id);
319  }
320  return false;
321}
322
323bool AppContextMenu::IsCommandIdEnabled(int command_id) const {
324  if (command_id == TOGGLE_PIN) {
325    return controller_->CanPin();
326  } else if (command_id == OPTIONS) {
327    const ExtensionService* service =
328        extensions::ExtensionSystem::Get(profile_)->extension_service();
329    const Extension* extension = GetExtension();
330    return service->IsExtensionEnabledForLauncher(app_id_) &&
331           extension &&
332           !extensions::ManifestURL::GetOptionsPage(extension).is_empty();
333  } else if (command_id == UNINSTALL) {
334    const Extension* extension = GetExtension();
335    const extensions::ManagementPolicy* policy =
336        extensions::ExtensionSystem::Get(profile_)->management_policy();
337    return extension &&
338           policy->UserMayModifySettings(extension, NULL);
339  } else if (command_id == DETAILS) {
340    const Extension* extension = GetExtension();
341    return extension && extension->from_webstore();
342  } else if (command_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST &&
343             command_id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) {
344    return extension_menu_items_->IsCommandIdEnabled(command_id);
345  } else if (command_id == MENU_NEW_WINDOW) {
346    // "Normal" windows are not allowed when incognito is enforced.
347    return IncognitoModePrefs::GetAvailability(profile_->GetPrefs()) !=
348        IncognitoModePrefs::FORCED;
349  } else if (command_id == MENU_NEW_INCOGNITO_WINDOW) {
350    // Incognito windows are not allowed when incognito is disabled.
351    return IncognitoModePrefs::GetAvailability(profile_->GetPrefs()) !=
352        IncognitoModePrefs::DISABLED;
353  }
354  return true;
355}
356
357bool AppContextMenu::GetAcceleratorForCommandId(
358    int command_id,
359    ui::Accelerator* acclelrator) {
360  return false;
361}
362
363void AppContextMenu::ExecuteCommand(int command_id, int event_flags) {
364  if (command_id == LAUNCH_NEW) {
365    delegate_->ExecuteLaunchCommand(event_flags);
366  } else if (command_id == TOGGLE_PIN && controller_->CanPin()) {
367    if (controller_->IsAppPinned(app_id_))
368      controller_->UnpinApp(app_id_);
369    else
370      controller_->PinApp(app_id_);
371  } else if (command_id == CREATE_SHORTCUTS) {
372    controller_->DoCreateShortcutsFlow(profile_, app_id_);
373  } else if (command_id >= LAUNCH_TYPE_START &&
374             command_id < LAUNCH_TYPE_LAST) {
375    SetExtensionLaunchType(profile_,
376                           app_id_,
377                           static_cast<extensions::ExtensionPrefs::LaunchType>(
378                               command_id - LAUNCH_TYPE_START));
379  } else if (command_id == OPTIONS) {
380    ShowExtensionOptions();
381  } else if (command_id == UNINSTALL) {
382    StartExtensionUninstall();
383  } else if (command_id == DETAILS) {
384    ShowExtensionDetails();
385  } else if (command_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST &&
386             command_id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) {
387    extension_menu_items_->ExecuteCommand(command_id, NULL,
388                                          content::ContextMenuParams());
389  } else if (command_id == MENU_NEW_WINDOW) {
390    controller_->CreateNewWindow(profile_, false);
391  } else if (command_id == MENU_NEW_INCOGNITO_WINDOW) {
392    controller_->CreateNewWindow(profile_, true);
393  }
394}
395
396}  // namespace app_list
397