Home | History | Annotate | Download | only in extensions
      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 
     42 using content::OpenURLParams;
     43 using content::Referrer;
     44 using content::WebContents;
     45 using extensions::Extension;
     46 using extensions::ExtensionActionAPI;
     47 using extensions::ExtensionPrefs;
     48 using extensions::MenuItem;
     49 using extensions::MenuManager;
     50 
     51 namespace {
     52 
     53 // Returns true if the given |item| is of the given |type|.
     54 bool 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.
     75 int 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 
     98 ExtensionContextMenuModel::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 
    117 ExtensionContextMenuModel::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 
    129 bool 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 
    136 bool 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 
    165 bool ExtensionContextMenuModel::GetAcceleratorForCommandId(
    166     int command_id, ui::Accelerator* accelerator) {
    167   return false;
    168 }
    169 
    170 void 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 
    236 void 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 
    248 void ExtensionContextMenuModel::ExtensionUninstallCanceled() {
    249   Release();
    250 }
    251 
    252 ExtensionContextMenuModel::~ExtensionContextMenuModel() {}
    253 
    254 void 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 
    303 const Extension* ExtensionContextMenuModel::GetExtension() const {
    304   return extensions::ExtensionRegistry::Get(profile_)
    305       ->enabled_extensions()
    306       .GetByID(extension_id_);
    307 }
    308 
    309 void 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 
    326 content::WebContents* ExtensionContextMenuModel::GetActiveWebContents() const {
    327   return browser_->tab_strip_model()->GetActiveWebContents();
    328 }
    329