extension_app_item.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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.
4
5#include "chrome/browser/ui/app_list/extension_app_item.h"
6
7#include "base/prefs/pref_service.h"
8#include "chrome/app/chrome_command_ids.h"
9#include "chrome/browser/extensions/context_menu_matcher.h"
10#include "chrome/browser/extensions/extension_prefs.h"
11#include "chrome/browser/extensions/extension_service.h"
12#include "chrome/browser/extensions/extension_sorting.h"
13#include "chrome/browser/extensions/extension_system.h"
14#include "chrome/browser/extensions/extension_uninstall_dialog.h"
15#include "chrome/browser/extensions/management_policy.h"
16#include "chrome/browser/prefs/incognito_mode_prefs.h"
17#include "chrome/browser/profiles/profile.h"
18#include "chrome/browser/ui/app_list/app_list_controller_delegate.h"
19#include "chrome/browser/ui/browser_navigator.h"
20#include "chrome/browser/ui/browser_tabstrip.h"
21#include "chrome/browser/ui/browser_window.h"
22#include "chrome/browser/ui/extensions/extension_enable_flow.h"
23#include "chrome/browser/ui/webui/ntp/app_launcher_handler.h"
24#include "chrome/common/extensions/api/icons/icons_handler.h"
25#include "chrome/common/extensions/extension.h"
26#include "chrome/common/extensions/extension_constants.h"
27#include "chrome/common/extensions/extension_icon_set.h"
28#include "chrome/common/extensions/manifest_url_handler.h"
29#include "content/public/common/context_menu_params.h"
30#include "grit/chromium_strings.h"
31#include "grit/generated_resources.h"
32#include "grit/theme_resources.h"
33#include "ui/base/l10n/l10n_util.h"
34#include "ui/base/resource/resource_bundle.h"
35#include "ui/gfx/canvas.h"
36#include "ui/gfx/color_utils.h"
37#include "ui/gfx/image/canvas_image_source.h"
38#include "ui/gfx/image/image_skia_operations.h"
39
40using extensions::Extension;
41
42namespace {
43
44enum CommandId {
45  LAUNCH_NEW = 100,
46  TOGGLE_PIN,
47  CREATE_SHORTCUTS,
48  OPTIONS,
49  UNINSTALL,
50  DETAILS,
51  MENU_NEW_WINDOW,
52  MENU_NEW_INCOGNITO_WINDOW,
53  // Order matters in LAUNCHER_TYPE_xxxx and must match LaunchType.
54  LAUNCH_TYPE_START = 200,
55  LAUNCH_TYPE_PINNED_TAB = LAUNCH_TYPE_START,
56  LAUNCH_TYPE_REGULAR_TAB,
57  LAUNCH_TYPE_FULLSCREEN,
58  LAUNCH_TYPE_WINDOW,
59  LAUNCH_TYPE_LAST,
60};
61
62// ExtensionUninstaller decouples ExtensionAppItem from the extension uninstall
63// flow. It shows extension uninstall dialog and wait for user to confirm or
64// cancel the uninstall.
65class ExtensionUninstaller : public ExtensionUninstallDialog::Delegate {
66 public:
67  ExtensionUninstaller(Profile* profile,
68                       const std::string& extension_id,
69                       AppListControllerDelegate* controller)
70      : profile_(profile),
71        extension_id_(extension_id),
72        controller_(controller) {
73  }
74
75  void Run() {
76    const Extension* extension =
77        extensions::ExtensionSystem::Get(profile_)->extension_service()->
78            GetExtensionById(extension_id_, true);
79    if (!extension) {
80      CleanUp();
81      return;
82    }
83    controller_->OnShowExtensionPrompt();
84    dialog_.reset(ExtensionUninstallDialog::Create(profile_, NULL, this));
85    dialog_->ConfirmUninstall(extension);
86  }
87
88 private:
89  // Overridden from ExtensionUninstallDialog::Delegate:
90  virtual void ExtensionUninstallAccepted() OVERRIDE {
91    ExtensionService* service =
92        extensions::ExtensionSystem::Get(profile_)->extension_service();
93    const Extension* extension = service->GetInstalledExtension(extension_id_);
94    if (extension) {
95      service->UninstallExtension(extension_id_,
96                                  false, /* external_uninstall*/
97                                  NULL);
98    }
99    controller_->OnCloseExtensionPrompt();
100    CleanUp();
101  }
102
103  virtual void ExtensionUninstallCanceled() OVERRIDE {
104    controller_->OnCloseExtensionPrompt();
105    CleanUp();
106  }
107
108  void CleanUp() {
109    delete this;
110  }
111
112  Profile* profile_;
113  std::string extension_id_;
114  AppListControllerDelegate* controller_;
115  scoped_ptr<ExtensionUninstallDialog> dialog_;
116
117  DISALLOW_COPY_AND_ASSIGN(ExtensionUninstaller);
118};
119
120class TabOverlayImageSource : public gfx::CanvasImageSource {
121 public:
122  TabOverlayImageSource(const gfx::ImageSkia& icon, const gfx::Size& size)
123      : gfx::CanvasImageSource(size, false),
124        icon_(icon) {
125    if (!icon_.isNull()) {
126      DCHECK_EQ(extension_misc::EXTENSION_ICON_SMALL, icon_.width());
127      DCHECK_EQ(extension_misc::EXTENSION_ICON_SMALL, icon_.height());
128    }
129  }
130  virtual ~TabOverlayImageSource() {}
131
132 private:
133  // gfx::CanvasImageSource overrides:
134  virtual void Draw(gfx::Canvas* canvas) OVERRIDE {
135    using extension_misc::EXTENSION_ICON_SMALL;
136    using extension_misc::EXTENSION_ICON_MEDIUM;
137
138    const int kIconOffset = (EXTENSION_ICON_MEDIUM - EXTENSION_ICON_SMALL) / 2;
139
140    // The tab overlay is not vertically symmetric, to position the app in the
141    // middle of the overlay we need a slight adjustment.
142    const int kVerticalAdjust = 4;
143    canvas->DrawImageInt(
144        *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
145            IDR_APP_LIST_TAB_OVERLAY),
146        0, 0);
147    canvas->DrawImageInt(icon_, kIconOffset, kIconOffset + kVerticalAdjust);
148  }
149
150  gfx::ImageSkia icon_;
151
152  DISALLOW_COPY_AND_ASSIGN(TabOverlayImageSource);
153};
154
155extensions::ExtensionPrefs::LaunchType GetExtensionLaunchType(
156    Profile* profile,
157    const Extension* extension) {
158  ExtensionService* service =
159      extensions::ExtensionSystem::Get(profile)->extension_service();
160  return service->extension_prefs()->
161      GetLaunchType(extension, extensions::ExtensionPrefs::LAUNCH_DEFAULT);
162}
163
164void SetExtensionLaunchType(
165    Profile* profile,
166    const std::string& extension_id,
167    extensions::ExtensionPrefs::LaunchType launch_type) {
168  ExtensionService* service =
169      extensions::ExtensionSystem::Get(profile)->extension_service();
170  service->extension_prefs()->SetLaunchType(extension_id, launch_type);
171}
172
173ExtensionSorting* GetExtensionSorting(Profile* profile) {
174  ExtensionService* service =
175      extensions::ExtensionSystem::Get(profile)->extension_service();
176  return service->extension_prefs()->extension_sorting();
177}
178
179bool MenuItemHasLauncherContext(const extensions::MenuItem* item) {
180  return item->contexts().Contains(extensions::MenuItem::LAUNCHER);
181}
182
183const color_utils::HSL shift = {-1, 0, 0.6};
184
185}  // namespace
186
187ExtensionAppItem::ExtensionAppItem(Profile* profile,
188                                   const std::string& extension_id,
189                                   AppListControllerDelegate* controller,
190                                   const std::string& extension_name,
191                                   const gfx::ImageSkia& installing_icon,
192                                   bool is_platform_app)
193    : ChromeAppListItem(TYPE_APP),
194      profile_(profile),
195      extension_id_(extension_id),
196      controller_(controller),
197      extension_name_(extension_name),
198      installing_icon_(
199          gfx::ImageSkiaOperations::CreateHSLShiftedImage(installing_icon,
200                                                          shift)),
201      is_platform_app_(is_platform_app) {
202  Reload();
203  GetExtensionSorting(profile_)->EnsureValidOrdinals(extension_id_,
204                                                     syncer::StringOrdinal());
205}
206
207ExtensionAppItem::~ExtensionAppItem() {
208}
209
210bool ExtensionAppItem::HasOverlay() const {
211#if defined(OS_CHROMEOS)
212  return false;
213#else
214  return !is_platform_app_ && extension_id_ != extension_misc::kChromeAppId;
215#endif
216}
217
218void ExtensionAppItem::Reload() {
219  const Extension* extension = GetExtension();
220  bool is_installing = !extension;
221  SetIsInstalling(is_installing);
222  if (is_installing) {
223    SetTitle(extension_name_);
224    UpdateIcon();
225    return;
226  }
227  SetTitle(extension->name());
228  LoadImage(extension);
229}
230
231syncer::StringOrdinal ExtensionAppItem::GetPageOrdinal() const {
232  return GetExtensionSorting(profile_)->GetPageOrdinal(extension_id_);
233}
234
235syncer::StringOrdinal ExtensionAppItem::GetAppLaunchOrdinal() const {
236  return GetExtensionSorting(profile_)->GetAppLaunchOrdinal(extension_id_);
237}
238
239void ExtensionAppItem::Move(const ExtensionAppItem* prev,
240                            const ExtensionAppItem* next) {
241  // Does nothing if no predecessor nor successor.
242  if (!prev && !next)
243    return;
244
245  ExtensionService* service =
246      extensions::ExtensionSystem::Get(profile_)->extension_service();
247  service->extension_prefs()->SetAppDraggedByUser(extension_id_);
248
249  // Handles only predecessor or only successor case.
250  if (!prev || !next) {
251    syncer::StringOrdinal page = prev ? prev->GetPageOrdinal() :
252                                        next->GetPageOrdinal();
253    GetExtensionSorting(profile_)->SetPageOrdinal(extension_id_, page);
254    service->OnExtensionMoved(extension_id_,
255                              prev ? prev->extension_id() : std::string(),
256                              next ? next->extension_id() : std::string());
257    return;
258  }
259
260  // Handles both predecessor and successor are on the same page.
261  syncer::StringOrdinal prev_page = prev->GetPageOrdinal();
262  syncer::StringOrdinal next_page = next->GetPageOrdinal();
263  if (prev_page.Equals(next_page)) {
264    GetExtensionSorting(profile_)->SetPageOrdinal(extension_id_, prev_page);
265    service->OnExtensionMoved(extension_id_,
266                              prev->extension_id(),
267                              next->extension_id());
268    return;
269  }
270
271  // Otherwise, go with |next|. This is okay because app list does not split
272  // page based ntp page ordinal.
273  // TODO(xiyuan): Revisit this when implementing paging support.
274  GetExtensionSorting(profile_)->SetPageOrdinal(extension_id_, prev_page);
275  service->OnExtensionMoved(extension_id_,
276                            prev->extension_id(),
277                            std::string());
278}
279
280void ExtensionAppItem::UpdateIcon() {
281  if (!GetExtension()) {
282    gfx::ImageSkia icon = installing_icon_;
283    if (HasOverlay()) {
284      // The tab overlay requires icons of a certain size.
285      gfx::Size small_size(extension_misc::EXTENSION_ICON_SMALL,
286                           extension_misc::EXTENSION_ICON_SMALL);
287      icon = gfx::ImageSkiaOperations::CreateResizedImage(
288          icon, skia::ImageOperations::RESIZE_GOOD, small_size);
289
290      gfx::Size size(extension_misc::EXTENSION_ICON_MEDIUM,
291                     extension_misc::EXTENSION_ICON_MEDIUM);
292      icon = gfx::ImageSkia(new TabOverlayImageSource(icon, size), size);
293    }
294    SetIcon(icon, !HasOverlay());
295    return;
296  }
297  gfx::ImageSkia icon = icon_->image_skia();
298
299  const ExtensionService* service =
300      extensions::ExtensionSystem::Get(profile_)->extension_service();
301  const bool enabled = service->IsExtensionEnabledForLauncher(extension_id_);
302  if (!enabled) {
303    const color_utils::HSL shift = {-1, 0, 0.6};
304    icon = gfx::ImageSkiaOperations::CreateHSLShiftedImage(icon, shift);
305  }
306
307  if (HasOverlay()) {
308    const gfx::Size size(extension_misc::EXTENSION_ICON_MEDIUM,
309                         extension_misc::EXTENSION_ICON_MEDIUM);
310    icon = gfx::ImageSkia(new TabOverlayImageSource(icon, size), size);
311  }
312
313  SetIcon(icon, !HasOverlay());
314}
315
316const Extension* ExtensionAppItem::GetExtension() const {
317  const ExtensionService* service =
318      extensions::ExtensionSystem::Get(profile_)->extension_service();
319  const Extension* extension = service->GetInstalledExtension(extension_id_);
320  return extension;
321}
322
323void ExtensionAppItem::LoadImage(const Extension* extension) {
324  int icon_size = extension_misc::EXTENSION_ICON_MEDIUM;
325  if (HasOverlay())
326    icon_size = extension_misc::EXTENSION_ICON_SMALL;
327
328  icon_.reset(new extensions::IconImage(
329      profile_,
330      extension,
331      extensions::IconsInfo::GetIcons(extension),
332      icon_size,
333      extensions::IconsInfo::GetDefaultAppIcon(),
334      this));
335  UpdateIcon();
336}
337
338void ExtensionAppItem::ShowExtensionOptions() {
339  const Extension* extension = GetExtension();
340  if (!extension)
341    return;
342
343  chrome::NavigateParams params(
344      profile_,
345      extensions::ManifestURL::GetOptionsPage(extension),
346      content::PAGE_TRANSITION_LINK);
347  chrome::Navigate(&params);
348}
349
350void ExtensionAppItem::ShowExtensionDetails() {
351  const Extension* extension = GetExtension();
352  if (!extension)
353    return;
354
355  chrome::NavigateParams params(
356      profile_,
357      extensions::ManifestURL::GetDetailsURL(extension),
358      content::PAGE_TRANSITION_LINK);
359  chrome::Navigate(&params);
360}
361
362void ExtensionAppItem::StartExtensionUninstall() {
363  // ExtensionUninstall deletes itself when done or aborted.
364  ExtensionUninstaller* uninstaller = new ExtensionUninstaller(profile_,
365                                                               extension_id_,
366                                                               controller_);
367  uninstaller->Run();
368}
369
370bool ExtensionAppItem::RunExtensionEnableFlow() {
371  const ExtensionService* service =
372      extensions::ExtensionSystem::Get(profile_)->extension_service();
373  if (service->IsExtensionEnabledForLauncher(extension_id_))
374    return false;
375
376  if (!extension_enable_flow_) {
377    controller_->OnShowExtensionPrompt();
378
379    extension_enable_flow_.reset(new ExtensionEnableFlow(
380        profile_, extension_id_, this));
381    extension_enable_flow_->StartForNativeWindow(
382        controller_->GetAppListWindow());
383  }
384  return true;
385}
386
387void ExtensionAppItem::Launch(int event_flags) {
388  // |extension| could be NULL when it is being unloaded for updating.
389  const Extension* extension = GetExtension();
390  if (!extension)
391    return;
392
393  if (RunExtensionEnableFlow())
394    return;
395
396  controller_->LaunchApp(profile_, extension, event_flags);
397}
398
399void ExtensionAppItem::OnExtensionIconImageChanged(
400    extensions::IconImage* image) {
401  DCHECK(icon_.get() == image);
402  UpdateIcon();
403}
404
405void ExtensionAppItem::ExtensionEnableFlowFinished() {
406  extension_enable_flow_.reset();
407  controller_->OnCloseExtensionPrompt();
408
409  // Automatically launch app after enabling.
410  Launch(ui::EF_NONE);
411}
412
413void ExtensionAppItem::ExtensionEnableFlowAborted(bool user_initiated) {
414  extension_enable_flow_.reset();
415  controller_->OnCloseExtensionPrompt();
416}
417
418bool ExtensionAppItem::IsItemForCommandIdDynamic(int command_id) const {
419  return command_id == TOGGLE_PIN || command_id == LAUNCH_NEW;
420}
421
422string16 ExtensionAppItem::GetLabelForCommandId(int command_id) const {
423  if (command_id == TOGGLE_PIN) {
424    return controller_->IsAppPinned(extension_id_) ?
425        l10n_util::GetStringUTF16(IDS_APP_LIST_CONTEXT_MENU_UNPIN) :
426        l10n_util::GetStringUTF16(IDS_APP_LIST_CONTEXT_MENU_PIN);
427  } else if (command_id == LAUNCH_NEW) {
428    if (IsCommandIdChecked(LAUNCH_TYPE_PINNED_TAB) ||
429        IsCommandIdChecked(LAUNCH_TYPE_REGULAR_TAB)) {
430      return l10n_util::GetStringUTF16(IDS_APP_LIST_CONTEXT_MENU_NEW_TAB);
431    } else {
432      return l10n_util::GetStringUTF16(IDS_APP_LIST_CONTEXT_MENU_NEW_WINDOW);
433    }
434  } else {
435    NOTREACHED();
436    return string16();
437  }
438}
439
440bool ExtensionAppItem::IsCommandIdChecked(int command_id) const {
441  if (command_id >= LAUNCH_TYPE_START && command_id < LAUNCH_TYPE_LAST) {
442    return static_cast<int>(GetExtensionLaunchType(profile_, GetExtension())) +
443        LAUNCH_TYPE_START == command_id;
444  } else if (command_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST &&
445             command_id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) {
446    return extension_menu_items_->IsCommandIdChecked(command_id);
447  }
448  return false;
449}
450
451bool ExtensionAppItem::IsCommandIdEnabled(int command_id) const {
452  if (command_id == TOGGLE_PIN) {
453    return controller_->CanPin();
454  } else if (command_id == OPTIONS) {
455    const ExtensionService* service =
456        extensions::ExtensionSystem::Get(profile_)->extension_service();
457    const Extension* extension = GetExtension();
458    return service->IsExtensionEnabledForLauncher(extension_id_) &&
459           extension &&
460           !extensions::ManifestURL::GetOptionsPage(extension).is_empty();
461  } else if (command_id == UNINSTALL) {
462    const Extension* extension = GetExtension();
463    const extensions::ManagementPolicy* policy =
464        extensions::ExtensionSystem::Get(profile_)->management_policy();
465    return extension &&
466           policy->UserMayModifySettings(extension, NULL);
467  } else if (command_id == DETAILS) {
468    const Extension* extension = GetExtension();
469    return extension && extension->from_webstore();
470  } else if (command_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST &&
471             command_id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) {
472    return extension_menu_items_->IsCommandIdEnabled(command_id);
473  } else if (command_id == MENU_NEW_WINDOW) {
474    // "Normal" windows are not allowed when incognito is enforced.
475    return IncognitoModePrefs::GetAvailability(profile_->GetPrefs()) !=
476        IncognitoModePrefs::FORCED;
477  } else if (command_id == MENU_NEW_INCOGNITO_WINDOW) {
478    // Incognito windows are not allowed when incognito is disabled.
479    return IncognitoModePrefs::GetAvailability(profile_->GetPrefs()) !=
480        IncognitoModePrefs::DISABLED;
481  }
482  return true;
483}
484
485bool ExtensionAppItem::GetAcceleratorForCommandId(
486    int command_id,
487    ui::Accelerator* acclelrator) {
488  return false;
489}
490
491void ExtensionAppItem::ExecuteCommand(int command_id, int event_flags) {
492  if (command_id == LAUNCH_NEW) {
493    Launch(ui::EF_NONE);
494  } else if (command_id == TOGGLE_PIN && controller_->CanPin()) {
495    if (controller_->IsAppPinned(extension_id_))
496      controller_->UnpinApp(extension_id_);
497    else
498      controller_->PinApp(extension_id_);
499  } else if (command_id == CREATE_SHORTCUTS) {
500    controller_->ShowCreateShortcutsDialog(profile_, extension_id_);
501  } else if (command_id >= LAUNCH_TYPE_START &&
502             command_id < LAUNCH_TYPE_LAST) {
503    SetExtensionLaunchType(profile_,
504                           extension_id_,
505                           static_cast<extensions::ExtensionPrefs::LaunchType>(
506                               command_id - LAUNCH_TYPE_START));
507  } else if (command_id == OPTIONS) {
508    ShowExtensionOptions();
509  } else if (command_id == UNINSTALL) {
510    StartExtensionUninstall();
511  } else if (command_id == DETAILS) {
512    ShowExtensionDetails();
513  } else if (command_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST &&
514             command_id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) {
515    extension_menu_items_->ExecuteCommand(command_id, NULL,
516                                          content::ContextMenuParams());
517  } else if (command_id == MENU_NEW_WINDOW) {
518    controller_->CreateNewWindow(profile_, false);
519  } else if (command_id == MENU_NEW_INCOGNITO_WINDOW) {
520    controller_->CreateNewWindow(profile_, true);
521  }
522}
523
524void ExtensionAppItem::Activate(int event_flags) {
525  // |extension| could be NULL when it is being unloaded for updating.
526  const Extension* extension = GetExtension();
527  if (!extension)
528    return;
529
530  if (RunExtensionEnableFlow())
531    return;
532
533  AppLauncherHandler::RecordAppLaunchType(
534      extension_misc::APP_LAUNCH_APP_LIST_MAIN,
535      extension->GetType());
536  controller_->ActivateApp(profile_, extension, event_flags);
537}
538
539ui::MenuModel* ExtensionAppItem::GetContextMenuModel() {
540  const Extension* extension = GetExtension();
541  if (!extension)
542    return NULL;
543
544  if (context_menu_model_.get())
545    return context_menu_model_.get();
546
547  context_menu_model_.reset(new ui::SimpleMenuModel(this));
548
549  if (extension_id_ == extension_misc::kChromeAppId) {
550    context_menu_model_->AddItemWithStringId(
551        MENU_NEW_WINDOW,
552        IDS_APP_LIST_NEW_WINDOW);
553    if (!profile_->IsOffTheRecord()) {
554      context_menu_model_->AddItemWithStringId(
555          MENU_NEW_INCOGNITO_WINDOW,
556          IDS_APP_LIST_NEW_INCOGNITO_WINDOW);
557    }
558  } else {
559    extension_menu_items_.reset(new extensions::ContextMenuMatcher(
560        profile_, this, context_menu_model_.get(),
561        base::Bind(MenuItemHasLauncherContext)));
562
563    if (!is_platform_app_)
564      context_menu_model_->AddItem(LAUNCH_NEW, string16());
565
566    int index = 0;
567    extension_menu_items_->AppendExtensionItems(extension_id_, string16(),
568                                                &index);
569
570    if (controller_->CanPin()) {
571      context_menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
572      context_menu_model_->AddItemWithStringId(
573          TOGGLE_PIN,
574          controller_->IsAppPinned(extension_id_) ?
575              IDS_APP_LIST_CONTEXT_MENU_UNPIN :
576              IDS_APP_LIST_CONTEXT_MENU_PIN);
577    }
578
579    if (controller_->CanShowCreateShortcutsDialog()) {
580      context_menu_model_->AddItemWithStringId(CREATE_SHORTCUTS,
581                                               IDS_NEW_TAB_APP_CREATE_SHORTCUT);
582    }
583
584    if (!is_platform_app_) {
585      context_menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
586      context_menu_model_->AddCheckItemWithStringId(
587          LAUNCH_TYPE_REGULAR_TAB,
588          IDS_APP_CONTEXT_MENU_OPEN_REGULAR);
589      context_menu_model_->AddCheckItemWithStringId(
590          LAUNCH_TYPE_PINNED_TAB,
591          IDS_APP_CONTEXT_MENU_OPEN_PINNED);
592      context_menu_model_->AddCheckItemWithStringId(
593          LAUNCH_TYPE_WINDOW,
594          IDS_APP_CONTEXT_MENU_OPEN_WINDOW);
595      // Even though the launch type is Full Screen it is more accurately
596      // described as Maximized in Ash.
597      context_menu_model_->AddCheckItemWithStringId(
598          LAUNCH_TYPE_FULLSCREEN,
599          IDS_APP_CONTEXT_MENU_OPEN_MAXIMIZED);
600
601      context_menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
602      context_menu_model_->AddItemWithStringId(OPTIONS,
603                                               IDS_NEW_TAB_APP_OPTIONS);
604    }
605
606    context_menu_model_->AddItemWithStringId(DETAILS,
607                                             IDS_NEW_TAB_APP_DETAILS);
608    context_menu_model_->AddItemWithStringId(UNINSTALL,
609                                             is_platform_app_ ?
610                                                 IDS_APP_LIST_UNINSTALL_ITEM :
611                                                 IDS_EXTENSIONS_UNINSTALL);
612  }
613
614  return context_menu_model_.get();
615}
616