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 "base/strings/utf_string_conversions.h"
      6 #include "chrome/app/chrome_command_ids.h"
      7 #include "chrome/browser/extensions/context_menu_matcher.h"
      8 #include "chrome/browser/extensions/extension_service.h"
      9 #include "chrome/browser/extensions/extension_system.h"
     10 #include "chrome/browser/profiles/profile.h"
     11 #include "content/public/common/context_menu_params.h"
     12 #include "ui/gfx/favicon_size.h"
     13 #include "ui/gfx/image/image.h"
     14 
     15 namespace extensions {
     16 
     17 // static
     18 const size_t ContextMenuMatcher::kMaxExtensionItemTitleLength = 75;
     19 
     20 ContextMenuMatcher::ContextMenuMatcher(
     21     Profile* profile,
     22     ui::SimpleMenuModel::Delegate* delegate,
     23     ui::SimpleMenuModel* menu_model,
     24     const base::Callback<bool(const MenuItem*)>& filter)
     25     : profile_(profile), menu_model_(menu_model), delegate_(delegate),
     26       filter_(filter) {
     27 }
     28 
     29 void ContextMenuMatcher::AppendExtensionItems(const std::string& extension_id,
     30                                               const string16& selection_text,
     31                                               int* index)
     32 {
     33   DCHECK_GE(*index, 0);
     34   int max_index =
     35       IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST - IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST;
     36   if (*index >= max_index)
     37     return;
     38 
     39   const Extension* extension = NULL;
     40   MenuItem::List items;
     41   bool can_cross_incognito;
     42   if (!GetRelevantExtensionTopLevelItems(extension_id, &extension,
     43                                          &can_cross_incognito, items))
     44     return;
     45 
     46   if (items.empty())
     47     return;
     48 
     49   // If this is the first extension-provided menu item, and there are other
     50   // items in the menu, and the last item is not a separator add a separator.
     51   if (*index == 0 && menu_model_->GetItemCount())
     52     menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
     53 
     54   // Extensions (other than platform apps) are only allowed one top-level slot
     55   // (and it can't be a radio or checkbox item because we are going to put the
     56   // extension icon next to it).
     57   // If they have more than that, we automatically push them into a submenu.
     58   if (extension->is_platform_app()) {
     59     RecursivelyAppendExtensionItems(items, can_cross_incognito, selection_text,
     60                                     menu_model_, index);
     61   } else {
     62     int menu_id = IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST + (*index)++;
     63     string16 title;
     64     MenuItem::List submenu_items;
     65 
     66     if (items.size() > 1 || items[0]->type() != MenuItem::NORMAL) {
     67       title = UTF8ToUTF16(extension->name());
     68       submenu_items = items;
     69     } else {
     70       MenuItem* item = items[0];
     71       extension_item_map_[menu_id] = item->id();
     72       title = item->TitleWithReplacement(selection_text,
     73                                        kMaxExtensionItemTitleLength);
     74       submenu_items = GetRelevantExtensionItems(item->children(),
     75                                                 can_cross_incognito);
     76     }
     77 
     78     // Now add our item(s) to the menu_model_.
     79     if (submenu_items.empty()) {
     80       menu_model_->AddItem(menu_id, title);
     81     } else {
     82       ui::SimpleMenuModel* submenu = new ui::SimpleMenuModel(delegate_);
     83       extension_menu_models_.push_back(submenu);
     84       menu_model_->AddSubMenu(menu_id, title, submenu);
     85       RecursivelyAppendExtensionItems(submenu_items, can_cross_incognito,
     86                                       selection_text, submenu, index);
     87     }
     88     SetExtensionIcon(extension_id);
     89   }
     90 }
     91 
     92 void ContextMenuMatcher::Clear() {
     93   extension_item_map_.clear();
     94   extension_menu_models_.clear();
     95 }
     96 
     97 base::string16 ContextMenuMatcher::GetTopLevelContextMenuTitle(
     98     const std::string& extension_id,
     99     const string16& selection_text) {
    100   const Extension* extension = NULL;
    101   MenuItem::List items;
    102   bool can_cross_incognito;
    103   GetRelevantExtensionTopLevelItems(extension_id, &extension,
    104       &can_cross_incognito, items);
    105 
    106   base::string16 title;
    107 
    108   if (items.empty() ||
    109       items.size() > 1 ||
    110       items[0]->type() != MenuItem::NORMAL) {
    111     title = UTF8ToUTF16(extension->name());
    112   } else {
    113     MenuItem* item = items[0];
    114     title = item->TitleWithReplacement(
    115         selection_text, kMaxExtensionItemTitleLength);
    116   }
    117   return title;
    118 }
    119 
    120 bool ContextMenuMatcher::IsCommandIdChecked(int command_id) const {
    121   MenuItem* item = GetExtensionMenuItem(command_id);
    122   if (!item)
    123     return false;
    124   return item->checked();
    125 }
    126 
    127 bool ContextMenuMatcher::IsCommandIdEnabled(int command_id) const {
    128   MenuItem* item = GetExtensionMenuItem(command_id);
    129   if (!item)
    130     return true;
    131   return item->enabled();
    132 }
    133 
    134 void ContextMenuMatcher::ExecuteCommand(int command_id,
    135     content::WebContents* web_contents,
    136     const content::ContextMenuParams& params) {
    137   MenuManager* manager = extensions::ExtensionSystem::Get(profile_)->
    138       extension_service()->menu_manager();
    139   MenuItem* item = GetExtensionMenuItem(command_id);
    140   if (!item)
    141     return;
    142 
    143   manager->ExecuteCommand(profile_, web_contents, params, item->id());
    144 }
    145 
    146 bool ContextMenuMatcher::GetRelevantExtensionTopLevelItems(
    147     const std::string& extension_id,
    148     const Extension** extension,
    149     bool* can_cross_incognito,
    150     MenuItem::List& items) {
    151   ExtensionService* service =
    152       extensions::ExtensionSystem::Get(profile_)->extension_service();
    153   MenuManager* manager = service->menu_manager();
    154   *extension = service->GetExtensionById(extension_id, false);
    155 
    156   if (!*extension)
    157     return false;
    158 
    159   // Find matching items.
    160   const MenuItem::List* all_items = manager->MenuItems(extension_id);
    161   if (!all_items || all_items->empty())
    162     return false;
    163 
    164   *can_cross_incognito = service->CanCrossIncognito(*extension);
    165   items = GetRelevantExtensionItems(*all_items,
    166                                     *can_cross_incognito);
    167 
    168   return true;
    169 }
    170 
    171 MenuItem::List ContextMenuMatcher::GetRelevantExtensionItems(
    172     const MenuItem::List& items,
    173     bool can_cross_incognito) {
    174   MenuItem::List result;
    175   for (MenuItem::List::const_iterator i = items.begin();
    176        i != items.end(); ++i) {
    177     const MenuItem* item = *i;
    178 
    179     if (!filter_.Run(item))
    180       continue;
    181 
    182     if (item->id().incognito == profile_->IsOffTheRecord() ||
    183         can_cross_incognito)
    184       result.push_back(*i);
    185   }
    186   return result;
    187 }
    188 
    189 void ContextMenuMatcher::RecursivelyAppendExtensionItems(
    190     const MenuItem::List& items,
    191     bool can_cross_incognito,
    192     const string16& selection_text,
    193     ui::SimpleMenuModel* menu_model,
    194     int* index)
    195 {
    196   MenuItem::Type last_type = MenuItem::NORMAL;
    197   int radio_group_id = 1;
    198 
    199   for (MenuItem::List::const_iterator i = items.begin();
    200        i != items.end(); ++i) {
    201     MenuItem* item = *i;
    202 
    203     // If last item was of type radio but the current one isn't, auto-insert
    204     // a separator.  The converse case is handled below.
    205     if (last_type == MenuItem::RADIO &&
    206         item->type() != MenuItem::RADIO) {
    207       menu_model->AddSeparator(ui::NORMAL_SEPARATOR);
    208       last_type = MenuItem::SEPARATOR;
    209     }
    210 
    211     int menu_id = IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST + (*index)++;
    212     if (menu_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST)
    213       return;
    214     extension_item_map_[menu_id] = item->id();
    215     string16 title = item->TitleWithReplacement(selection_text,
    216                                                 kMaxExtensionItemTitleLength);
    217     if (item->type() == MenuItem::NORMAL) {
    218       MenuItem::List children =
    219           GetRelevantExtensionItems(item->children(), can_cross_incognito);
    220       if (children.empty()) {
    221         menu_model->AddItem(menu_id, title);
    222       } else {
    223         ui::SimpleMenuModel* submenu = new ui::SimpleMenuModel(delegate_);
    224         extension_menu_models_.push_back(submenu);
    225         menu_model->AddSubMenu(menu_id, title, submenu);
    226         RecursivelyAppendExtensionItems(children, can_cross_incognito,
    227                                         selection_text, submenu, index);
    228       }
    229     } else if (item->type() == MenuItem::CHECKBOX) {
    230       menu_model->AddCheckItem(menu_id, title);
    231     } else if (item->type() == MenuItem::RADIO) {
    232       if (i != items.begin() &&
    233           last_type != MenuItem::RADIO) {
    234         radio_group_id++;
    235 
    236         // Auto-append a separator if needed.
    237         menu_model->AddSeparator(ui::NORMAL_SEPARATOR);
    238       }
    239 
    240       menu_model->AddRadioItem(menu_id, title, radio_group_id);
    241     } else if (item->type() == MenuItem::SEPARATOR) {
    242       menu_model->AddSeparator(ui::NORMAL_SEPARATOR);
    243     }
    244     last_type = item->type();
    245   }
    246 }
    247 
    248 MenuItem* ContextMenuMatcher::GetExtensionMenuItem(int id) const {
    249   MenuManager* manager = extensions::ExtensionSystem::Get(profile_)->
    250       extension_service()->menu_manager();
    251   std::map<int, MenuItem::Id>::const_iterator i =
    252       extension_item_map_.find(id);
    253   if (i != extension_item_map_.end()) {
    254     MenuItem* item = manager->GetItemById(i->second);
    255     if (item)
    256       return item;
    257   }
    258   return NULL;
    259 }
    260 
    261 void ContextMenuMatcher::SetExtensionIcon(const std::string& extension_id) {
    262   ExtensionService* service =
    263       extensions::ExtensionSystem::Get(profile_)->extension_service();
    264   MenuManager* menu_manager = service->menu_manager();
    265 
    266   int index = menu_model_->GetItemCount() - 1;
    267   DCHECK_GE(index, 0);
    268 
    269   const SkBitmap& icon = menu_manager->GetIconForExtension(extension_id);
    270   DCHECK(icon.width() == gfx::kFaviconSize);
    271   DCHECK(icon.height() == gfx::kFaviconSize);
    272 
    273   menu_model_->SetIcon(index, gfx::Image::CreateFrom1xBitmap(icon));
    274 }
    275 
    276 }  // namespace extensions
    277