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