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_util.h"
     10 #include "chrome/browser/profiles/profile.h"
     11 #include "content/public/common/context_menu_params.h"
     12 #include "extensions/browser/extension_system.h"
     13 #include "ui/gfx/favicon_size.h"
     14 #include "ui/gfx/image/image.h"
     15 
     16 namespace extensions {
     17 
     18 // static
     19 const size_t ContextMenuMatcher::kMaxExtensionItemTitleLength = 75;
     20 
     21 ContextMenuMatcher::ContextMenuMatcher(
     22     Profile* profile,
     23     ui::SimpleMenuModel::Delegate* delegate,
     24     ui::SimpleMenuModel* menu_model,
     25     const base::Callback<bool(const MenuItem*)>& filter)
     26     : profile_(profile), menu_model_(menu_model), delegate_(delegate),
     27       filter_(filter) {
     28 }
     29 
     30 void ContextMenuMatcher::AppendExtensionItems(
     31     const MenuItem::ExtensionKey& extension_key,
     32     const base::string16& selection_text,
     33     int* index) {
     34   DCHECK_GE(*index, 0);
     35   int max_index =
     36       IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST - IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST;
     37   if (*index >= max_index)
     38     return;
     39 
     40   const Extension* extension = NULL;
     41   MenuItem::List items;
     42   bool can_cross_incognito;
     43   if (!GetRelevantExtensionTopLevelItems(
     44           extension_key, &extension, &can_cross_incognito, items))
     45     return;
     46 
     47   if (items.empty())
     48     return;
     49 
     50   // If this is the first extension-provided menu item, and there are other
     51   // items in the menu, and the last item is not a separator add a separator.
     52   if (*index == 0 && menu_model_->GetItemCount())
     53     menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
     54 
     55   // Extensions (other than platform apps) are only allowed one top-level slot
     56   // (and it can't be a radio or checkbox item because we are going to put the
     57   // extension icon next to it).
     58   // If they have more than that, we automatically push them into a submenu.
     59   if (extension->is_platform_app()) {
     60     RecursivelyAppendExtensionItems(items, can_cross_incognito, selection_text,
     61                                     menu_model_, index);
     62   } else {
     63     int menu_id = IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST + (*index)++;
     64     base::string16 title;
     65     MenuItem::List submenu_items;
     66 
     67     if (items.size() > 1 || items[0]->type() != MenuItem::NORMAL) {
     68       title = base::UTF8ToUTF16(extension->name());
     69       submenu_items = items;
     70     } else {
     71       MenuItem* item = items[0];
     72       extension_item_map_[menu_id] = item->id();
     73       title = item->TitleWithReplacement(selection_text,
     74                                        kMaxExtensionItemTitleLength);
     75       submenu_items = GetRelevantExtensionItems(item->children(),
     76                                                 can_cross_incognito);
     77     }
     78 
     79     // Now add our item(s) to the menu_model_.
     80     if (submenu_items.empty()) {
     81       menu_model_->AddItem(menu_id, title);
     82     } else {
     83       ui::SimpleMenuModel* submenu = new ui::SimpleMenuModel(delegate_);
     84       extension_menu_models_.push_back(submenu);
     85       menu_model_->AddSubMenu(menu_id, title, submenu);
     86       RecursivelyAppendExtensionItems(submenu_items, can_cross_incognito,
     87                                       selection_text, submenu, index);
     88     }
     89     SetExtensionIcon(extension_key.extension_id);
     90   }
     91 }
     92 
     93 void ContextMenuMatcher::Clear() {
     94   extension_item_map_.clear();
     95   extension_menu_models_.clear();
     96 }
     97 
     98 base::string16 ContextMenuMatcher::GetTopLevelContextMenuTitle(
     99     const MenuItem::ExtensionKey& extension_key,
    100     const base::string16& selection_text) {
    101   const Extension* extension = NULL;
    102   MenuItem::List items;
    103   bool can_cross_incognito;
    104   GetRelevantExtensionTopLevelItems(
    105       extension_key, &extension, &can_cross_incognito, items);
    106 
    107   base::string16 title;
    108 
    109   if (items.empty() ||
    110       items.size() > 1 ||
    111       items[0]->type() != MenuItem::NORMAL) {
    112     title = base::UTF8ToUTF16(extension->name());
    113   } else {
    114     MenuItem* item = items[0];
    115     title = item->TitleWithReplacement(
    116         selection_text, kMaxExtensionItemTitleLength);
    117   }
    118   return title;
    119 }
    120 
    121 bool ContextMenuMatcher::IsCommandIdChecked(int command_id) const {
    122   MenuItem* item = GetExtensionMenuItem(command_id);
    123   if (!item)
    124     return false;
    125   return item->checked();
    126 }
    127 
    128 bool ContextMenuMatcher::IsCommandIdEnabled(int command_id) const {
    129   MenuItem* item = GetExtensionMenuItem(command_id);
    130   if (!item)
    131     return true;
    132   return item->enabled();
    133 }
    134 
    135 void ContextMenuMatcher::ExecuteCommand(int command_id,
    136     content::WebContents* web_contents,
    137     const content::ContextMenuParams& params) {
    138   MenuItem* item = GetExtensionMenuItem(command_id);
    139   if (!item)
    140     return;
    141 
    142   MenuManager* manager = MenuManager::Get(profile_);
    143   manager->ExecuteCommand(profile_, web_contents, params, item->id());
    144 }
    145 
    146 bool ContextMenuMatcher::GetRelevantExtensionTopLevelItems(
    147     const MenuItem::ExtensionKey& extension_key,
    148     const Extension** extension,
    149     bool* can_cross_incognito,
    150     MenuItem::List& items) {
    151   ExtensionService* service =
    152       extensions::ExtensionSystem::Get(profile_)->extension_service();
    153   *extension = service->GetExtensionById(extension_key.extension_id, false);
    154 
    155   if (!*extension)
    156     return false;
    157 
    158   // Find matching items.
    159   MenuManager* manager = MenuManager::Get(profile_);
    160   const MenuItem::List* all_items = manager->MenuItems(extension_key);
    161   if (!all_items || all_items->empty())
    162     return false;
    163 
    164   *can_cross_incognito = util::CanCrossIncognito(*extension, profile_);
    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 base::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     base::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 = MenuManager::Get(profile_);
    250   std::map<int, MenuItem::Id>::const_iterator i =
    251       extension_item_map_.find(id);
    252   if (i != extension_item_map_.end()) {
    253     MenuItem* item = manager->GetItemById(i->second);
    254     if (item)
    255       return item;
    256   }
    257   return NULL;
    258 }
    259 
    260 void ContextMenuMatcher::SetExtensionIcon(const std::string& extension_id) {
    261   MenuManager* menu_manager = MenuManager::Get(profile_);
    262 
    263   int index = menu_model_->GetItemCount() - 1;
    264   DCHECK_GE(index, 0);
    265 
    266   const SkBitmap& icon = menu_manager->GetIconForExtension(extension_id);
    267   DCHECK(icon.width() == gfx::kFaviconSize);
    268   DCHECK(icon.height() == gfx::kFaviconSize);
    269 
    270   menu_model_->SetIcon(index, gfx::Image::CreateFrom1xBitmap(icon));
    271 }
    272 
    273 }  // namespace extensions
    274