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 "base/bind.h" 8 #include "base/command_line.h" 9 #include "chrome/app/chrome_command_ids.h" 10 #include "chrome/browser/chromeos/genius_app/app_id.h" 11 #include "chrome/browser/extensions/context_menu_matcher.h" 12 #include "chrome/browser/extensions/menu_manager.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/common/chrome_switches.h" 18 #include "chrome/common/extensions/extension_constants.h" 19 #include "chrome/common/extensions/manifest_url_handler.h" 20 #include "content/public/common/context_menu_params.h" 21 #include "grit/chromium_strings.h" 22 #include "grit/generated_resources.h" 23 #include "ui/base/l10n/l10n_util.h" 24 25 #if defined(USE_ASH) 26 #include "ash/shell.h" 27 #endif 28 29 namespace app_list { 30 31 namespace { 32 33 enum CommandId { 34 LAUNCH_NEW = 100, 35 TOGGLE_PIN, 36 CREATE_SHORTCUTS, 37 SHOW_APP_INFO, 38 OPTIONS, 39 UNINSTALL, 40 REMOVE_FROM_FOLDER, 41 DETAILS, 42 MENU_NEW_WINDOW, 43 MENU_NEW_INCOGNITO_WINDOW, 44 // Order matters in USE_LAUNCH_TYPE_* and must match the LaunchType enum. 45 USE_LAUNCH_TYPE_COMMAND_START = 200, 46 USE_LAUNCH_TYPE_PINNED = USE_LAUNCH_TYPE_COMMAND_START, 47 USE_LAUNCH_TYPE_REGULAR, 48 USE_LAUNCH_TYPE_FULLSCREEN, 49 USE_LAUNCH_TYPE_WINDOW, 50 USE_LAUNCH_TYPE_COMMAND_END, 51 }; 52 53 bool MenuItemHasLauncherContext(const extensions::MenuItem* item) { 54 return item->contexts().Contains(extensions::MenuItem::LAUNCHER); 55 } 56 57 } // namespace 58 59 AppContextMenu::AppContextMenu(AppContextMenuDelegate* delegate, 60 Profile* profile, 61 const std::string& app_id, 62 AppListControllerDelegate* controller) 63 : delegate_(delegate), 64 profile_(profile), 65 app_id_(app_id), 66 controller_(controller), 67 is_platform_app_(false), 68 is_search_result_(false), 69 is_in_folder_(false) { 70 } 71 72 AppContextMenu::~AppContextMenu() { 73 } 74 75 ui::MenuModel* AppContextMenu::GetMenuModel() { 76 if (!controller_->IsExtensionInstalled(profile_, app_id_)) 77 return NULL; 78 79 if (menu_model_.get()) 80 return menu_model_.get(); 81 82 menu_model_.reset(new ui::SimpleMenuModel(this)); 83 84 if (app_id_ == extension_misc::kChromeAppId) { 85 menu_model_->AddItemWithStringId( 86 MENU_NEW_WINDOW, 87 IDS_APP_LIST_NEW_WINDOW); 88 if (!profile_->IsOffTheRecord()) { 89 menu_model_->AddItemWithStringId( 90 MENU_NEW_INCOGNITO_WINDOW, 91 IDS_APP_LIST_NEW_INCOGNITO_WINDOW); 92 } 93 } else { 94 extension_menu_items_.reset(new extensions::ContextMenuMatcher( 95 profile_, this, menu_model_.get(), 96 base::Bind(MenuItemHasLauncherContext))); 97 98 if (!is_platform_app_) 99 menu_model_->AddItem(LAUNCH_NEW, base::string16()); 100 101 int index = 0; 102 extension_menu_items_->AppendExtensionItems( 103 extensions::MenuItem::ExtensionKey(app_id_), base::string16(), &index); 104 105 // Show Pin/Unpin option if shelf is available. 106 if (controller_->GetPinnable() != AppListControllerDelegate::NO_PIN) { 107 menu_model_->AddSeparator(ui::NORMAL_SEPARATOR); 108 menu_model_->AddItemWithStringId( 109 TOGGLE_PIN, 110 controller_->IsAppPinned(app_id_) ? 111 IDS_APP_LIST_CONTEXT_MENU_UNPIN : 112 IDS_APP_LIST_CONTEXT_MENU_PIN); 113 } 114 115 if (controller_->CanDoCreateShortcutsFlow()) { 116 menu_model_->AddItemWithStringId(CREATE_SHORTCUTS, 117 IDS_NEW_TAB_APP_CREATE_SHORTCUT); 118 } 119 120 // Don't display the app info dialog for the Store app or the Genius app. 121 // TODO(sashab): Update the metadata for these apps so their dialogs can be 122 // re-enabled (see crbug.com/383713). 123 if (controller_->CanDoShowAppInfoFlow() && 124 app_id_ != extension_misc::kWebStoreAppId && 125 app_id_ != genius_app::kGeniusAppId) { 126 menu_model_->AddItemWithStringId(SHOW_APP_INFO, 127 IDS_APP_CONTEXT_MENU_SHOW_INFO); 128 } 129 130 if (!is_platform_app_) { 131 menu_model_->AddSeparator(ui::NORMAL_SEPARATOR); 132 // Streamlined hosted apps can only toggle between USE_LAUNCH_TYPE_WINDOW 133 // and USE_LAUNCH_TYPE_REGULAR. 134 if (CommandLine::ForCurrentProcess()->HasSwitch( 135 switches::kEnableStreamlinedHostedApps)) { 136 menu_model_->AddCheckItemWithStringId( 137 USE_LAUNCH_TYPE_REGULAR, 138 IDS_APP_CONTEXT_MENU_OPEN_TAB); 139 } else { 140 menu_model_->AddCheckItemWithStringId( 141 USE_LAUNCH_TYPE_REGULAR, 142 IDS_APP_CONTEXT_MENU_OPEN_REGULAR); 143 menu_model_->AddCheckItemWithStringId( 144 USE_LAUNCH_TYPE_PINNED, 145 IDS_APP_CONTEXT_MENU_OPEN_PINNED); 146 #if defined(OS_MACOSX) 147 // Mac does not support standalone web app browser windows or maximize. 148 menu_model_->AddCheckItemWithStringId( 149 USE_LAUNCH_TYPE_FULLSCREEN, 150 IDS_APP_CONTEXT_MENU_OPEN_FULLSCREEN); 151 #else 152 menu_model_->AddCheckItemWithStringId( 153 USE_LAUNCH_TYPE_WINDOW, 154 IDS_APP_CONTEXT_MENU_OPEN_WINDOW); 155 // Even though the launch type is Full Screen it is more accurately 156 // described as Maximized in Ash. 157 menu_model_->AddCheckItemWithStringId( 158 USE_LAUNCH_TYPE_FULLSCREEN, 159 IDS_APP_CONTEXT_MENU_OPEN_MAXIMIZED); 160 #endif 161 } 162 menu_model_->AddSeparator(ui::NORMAL_SEPARATOR); 163 menu_model_->AddItemWithStringId(OPTIONS, IDS_NEW_TAB_APP_OPTIONS); 164 } 165 166 menu_model_->AddItemWithStringId(DETAILS, IDS_NEW_TAB_APP_DETAILS); 167 menu_model_->AddItemWithStringId( 168 UNINSTALL, 169 is_platform_app_ ? IDS_APP_LIST_UNINSTALL_ITEM 170 : IDS_EXTENSIONS_UNINSTALL); 171 } 172 173 return menu_model_.get(); 174 } 175 176 bool AppContextMenu::IsItemForCommandIdDynamic(int command_id) const { 177 return command_id == TOGGLE_PIN || command_id == LAUNCH_NEW; 178 } 179 180 base::string16 AppContextMenu::GetLabelForCommandId(int command_id) const { 181 if (command_id == TOGGLE_PIN) { 182 return controller_->IsAppPinned(app_id_) ? 183 l10n_util::GetStringUTF16(IDS_APP_LIST_CONTEXT_MENU_UNPIN) : 184 l10n_util::GetStringUTF16(IDS_APP_LIST_CONTEXT_MENU_PIN); 185 } else if (command_id == LAUNCH_NEW) { 186 #if defined(OS_MACOSX) 187 // Even fullscreen windows launch in a browser tab on Mac. 188 const bool launches_in_tab = true; 189 #else 190 const bool launches_in_tab = IsCommandIdChecked(USE_LAUNCH_TYPE_PINNED) || 191 IsCommandIdChecked(USE_LAUNCH_TYPE_REGULAR); 192 #endif 193 return launches_in_tab ? 194 l10n_util::GetStringUTF16(IDS_APP_LIST_CONTEXT_MENU_NEW_TAB) : 195 l10n_util::GetStringUTF16(IDS_APP_LIST_CONTEXT_MENU_NEW_WINDOW); 196 } else { 197 NOTREACHED(); 198 return base::string16(); 199 } 200 } 201 202 bool AppContextMenu::IsCommandIdChecked(int command_id) const { 203 if (command_id >= USE_LAUNCH_TYPE_COMMAND_START && 204 command_id < USE_LAUNCH_TYPE_COMMAND_END) { 205 return static_cast<int>(controller_->GetExtensionLaunchType( 206 profile_, app_id_)) + USE_LAUNCH_TYPE_COMMAND_START == command_id; 207 } else if (command_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST && 208 command_id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) { 209 return extension_menu_items_->IsCommandIdChecked(command_id); 210 } 211 return false; 212 } 213 214 bool AppContextMenu::IsCommandIdEnabled(int command_id) const { 215 if (command_id == TOGGLE_PIN) { 216 return controller_->GetPinnable() == 217 AppListControllerDelegate::PIN_EDITABLE; 218 } else if (command_id == OPTIONS) { 219 return controller_->HasOptionsPage(profile_, app_id_); 220 } else if (command_id == UNINSTALL) { 221 return controller_->UserMayModifySettings(profile_, app_id_); 222 } else if (command_id == DETAILS) { 223 return controller_->IsAppFromWebStore(profile_, app_id_); 224 } else if (command_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST && 225 command_id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) { 226 return extension_menu_items_->IsCommandIdEnabled(command_id); 227 } else if (command_id == MENU_NEW_WINDOW) { 228 // "Normal" windows are not allowed when incognito is enforced. 229 return IncognitoModePrefs::GetAvailability(profile_->GetPrefs()) != 230 IncognitoModePrefs::FORCED; 231 } else if (command_id == MENU_NEW_INCOGNITO_WINDOW) { 232 // Incognito windows are not allowed when incognito is disabled. 233 return IncognitoModePrefs::GetAvailability(profile_->GetPrefs()) != 234 IncognitoModePrefs::DISABLED; 235 } 236 return true; 237 } 238 239 bool AppContextMenu::GetAcceleratorForCommandId( 240 int command_id, 241 ui::Accelerator* acclelrator) { 242 return false; 243 } 244 245 void AppContextMenu::ExecuteCommand(int command_id, int event_flags) { 246 if (command_id == LAUNCH_NEW) { 247 delegate_->ExecuteLaunchCommand(event_flags); 248 } else if (command_id == TOGGLE_PIN && controller_->GetPinnable() == 249 AppListControllerDelegate::PIN_EDITABLE) { 250 if (controller_->IsAppPinned(app_id_)) 251 controller_->UnpinApp(app_id_); 252 else 253 controller_->PinApp(app_id_); 254 } else if (command_id == CREATE_SHORTCUTS) { 255 controller_->DoCreateShortcutsFlow(profile_, app_id_); 256 } else if (command_id == SHOW_APP_INFO) { 257 controller_->DoShowAppInfoFlow(profile_, app_id_); 258 } else if (command_id >= USE_LAUNCH_TYPE_COMMAND_START && 259 command_id < USE_LAUNCH_TYPE_COMMAND_END) { 260 extensions::LaunchType launch_type = static_cast<extensions::LaunchType>( 261 command_id - USE_LAUNCH_TYPE_COMMAND_START); 262 // Streamlined hosted apps can only toggle between LAUNCH_TYPE_WINDOW and 263 // LAUNCH_TYPE_REGULAR. 264 if (CommandLine::ForCurrentProcess()->HasSwitch( 265 switches::kEnableStreamlinedHostedApps)) { 266 launch_type = (controller_->GetExtensionLaunchType(profile_, app_id_) == 267 extensions::LAUNCH_TYPE_REGULAR) ? 268 extensions::LAUNCH_TYPE_WINDOW : 269 extensions::LAUNCH_TYPE_REGULAR; 270 } 271 controller_->SetExtensionLaunchType(profile_, app_id_, launch_type); 272 } else if (command_id == OPTIONS) { 273 controller_->ShowOptionsPage(profile_, app_id_); 274 } else if (command_id == UNINSTALL) { 275 controller_->UninstallApp(profile_, app_id_); 276 } else if (command_id == DETAILS) { 277 controller_->ShowAppInWebStore(profile_, app_id_, is_search_result_); 278 } else if (command_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST && 279 command_id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) { 280 extension_menu_items_->ExecuteCommand(command_id, NULL, 281 content::ContextMenuParams()); 282 } else if (command_id == MENU_NEW_WINDOW) { 283 controller_->CreateNewWindow(profile_, false); 284 } else if (command_id == MENU_NEW_INCOGNITO_WINDOW) { 285 controller_->CreateNewWindow(profile_, true); 286 } 287 } 288 289 } // namespace app_list 290