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(¶ms); 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(¶ms); 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