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