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 "ui/base/l10n/l10n_util.h" 23 24 #if defined(USE_ASH) 25 #include "ash/shell.h" 26 #endif 27 28 using extensions::Extension; 29 30 namespace app_list { 31 32 namespace { 33 34 enum CommandId { 35 LAUNCH_NEW = 100, 36 TOGGLE_PIN, 37 CREATE_SHORTCUTS, 38 OPTIONS, 39 UNINSTALL, 40 DETAILS, 41 MENU_NEW_WINDOW, 42 MENU_NEW_INCOGNITO_WINDOW, 43 // Order matters in LAUNCHER_TYPE_xxxx and must match LaunchType. 44 LAUNCH_TYPE_START = 200, 45 LAUNCH_TYPE_PINNED_TAB = LAUNCH_TYPE_START, 46 LAUNCH_TYPE_REGULAR_TAB, 47 LAUNCH_TYPE_FULLSCREEN, 48 LAUNCH_TYPE_WINDOW, 49 LAUNCH_TYPE_LAST, 50 }; 51 52 // ExtensionUninstaller runs the extension uninstall flow. It shows the 53 // extension uninstall dialog and wait for user to confirm or cancel the 54 // uninstall. 55 class ExtensionUninstaller : public ExtensionUninstallDialog::Delegate { 56 public: 57 ExtensionUninstaller(Profile* profile, 58 const std::string& extension_id, 59 AppListControllerDelegate* controller) 60 : profile_(profile), 61 app_id_(extension_id), 62 controller_(controller) { 63 } 64 65 void Run() { 66 const Extension* extension = 67 extensions::ExtensionSystem::Get(profile_)->extension_service()-> 68 GetExtensionById(app_id_, true); 69 if (!extension) { 70 CleanUp(); 71 return; 72 } 73 controller_->OnShowExtensionPrompt(); 74 dialog_.reset(ExtensionUninstallDialog::Create(profile_, NULL, this)); 75 dialog_->ConfirmUninstall(extension); 76 } 77 78 private: 79 // Overridden from ExtensionUninstallDialog::Delegate: 80 virtual void ExtensionUninstallAccepted() OVERRIDE { 81 ExtensionService* service = 82 extensions::ExtensionSystem::Get(profile_)->extension_service(); 83 const Extension* extension = service->GetInstalledExtension(app_id_); 84 if (extension) { 85 service->UninstallExtension(app_id_, 86 false, /* external_uninstall*/ 87 NULL); 88 } 89 controller_->OnCloseExtensionPrompt(); 90 CleanUp(); 91 } 92 93 virtual void ExtensionUninstallCanceled() OVERRIDE { 94 controller_->OnCloseExtensionPrompt(); 95 CleanUp(); 96 } 97 98 void CleanUp() { 99 delete this; 100 } 101 102 Profile* profile_; 103 std::string app_id_; 104 AppListControllerDelegate* controller_; 105 scoped_ptr<ExtensionUninstallDialog> dialog_; 106 107 DISALLOW_COPY_AND_ASSIGN(ExtensionUninstaller); 108 }; 109 110 extensions::ExtensionPrefs::LaunchType GetExtensionLaunchType( 111 Profile* profile, 112 const Extension* extension) { 113 ExtensionService* service = 114 extensions::ExtensionSystem::Get(profile)->extension_service(); 115 return service->extension_prefs()-> 116 GetLaunchType(extension, extensions::ExtensionPrefs::LAUNCH_DEFAULT); 117 } 118 119 void SetExtensionLaunchType( 120 Profile* profile, 121 const std::string& extension_id, 122 extensions::ExtensionPrefs::LaunchType launch_type) { 123 ExtensionService* service = 124 extensions::ExtensionSystem::Get(profile)->extension_service(); 125 service->extension_prefs()->SetLaunchType(extension_id, launch_type); 126 } 127 128 bool MenuItemHasLauncherContext(const extensions::MenuItem* item) { 129 return item->contexts().Contains(extensions::MenuItem::LAUNCHER); 130 } 131 132 } // namespace 133 134 AppContextMenu::AppContextMenu(AppContextMenuDelegate* delegate, 135 Profile* profile, 136 const std::string& app_id, 137 AppListControllerDelegate* controller, 138 bool is_platform_app) 139 : delegate_(delegate), 140 profile_(profile), 141 app_id_(app_id), 142 controller_(controller), 143 is_platform_app_(is_platform_app) { 144 } 145 146 AppContextMenu::~AppContextMenu() {} 147 148 ui::MenuModel* AppContextMenu::GetMenuModel() { 149 const Extension* extension = GetExtension(); 150 if (!extension) 151 return NULL; 152 153 if (menu_model_.get()) 154 return menu_model_.get(); 155 156 menu_model_.reset(new ui::SimpleMenuModel(this)); 157 158 if (app_id_ == extension_misc::kChromeAppId) { 159 menu_model_->AddItemWithStringId( 160 MENU_NEW_WINDOW, 161 IDS_APP_LIST_NEW_WINDOW); 162 if (!profile_->IsOffTheRecord()) { 163 menu_model_->AddItemWithStringId( 164 MENU_NEW_INCOGNITO_WINDOW, 165 IDS_APP_LIST_NEW_INCOGNITO_WINDOW); 166 } 167 } else { 168 extension_menu_items_.reset(new extensions::ContextMenuMatcher( 169 profile_, this, menu_model_.get(), 170 base::Bind(MenuItemHasLauncherContext))); 171 172 if (!is_platform_app_) 173 menu_model_->AddItem(LAUNCH_NEW, string16()); 174 175 int index = 0; 176 extension_menu_items_->AppendExtensionItems(app_id_, string16(), 177 &index); 178 179 if (controller_->CanPin()) { 180 menu_model_->AddSeparator(ui::NORMAL_SEPARATOR); 181 menu_model_->AddItemWithStringId( 182 TOGGLE_PIN, 183 controller_->IsAppPinned(app_id_) ? 184 IDS_APP_LIST_CONTEXT_MENU_UNPIN : 185 IDS_APP_LIST_CONTEXT_MENU_PIN); 186 } 187 188 if (controller_->CanDoCreateShortcutsFlow(is_platform_app_)) { 189 menu_model_->AddItemWithStringId(CREATE_SHORTCUTS, 190 IDS_NEW_TAB_APP_CREATE_SHORTCUT); 191 } 192 193 if (!is_platform_app_) { 194 menu_model_->AddSeparator(ui::NORMAL_SEPARATOR); 195 menu_model_->AddCheckItemWithStringId( 196 LAUNCH_TYPE_REGULAR_TAB, 197 IDS_APP_CONTEXT_MENU_OPEN_REGULAR); 198 menu_model_->AddCheckItemWithStringId( 199 LAUNCH_TYPE_PINNED_TAB, 200 IDS_APP_CONTEXT_MENU_OPEN_PINNED); 201 #if defined(USE_ASH) 202 if (!ash::Shell::IsForcedMaximizeMode()) 203 #endif 204 { 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 } 221 menu_model_->AddSeparator(ui::NORMAL_SEPARATOR); 222 menu_model_->AddItemWithStringId(OPTIONS, IDS_NEW_TAB_APP_OPTIONS); 223 } 224 225 menu_model_->AddItemWithStringId(DETAILS, IDS_NEW_TAB_APP_DETAILS); 226 menu_model_->AddItemWithStringId( 227 UNINSTALL, 228 is_platform_app_ ? IDS_APP_LIST_UNINSTALL_ITEM 229 : IDS_EXTENSIONS_UNINSTALL); 230 } 231 232 return menu_model_.get(); 233 } 234 235 const Extension* AppContextMenu::GetExtension() const { 236 const ExtensionService* service = 237 extensions::ExtensionSystem::Get(profile_)->extension_service(); 238 const Extension* extension = service->GetInstalledExtension(app_id_); 239 return extension; 240 } 241 242 void AppContextMenu::ShowExtensionOptions() { 243 const Extension* extension = GetExtension(); 244 if (!extension) 245 return; 246 247 chrome::NavigateParams params( 248 profile_, 249 extensions::ManifestURL::GetOptionsPage(extension), 250 content::PAGE_TRANSITION_LINK); 251 chrome::Navigate(¶ms); 252 } 253 254 void AppContextMenu::ShowExtensionDetails() { 255 const Extension* extension = GetExtension(); 256 if (!extension) 257 return; 258 259 chrome::NavigateParams params( 260 profile_, 261 extensions::ManifestURL::GetDetailsURL(extension), 262 content::PAGE_TRANSITION_LINK); 263 chrome::Navigate(¶ms); 264 } 265 266 void AppContextMenu::StartExtensionUninstall() { 267 // ExtensionUninstall deletes itself when done or aborted. 268 ExtensionUninstaller* uninstaller = new ExtensionUninstaller(profile_, 269 app_id_, 270 controller_); 271 uninstaller->Run(); 272 } 273 274 bool AppContextMenu::IsItemForCommandIdDynamic(int command_id) const { 275 return command_id == TOGGLE_PIN || command_id == LAUNCH_NEW; 276 } 277 278 string16 AppContextMenu::GetLabelForCommandId(int command_id) const { 279 if (command_id == TOGGLE_PIN) { 280 return controller_->IsAppPinned(app_id_) ? 281 l10n_util::GetStringUTF16(IDS_APP_LIST_CONTEXT_MENU_UNPIN) : 282 l10n_util::GetStringUTF16(IDS_APP_LIST_CONTEXT_MENU_PIN); 283 } else if (command_id == LAUNCH_NEW) { 284 #if defined(OS_MACOSX) 285 // Even fullscreen windows launch in a browser tab on Mac. 286 const bool launches_in_tab = true; 287 #else 288 const bool launches_in_tab = IsCommandIdChecked(LAUNCH_TYPE_PINNED_TAB) || 289 IsCommandIdChecked(LAUNCH_TYPE_REGULAR_TAB); 290 #endif 291 return launches_in_tab ? 292 l10n_util::GetStringUTF16(IDS_APP_LIST_CONTEXT_MENU_NEW_TAB) : 293 l10n_util::GetStringUTF16(IDS_APP_LIST_CONTEXT_MENU_NEW_WINDOW); 294 } else { 295 NOTREACHED(); 296 return string16(); 297 } 298 } 299 300 bool AppContextMenu::IsCommandIdChecked(int command_id) const { 301 if (command_id >= LAUNCH_TYPE_START && command_id < LAUNCH_TYPE_LAST) { 302 return static_cast<int>(GetExtensionLaunchType(profile_, GetExtension())) + 303 LAUNCH_TYPE_START == command_id; 304 } else if (command_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST && 305 command_id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) { 306 return extension_menu_items_->IsCommandIdChecked(command_id); 307 } 308 return false; 309 } 310 311 bool AppContextMenu::IsCommandIdEnabled(int command_id) const { 312 if (command_id == TOGGLE_PIN) { 313 return controller_->CanPin(); 314 } else if (command_id == OPTIONS) { 315 const ExtensionService* service = 316 extensions::ExtensionSystem::Get(profile_)->extension_service(); 317 const Extension* extension = GetExtension(); 318 return service->IsExtensionEnabledForLauncher(app_id_) && 319 extension && 320 !extensions::ManifestURL::GetOptionsPage(extension).is_empty(); 321 } else if (command_id == UNINSTALL) { 322 const Extension* extension = GetExtension(); 323 const extensions::ManagementPolicy* policy = 324 extensions::ExtensionSystem::Get(profile_)->management_policy(); 325 return extension && 326 policy->UserMayModifySettings(extension, NULL); 327 } else if (command_id == DETAILS) { 328 const Extension* extension = GetExtension(); 329 return extension && extension->from_webstore(); 330 } else if (command_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST && 331 command_id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) { 332 return extension_menu_items_->IsCommandIdEnabled(command_id); 333 } else if (command_id == MENU_NEW_WINDOW) { 334 // "Normal" windows are not allowed when incognito is enforced. 335 return IncognitoModePrefs::GetAvailability(profile_->GetPrefs()) != 336 IncognitoModePrefs::FORCED; 337 } else if (command_id == MENU_NEW_INCOGNITO_WINDOW) { 338 // Incognito windows are not allowed when incognito is disabled. 339 return IncognitoModePrefs::GetAvailability(profile_->GetPrefs()) != 340 IncognitoModePrefs::DISABLED; 341 } 342 return true; 343 } 344 345 bool AppContextMenu::GetAcceleratorForCommandId( 346 int command_id, 347 ui::Accelerator* acclelrator) { 348 return false; 349 } 350 351 void AppContextMenu::ExecuteCommand(int command_id, int event_flags) { 352 if (command_id == LAUNCH_NEW) { 353 delegate_->ExecuteLaunchCommand(event_flags); 354 } else if (command_id == TOGGLE_PIN && controller_->CanPin()) { 355 if (controller_->IsAppPinned(app_id_)) 356 controller_->UnpinApp(app_id_); 357 else 358 controller_->PinApp(app_id_); 359 } else if (command_id == CREATE_SHORTCUTS) { 360 controller_->DoCreateShortcutsFlow(profile_, app_id_); 361 } else if (command_id >= LAUNCH_TYPE_START && 362 command_id < LAUNCH_TYPE_LAST) { 363 SetExtensionLaunchType(profile_, 364 app_id_, 365 static_cast<extensions::ExtensionPrefs::LaunchType>( 366 command_id - LAUNCH_TYPE_START)); 367 } else if (command_id == OPTIONS) { 368 ShowExtensionOptions(); 369 } else if (command_id == UNINSTALL) { 370 StartExtensionUninstall(); 371 } else if (command_id == DETAILS) { 372 ShowExtensionDetails(); 373 } else if (command_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST && 374 command_id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) { 375 extension_menu_items_->ExecuteCommand(command_id, NULL, 376 content::ContextMenuParams()); 377 } else if (command_id == MENU_NEW_WINDOW) { 378 controller_->CreateNewWindow(profile_, false); 379 } else if (command_id == MENU_NEW_INCOGNITO_WINDOW) { 380 controller_->CreateNewWindow(profile_, true); 381 } 382 } 383 384 } // namespace app_list 385