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/extensions/extension_context_menu_model.h" 6 7#include "base/prefs/pref_service.h" 8#include "base/strings/utf_string_conversions.h" 9#include "chrome/app/chrome_command_ids.h" 10#include "chrome/browser/extensions/active_script_controller.h" 11#include "chrome/browser/extensions/api/extension_action/extension_action_api.h" 12#include "chrome/browser/extensions/context_menu_matcher.h" 13#include "chrome/browser/extensions/extension_action.h" 14#include "chrome/browser/extensions/extension_action_manager.h" 15#include "chrome/browser/extensions/extension_service.h" 16#include "chrome/browser/extensions/extension_tab_util.h" 17#include "chrome/browser/extensions/menu_manager.h" 18#include "chrome/browser/profiles/profile.h" 19#include "chrome/browser/sessions/session_tab_helper.h" 20#include "chrome/browser/ui/browser.h" 21#include "chrome/browser/ui/browser_window.h" 22#include "chrome/browser/ui/chrome_pages.h" 23#include "chrome/browser/ui/tabs/tab_strip_model.h" 24#include "chrome/common/extensions/extension_constants.h" 25#include "chrome/common/extensions/manifest_url_handler.h" 26#include "chrome/common/pref_names.h" 27#include "chrome/common/url_constants.h" 28#include "chrome/grit/chromium_strings.h" 29#include "chrome/grit/generated_resources.h" 30#include "content/public/browser/web_contents.h" 31#include "content/public/common/context_menu_params.h" 32#include "extensions/browser/extension_prefs.h" 33#include "extensions/browser/extension_registry.h" 34#include "extensions/browser/extension_system.h" 35#include "extensions/browser/management_policy.h" 36#include "extensions/browser/uninstall_reason.h" 37#include "extensions/common/extension.h" 38#include "extensions/common/feature_switch.h" 39#include "extensions/common/manifest_handlers/options_page_info.h" 40#include "ui/base/l10n/l10n_util.h" 41 42using content::OpenURLParams; 43using content::Referrer; 44using content::WebContents; 45using extensions::Extension; 46using extensions::ExtensionActionAPI; 47using extensions::ExtensionPrefs; 48using extensions::MenuItem; 49using extensions::MenuManager; 50 51namespace { 52 53// Returns true if the given |item| is of the given |type|. 54bool MenuItemMatchesAction(ExtensionContextMenuModel::ActionType type, 55 const MenuItem* item) { 56 if (type == ExtensionContextMenuModel::NO_ACTION) 57 return false; 58 59 const MenuItem::ContextList& contexts = item->contexts(); 60 61 if (contexts.Contains(MenuItem::ALL)) 62 return true; 63 if (contexts.Contains(MenuItem::PAGE_ACTION) && 64 (type == ExtensionContextMenuModel::PAGE_ACTION)) 65 return true; 66 if (contexts.Contains(MenuItem::BROWSER_ACTION) && 67 (type == ExtensionContextMenuModel::BROWSER_ACTION)) 68 return true; 69 70 return false; 71} 72 73// Returns the id for the visibility command for the given |extension|, or -1 74// if none should be shown. 75int GetVisibilityStringId(Profile* profile, const Extension* extension) { 76 DCHECK(profile); 77 int string_id = -1; 78 if (!extensions::FeatureSwitch::extension_action_redesign()->IsEnabled()) { 79 // Without the toolbar redesign, we only show the visibility toggle for 80 // browser actions, and only give the option to hide. 81 if (extensions::ExtensionActionManager::Get(profile)->GetBrowserAction( 82 *extension)) { 83 string_id = IDS_EXTENSIONS_HIDE_BUTTON; 84 } 85 } else { 86 // With the redesign, we display "show" or "hide" based on the icon's 87 // visibility. 88 bool visible = ExtensionActionAPI::GetBrowserActionVisibility( 89 ExtensionPrefs::Get(profile), extension->id()); 90 string_id = 91 visible ? IDS_EXTENSIONS_HIDE_BUTTON : IDS_EXTENSIONS_SHOW_BUTTON; 92 } 93 return string_id; 94} 95 96} // namespace 97 98ExtensionContextMenuModel::ExtensionContextMenuModel(const Extension* extension, 99 Browser* browser, 100 PopupDelegate* delegate) 101 : SimpleMenuModel(this), 102 extension_id_(extension->id()), 103 browser_(browser), 104 profile_(browser->profile()), 105 delegate_(delegate), 106 action_type_(NO_ACTION), 107 extension_items_count_(0) { 108 InitMenu(extension); 109 110 if (profile_->GetPrefs()->GetBoolean(prefs::kExtensionsUIDeveloperMode) && 111 delegate_) { 112 AddSeparator(ui::NORMAL_SEPARATOR); 113 AddItemWithStringId(INSPECT_POPUP, IDS_EXTENSION_ACTION_INSPECT_POPUP); 114 } 115} 116 117ExtensionContextMenuModel::ExtensionContextMenuModel(const Extension* extension, 118 Browser* browser) 119 : SimpleMenuModel(this), 120 extension_id_(extension->id()), 121 browser_(browser), 122 profile_(browser->profile()), 123 delegate_(NULL), 124 action_type_(NO_ACTION), 125 extension_items_count_(0) { 126 InitMenu(extension); 127} 128 129bool ExtensionContextMenuModel::IsCommandIdChecked(int command_id) const { 130 if (command_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST && 131 command_id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) 132 return extension_items_->IsCommandIdChecked(command_id); 133 return false; 134} 135 136bool ExtensionContextMenuModel::IsCommandIdEnabled(int command_id) const { 137 const Extension* extension = GetExtension(); 138 if (!extension) 139 return false; 140 141 if (command_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST && 142 command_id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) { 143 return extension_items_->IsCommandIdEnabled(command_id); 144 } else if (command_id == CONFIGURE) { 145 return extensions::OptionsPageInfo::HasOptionsPage(extension); 146 } else if (command_id == NAME) { 147 // The NAME links to the Homepage URL. If the extension doesn't have a 148 // homepage, we just disable this menu item. 149 return extensions::ManifestURL::GetHomepageURL(extension).is_valid(); 150 } else if (command_id == INSPECT_POPUP) { 151 WebContents* web_contents = GetActiveWebContents(); 152 if (!web_contents) 153 return false; 154 155 return extension_action_ && 156 extension_action_->HasPopup(SessionTabHelper::IdForTab(web_contents)); 157 } else if (command_id == UNINSTALL) { 158 // Some extension types can not be uninstalled. 159 return extensions::ExtensionSystem::Get( 160 profile_)->management_policy()->UserMayModifySettings(extension, NULL); 161 } 162 return true; 163} 164 165bool ExtensionContextMenuModel::GetAcceleratorForCommandId( 166 int command_id, ui::Accelerator* accelerator) { 167 return false; 168} 169 170void ExtensionContextMenuModel::ExecuteCommand(int command_id, 171 int event_flags) { 172 const Extension* extension = GetExtension(); 173 if (!extension) 174 return; 175 176 if (command_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST && 177 command_id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) { 178 WebContents* web_contents = 179 browser_->tab_strip_model()->GetActiveWebContents(); 180 DCHECK(extension_items_); 181 extension_items_->ExecuteCommand( 182 command_id, web_contents, content::ContextMenuParams()); 183 return; 184 } 185 186 switch (command_id) { 187 case NAME: { 188 OpenURLParams params(extensions::ManifestURL::GetHomepageURL(extension), 189 Referrer(), NEW_FOREGROUND_TAB, 190 ui::PAGE_TRANSITION_LINK, false); 191 browser_->OpenURL(params); 192 break; 193 } 194 case ALWAYS_RUN: { 195 WebContents* web_contents = GetActiveWebContents(); 196 if (web_contents) { 197 extensions::ActiveScriptController::GetForWebContents(web_contents) 198 ->AlwaysRunOnVisibleOrigin(extension); 199 } 200 break; 201 } 202 case CONFIGURE: 203 DCHECK(extensions::OptionsPageInfo::HasOptionsPage(extension)); 204 extensions::ExtensionTabUtil::OpenOptionsPage(extension, browser_); 205 break; 206 case TOGGLE_VISIBILITY: { 207 ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_); 208 bool visible = ExtensionActionAPI::GetBrowserActionVisibility( 209 prefs, extension->id()); 210 ExtensionActionAPI::SetBrowserActionVisibility( 211 prefs, extension->id(), !visible); 212 break; 213 } 214 case UNINSTALL: { 215 AddRef(); // Balanced in Accepted() and Canceled() 216 extension_uninstall_dialog_.reset( 217 extensions::ExtensionUninstallDialog::Create( 218 profile_, browser_->window()->GetNativeWindow(), this)); 219 extension_uninstall_dialog_->ConfirmUninstall(extension); 220 break; 221 } 222 case MANAGE: { 223 chrome::ShowExtensions(browser_, extension->id()); 224 break; 225 } 226 case INSPECT_POPUP: { 227 delegate_->InspectPopup(); 228 break; 229 } 230 default: 231 NOTREACHED() << "Unknown option"; 232 break; 233 } 234} 235 236void ExtensionContextMenuModel::ExtensionUninstallAccepted() { 237 if (GetExtension()) { 238 extensions::ExtensionSystem::Get(profile_) 239 ->extension_service() 240 ->UninstallExtension(extension_id_, 241 extensions::UNINSTALL_REASON_USER_INITIATED, 242 base::Bind(&base::DoNothing), 243 NULL); 244 } 245 Release(); 246} 247 248void ExtensionContextMenuModel::ExtensionUninstallCanceled() { 249 Release(); 250} 251 252ExtensionContextMenuModel::~ExtensionContextMenuModel() {} 253 254void ExtensionContextMenuModel::InitMenu(const Extension* extension) { 255 DCHECK(extension); 256 257 extensions::ExtensionActionManager* extension_action_manager = 258 extensions::ExtensionActionManager::Get(profile_); 259 extension_action_ = extension_action_manager->GetBrowserAction(*extension); 260 if (!extension_action_) { 261 extension_action_ = extension_action_manager->GetPageAction(*extension); 262 if (extension_action_) 263 action_type_ = PAGE_ACTION; 264 } else { 265 action_type_ = BROWSER_ACTION; 266 } 267 268 extension_items_.reset(new extensions::ContextMenuMatcher( 269 profile_, this, this, base::Bind(MenuItemMatchesAction, action_type_))); 270 271 std::string extension_name = extension->name(); 272 // Ampersands need to be escaped to avoid being treated like 273 // mnemonics in the menu. 274 base::ReplaceChars(extension_name, "&", "&&", &extension_name); 275 AddItem(NAME, base::UTF8ToUTF16(extension_name)); 276 AppendExtensionItems(); 277 AddSeparator(ui::NORMAL_SEPARATOR); 278 279 // Add the "Always Allow" item for adding persisted permissions for script 280 // injections if there is an active action for this extension. Note that this 281 // will add it to *all* extension action context menus, not just the one 282 // attached to the script injection request icon, but that's okay. 283 WebContents* web_contents = GetActiveWebContents(); 284 if (web_contents && 285 extensions::ActiveScriptController::GetForWebContents(web_contents) 286 ->WantsToRun(extension)) { 287 AddItemWithStringId(ALWAYS_RUN, IDS_EXTENSIONS_ALWAYS_RUN); 288 } 289 290 AddItemWithStringId(CONFIGURE, IDS_EXTENSIONS_OPTIONS_MENU_ITEM); 291 AddItem(UNINSTALL, l10n_util::GetStringUTF16(IDS_EXTENSIONS_UNINSTALL)); 292 293 // Add a toggle visibility (show/hide) if the extension icon is shown on the 294 // toolbar. 295 int visibility_string_id = GetVisibilityStringId(profile_, extension); 296 if (visibility_string_id != -1) 297 AddItemWithStringId(TOGGLE_VISIBILITY, visibility_string_id); 298 299 AddSeparator(ui::NORMAL_SEPARATOR); 300 AddItemWithStringId(MANAGE, IDS_MANAGE_EXTENSION); 301} 302 303const Extension* ExtensionContextMenuModel::GetExtension() const { 304 return extensions::ExtensionRegistry::Get(profile_) 305 ->enabled_extensions() 306 .GetByID(extension_id_); 307} 308 309void ExtensionContextMenuModel::AppendExtensionItems() { 310 extension_items_->Clear(); 311 312 MenuManager* menu_manager = MenuManager::Get(profile_); 313 if (!menu_manager || 314 !menu_manager->MenuItems(MenuItem::ExtensionKey(extension_id_))) 315 return; 316 317 AddSeparator(ui::NORMAL_SEPARATOR); 318 319 extension_items_count_ = 0; 320 extension_items_->AppendExtensionItems(MenuItem::ExtensionKey(extension_id_), 321 base::string16(), 322 &extension_items_count_, 323 true); // is_action_menu 324} 325 326content::WebContents* ExtensionContextMenuModel::GetActiveWebContents() const { 327 return browser_->tab_strip_model()->GetActiveWebContents(); 328} 329