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/menu_manager.h"
      6 
      7 #include <algorithm>
      8 
      9 #include "base/json/json_writer.h"
     10 #include "base/logging.h"
     11 #include "base/stl_util.h"
     12 #include "base/strings/string_util.h"
     13 #include "base/strings/utf_string_conversions.h"
     14 #include "base/values.h"
     15 #include "chrome/browser/chrome_notification_types.h"
     16 #include "chrome/browser/extensions/event_names.h"
     17 #include "chrome/browser/extensions/extension_service.h"
     18 #include "chrome/browser/extensions/extension_system.h"
     19 #include "chrome/browser/extensions/extension_tab_util.h"
     20 #include "chrome/browser/extensions/menu_manager_factory.h"
     21 #include "chrome/browser/extensions/state_store.h"
     22 #include "chrome/browser/extensions/tab_helper.h"
     23 #include "chrome/browser/profiles/profile.h"
     24 #include "chrome/common/extensions/api/context_menus.h"
     25 #include "content/public/browser/notification_details.h"
     26 #include "content/public/browser/notification_service.h"
     27 #include "content/public/browser/notification_source.h"
     28 #include "content/public/browser/web_contents.h"
     29 #include "content/public/common/context_menu_params.h"
     30 #include "extensions/browser/event_router.h"
     31 #include "extensions/common/extension.h"
     32 #include "extensions/common/manifest_handlers/background_info.h"
     33 #include "ui/gfx/favicon_size.h"
     34 #include "ui/gfx/text_elider.h"
     35 
     36 using content::WebContents;
     37 using extensions::ExtensionSystem;
     38 
     39 namespace extensions {
     40 
     41 namespace context_menus = api::context_menus;
     42 
     43 namespace {
     44 
     45 // Keys for serialization to and from Value to store in the preferences.
     46 const char kContextMenusKey[] = "context_menus";
     47 
     48 const char kCheckedKey[] = "checked";
     49 const char kContextsKey[] = "contexts";
     50 const char kDocumentURLPatternsKey[] = "document_url_patterns";
     51 const char kEnabledKey[] = "enabled";
     52 const char kIncognitoKey[] = "incognito";
     53 const char kParentUIDKey[] = "parent_uid";
     54 const char kStringUIDKey[] = "string_uid";
     55 const char kTargetURLPatternsKey[] = "target_url_patterns";
     56 const char kTitleKey[] = "title";
     57 const char kTypeKey[] = "type";
     58 
     59 void SetIdKeyValue(base::DictionaryValue* properties,
     60                    const char* key,
     61                    const MenuItem::Id& id) {
     62   if (id.uid == 0)
     63     properties->SetString(key, id.string_uid);
     64   else
     65     properties->SetInteger(key, id.uid);
     66 }
     67 
     68 MenuItem::List MenuItemsFromValue(const std::string& extension_id,
     69                                   base::Value* value) {
     70   MenuItem::List items;
     71 
     72   base::ListValue* list = NULL;
     73   if (!value || !value->GetAsList(&list))
     74     return items;
     75 
     76   for (size_t i = 0; i < list->GetSize(); ++i) {
     77     base::DictionaryValue* dict = NULL;
     78     if (!list->GetDictionary(i, &dict))
     79       continue;
     80     MenuItem* item = MenuItem::Populate(
     81         extension_id, *dict, NULL);
     82     if (!item)
     83       continue;
     84     items.push_back(item);
     85   }
     86   return items;
     87 }
     88 
     89 scoped_ptr<base::Value> MenuItemsToValue(const MenuItem::List& items) {
     90   scoped_ptr<base::ListValue> list(new base::ListValue());
     91   for (size_t i = 0; i < items.size(); ++i)
     92     list->Append(items[i]->ToValue().release());
     93   return scoped_ptr<Value>(list.release());
     94 }
     95 
     96 bool GetStringList(const DictionaryValue& dict,
     97                    const std::string& key,
     98                    std::vector<std::string>* out) {
     99   if (!dict.HasKey(key))
    100     return true;
    101 
    102   const base::ListValue* list = NULL;
    103   if (!dict.GetListWithoutPathExpansion(key, &list))
    104     return false;
    105 
    106   for (size_t i = 0; i < list->GetSize(); ++i) {
    107     std::string pattern;
    108     if (!list->GetString(i, &pattern))
    109       return false;
    110     out->push_back(pattern);
    111   }
    112 
    113   return true;
    114 }
    115 
    116 }  // namespace
    117 
    118 MenuItem::MenuItem(const Id& id,
    119                    const std::string& title,
    120                    bool checked,
    121                    bool enabled,
    122                    Type type,
    123                    const ContextList& contexts)
    124     : id_(id),
    125       title_(title),
    126       type_(type),
    127       checked_(checked),
    128       enabled_(enabled),
    129       contexts_(contexts) {}
    130 
    131 MenuItem::~MenuItem() {
    132   STLDeleteElements(&children_);
    133 }
    134 
    135 MenuItem* MenuItem::ReleaseChild(const Id& child_id,
    136                                  bool recursive) {
    137   for (List::iterator i = children_.begin(); i != children_.end(); ++i) {
    138     MenuItem* child = NULL;
    139     if ((*i)->id() == child_id) {
    140       child = *i;
    141       children_.erase(i);
    142       return child;
    143     } else if (recursive) {
    144       child = (*i)->ReleaseChild(child_id, recursive);
    145       if (child)
    146         return child;
    147     }
    148   }
    149   return NULL;
    150 }
    151 
    152 void MenuItem::GetFlattenedSubtree(MenuItem::List* list) {
    153   list->push_back(this);
    154   for (List::iterator i = children_.begin(); i != children_.end(); ++i)
    155     (*i)->GetFlattenedSubtree(list);
    156 }
    157 
    158 std::set<MenuItem::Id> MenuItem::RemoveAllDescendants() {
    159   std::set<Id> result;
    160   for (List::iterator i = children_.begin(); i != children_.end(); ++i) {
    161     MenuItem* child = *i;
    162     result.insert(child->id());
    163     std::set<Id> removed = child->RemoveAllDescendants();
    164     result.insert(removed.begin(), removed.end());
    165   }
    166   STLDeleteElements(&children_);
    167   return result;
    168 }
    169 
    170 base::string16 MenuItem::TitleWithReplacement(const base::string16& selection,
    171                                               size_t max_length) const {
    172   base::string16 result = UTF8ToUTF16(title_);
    173   // TODO(asargent) - Change this to properly handle %% escaping so you can
    174   // put "%s" in titles that won't get substituted.
    175   ReplaceSubstringsAfterOffset(&result, 0, ASCIIToUTF16("%s"), selection);
    176 
    177   if (result.length() > max_length)
    178     result = gfx::TruncateString(result, max_length);
    179   return result;
    180 }
    181 
    182 bool MenuItem::SetChecked(bool checked) {
    183   if (type_ != CHECKBOX && type_ != RADIO)
    184     return false;
    185   checked_ = checked;
    186   return true;
    187 }
    188 
    189 void MenuItem::AddChild(MenuItem* item) {
    190   item->parent_id_.reset(new Id(id_));
    191   children_.push_back(item);
    192 }
    193 
    194 scoped_ptr<DictionaryValue> MenuItem::ToValue() const {
    195   scoped_ptr<DictionaryValue> value(new DictionaryValue);
    196   // Should only be called for extensions with event pages, which only have
    197   // string IDs for items.
    198   DCHECK_EQ(0, id_.uid);
    199   value->SetString(kStringUIDKey, id_.string_uid);
    200   value->SetBoolean(kIncognitoKey, id_.incognito);
    201   value->SetInteger(kTypeKey, type_);
    202   if (type_ != SEPARATOR)
    203     value->SetString(kTitleKey, title_);
    204   if (type_ == CHECKBOX || type_ == RADIO)
    205     value->SetBoolean(kCheckedKey, checked_);
    206   value->SetBoolean(kEnabledKey, enabled_);
    207   value->Set(kContextsKey, contexts_.ToValue().release());
    208   if (parent_id_) {
    209     DCHECK_EQ(0, parent_id_->uid);
    210     value->SetString(kParentUIDKey, parent_id_->string_uid);
    211   }
    212   value->Set(kDocumentURLPatternsKey,
    213              document_url_patterns_.ToValue().release());
    214   value->Set(kTargetURLPatternsKey, target_url_patterns_.ToValue().release());
    215   return value.Pass();
    216 }
    217 
    218 // static
    219 MenuItem* MenuItem::Populate(const std::string& extension_id,
    220                              const DictionaryValue& value,
    221                              std::string* error) {
    222   bool incognito = false;
    223   if (!value.GetBoolean(kIncognitoKey, &incognito))
    224     return NULL;
    225   Id id(incognito, extension_id);
    226   if (!value.GetString(kStringUIDKey, &id.string_uid))
    227     return NULL;
    228   int type_int;
    229   Type type = NORMAL;
    230   if (!value.GetInteger(kTypeKey, &type_int))
    231     return NULL;
    232   type = static_cast<Type>(type_int);
    233   std::string title;
    234   if (type != SEPARATOR && !value.GetString(kTitleKey, &title))
    235     return NULL;
    236   bool checked = false;
    237   if ((type == CHECKBOX || type == RADIO) &&
    238       !value.GetBoolean(kCheckedKey, &checked)) {
    239     return NULL;
    240   }
    241   bool enabled = true;
    242   if (!value.GetBoolean(kEnabledKey, &enabled))
    243     return NULL;
    244   ContextList contexts;
    245   const Value* contexts_value = NULL;
    246   if (!value.Get(kContextsKey, &contexts_value))
    247     return NULL;
    248   if (!contexts.Populate(*contexts_value))
    249     return NULL;
    250 
    251   scoped_ptr<MenuItem> result(new MenuItem(
    252       id, title, checked, enabled, type, contexts));
    253 
    254   std::vector<std::string> document_url_patterns;
    255   if (!GetStringList(value, kDocumentURLPatternsKey, &document_url_patterns))
    256     return NULL;
    257   std::vector<std::string> target_url_patterns;
    258   if (!GetStringList(value, kTargetURLPatternsKey, &target_url_patterns))
    259     return NULL;
    260 
    261   if (!result->PopulateURLPatterns(&document_url_patterns,
    262                                    &target_url_patterns,
    263                                    error)) {
    264     return NULL;
    265   }
    266 
    267   // parent_id is filled in from the value, but it might not be valid. It's left
    268   // to be validated upon being added (via AddChildItem) to the menu manager.
    269   scoped_ptr<Id> parent_id(new Id(incognito, extension_id));
    270   if (value.HasKey(kParentUIDKey)) {
    271     if (!value.GetString(kParentUIDKey, &parent_id->string_uid))
    272       return NULL;
    273     result->parent_id_.swap(parent_id);
    274   }
    275   return result.release();
    276 }
    277 
    278 bool MenuItem::PopulateURLPatterns(
    279     std::vector<std::string>* document_url_patterns,
    280     std::vector<std::string>* target_url_patterns,
    281     std::string* error) {
    282   if (document_url_patterns) {
    283     if (!document_url_patterns_.Populate(
    284             *document_url_patterns, URLPattern::SCHEME_ALL, true, error)) {
    285       return false;
    286     }
    287   }
    288   if (target_url_patterns) {
    289     if (!target_url_patterns_.Populate(
    290             *target_url_patterns, URLPattern::SCHEME_ALL, true, error)) {
    291       return false;
    292     }
    293   }
    294   return true;
    295 }
    296 
    297 MenuManager::MenuManager(Profile* profile, StateStore* store)
    298     : profile_(profile), store_(store) {
    299   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED,
    300                  content::Source<Profile>(profile));
    301   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
    302                  content::Source<Profile>(profile));
    303   registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED,
    304                  content::NotificationService::AllSources());
    305   if (store_)
    306     store_->RegisterKey(kContextMenusKey);
    307 }
    308 
    309 MenuManager::~MenuManager() {
    310   MenuItemMap::iterator i;
    311   for (i = context_items_.begin(); i != context_items_.end(); ++i) {
    312     STLDeleteElements(&(i->second));
    313   }
    314 }
    315 
    316 // static
    317 MenuManager* MenuManager::Get(Profile* profile) {
    318   return MenuManagerFactory::GetForProfile(profile);
    319 }
    320 
    321 std::set<std::string> MenuManager::ExtensionIds() {
    322   std::set<std::string> id_set;
    323   for (MenuItemMap::const_iterator i = context_items_.begin();
    324        i != context_items_.end(); ++i) {
    325     id_set.insert(i->first);
    326   }
    327   return id_set;
    328 }
    329 
    330 const MenuItem::List* MenuManager::MenuItems(
    331     const std::string& extension_id) {
    332   MenuItemMap::iterator i = context_items_.find(extension_id);
    333   if (i != context_items_.end()) {
    334     return &(i->second);
    335   }
    336   return NULL;
    337 }
    338 
    339 bool MenuManager::AddContextItem(
    340     const Extension* extension,
    341     MenuItem* item) {
    342   const std::string& extension_id = item->extension_id();
    343   // The item must have a non-empty extension id, and not have already been
    344   // added.
    345   if (extension_id.empty() || ContainsKey(items_by_id_, item->id()))
    346     return false;
    347 
    348   DCHECK_EQ(extension->id(), extension_id);
    349 
    350   bool first_item = !ContainsKey(context_items_, extension_id);
    351   context_items_[extension_id].push_back(item);
    352   items_by_id_[item->id()] = item;
    353 
    354   if (item->type() == MenuItem::RADIO) {
    355     if (item->checked())
    356       RadioItemSelected(item);
    357     else
    358       SanitizeRadioList(context_items_[extension_id]);
    359   }
    360 
    361   // If this is the first item for this extension, start loading its icon.
    362   if (first_item)
    363     icon_manager_.LoadIcon(profile_, extension);
    364 
    365   return true;
    366 }
    367 
    368 bool MenuManager::AddChildItem(const MenuItem::Id& parent_id,
    369                                MenuItem* child) {
    370   MenuItem* parent = GetItemById(parent_id);
    371   if (!parent || parent->type() != MenuItem::NORMAL ||
    372       parent->incognito() != child->incognito() ||
    373       parent->extension_id() != child->extension_id() ||
    374       ContainsKey(items_by_id_, child->id()))
    375     return false;
    376   parent->AddChild(child);
    377   items_by_id_[child->id()] = child;
    378 
    379   if (child->type() == MenuItem::RADIO)
    380     SanitizeRadioList(parent->children());
    381   return true;
    382 }
    383 
    384 bool MenuManager::DescendantOf(MenuItem* item,
    385                                const MenuItem::Id& ancestor_id) {
    386   // Work our way up the tree until we find the ancestor or NULL.
    387   MenuItem::Id* id = item->parent_id();
    388   while (id != NULL) {
    389     DCHECK(*id != item->id());  // Catch circular graphs.
    390     if (*id == ancestor_id)
    391       return true;
    392     MenuItem* next = GetItemById(*id);
    393     if (!next) {
    394       NOTREACHED();
    395       return false;
    396     }
    397     id = next->parent_id();
    398   }
    399   return false;
    400 }
    401 
    402 bool MenuManager::ChangeParent(const MenuItem::Id& child_id,
    403                                const MenuItem::Id* parent_id) {
    404   MenuItem* child = GetItemById(child_id);
    405   MenuItem* new_parent = parent_id ? GetItemById(*parent_id) : NULL;
    406   if ((parent_id && (child_id == *parent_id)) || !child ||
    407       (!new_parent && parent_id != NULL) ||
    408       (new_parent && (DescendantOf(new_parent, child_id) ||
    409                       child->incognito() != new_parent->incognito() ||
    410                       child->extension_id() != new_parent->extension_id())))
    411     return false;
    412 
    413   MenuItem::Id* old_parent_id = child->parent_id();
    414   if (old_parent_id != NULL) {
    415     MenuItem* old_parent = GetItemById(*old_parent_id);
    416     if (!old_parent) {
    417       NOTREACHED();
    418       return false;
    419     }
    420     MenuItem* taken =
    421       old_parent->ReleaseChild(child_id, false /* non-recursive search*/);
    422     DCHECK(taken == child);
    423     SanitizeRadioList(old_parent->children());
    424   } else {
    425     // This is a top-level item, so we need to pull it out of our list of
    426     // top-level items.
    427     MenuItemMap::iterator i = context_items_.find(child->extension_id());
    428     if (i == context_items_.end()) {
    429       NOTREACHED();
    430       return false;
    431     }
    432     MenuItem::List& list = i->second;
    433     MenuItem::List::iterator j = std::find(list.begin(), list.end(),
    434                                                     child);
    435     if (j == list.end()) {
    436       NOTREACHED();
    437       return false;
    438     }
    439     list.erase(j);
    440     SanitizeRadioList(list);
    441   }
    442 
    443   if (new_parent) {
    444     new_parent->AddChild(child);
    445     SanitizeRadioList(new_parent->children());
    446   } else {
    447     context_items_[child->extension_id()].push_back(child);
    448     child->parent_id_.reset(NULL);
    449     SanitizeRadioList(context_items_[child->extension_id()]);
    450   }
    451   return true;
    452 }
    453 
    454 bool MenuManager::RemoveContextMenuItem(const MenuItem::Id& id) {
    455   if (!ContainsKey(items_by_id_, id))
    456     return false;
    457 
    458   MenuItem* menu_item = GetItemById(id);
    459   DCHECK(menu_item);
    460   std::string extension_id = menu_item->extension_id();
    461   MenuItemMap::iterator i = context_items_.find(extension_id);
    462   if (i == context_items_.end()) {
    463     NOTREACHED();
    464     return false;
    465   }
    466 
    467   bool result = false;
    468   std::set<MenuItem::Id> items_removed;
    469   MenuItem::List& list = i->second;
    470   MenuItem::List::iterator j;
    471   for (j = list.begin(); j < list.end(); ++j) {
    472     // See if the current top-level item is a match.
    473     if ((*j)->id() == id) {
    474       items_removed = (*j)->RemoveAllDescendants();
    475       items_removed.insert(id);
    476       delete *j;
    477       list.erase(j);
    478       result = true;
    479       SanitizeRadioList(list);
    480       break;
    481     } else {
    482       // See if the item to remove was found as a descendant of the current
    483       // top-level item.
    484       MenuItem* child = (*j)->ReleaseChild(id, true /* recursive */);
    485       if (child) {
    486         items_removed = child->RemoveAllDescendants();
    487         items_removed.insert(id);
    488         SanitizeRadioList(GetItemById(*child->parent_id())->children());
    489         delete child;
    490         result = true;
    491         break;
    492       }
    493     }
    494   }
    495   DCHECK(result);  // The check at the very top should have prevented this.
    496 
    497   // Clear entries from the items_by_id_ map.
    498   std::set<MenuItem::Id>::iterator removed_iter;
    499   for (removed_iter = items_removed.begin();
    500        removed_iter != items_removed.end();
    501        ++removed_iter) {
    502     items_by_id_.erase(*removed_iter);
    503   }
    504 
    505   if (list.empty()) {
    506     context_items_.erase(extension_id);
    507     icon_manager_.RemoveIcon(extension_id);
    508   }
    509   return result;
    510 }
    511 
    512 void MenuManager::RemoveAllContextItems(const std::string& extension_id) {
    513   MenuItem::List::iterator i;
    514   for (i = context_items_[extension_id].begin();
    515        i != context_items_[extension_id].end(); ++i) {
    516     MenuItem* item = *i;
    517     items_by_id_.erase(item->id());
    518 
    519     // Remove descendants from this item and erase them from the lookup cache.
    520     std::set<MenuItem::Id> removed_ids = item->RemoveAllDescendants();
    521     std::set<MenuItem::Id>::const_iterator j;
    522     for (j = removed_ids.begin(); j != removed_ids.end(); ++j) {
    523       items_by_id_.erase(*j);
    524     }
    525   }
    526   STLDeleteElements(&context_items_[extension_id]);
    527   context_items_.erase(extension_id);
    528   icon_manager_.RemoveIcon(extension_id);
    529 }
    530 
    531 MenuItem* MenuManager::GetItemById(const MenuItem::Id& id) const {
    532   std::map<MenuItem::Id, MenuItem*>::const_iterator i =
    533       items_by_id_.find(id);
    534   if (i != items_by_id_.end())
    535     return i->second;
    536   else
    537     return NULL;
    538 }
    539 
    540 void MenuManager::RadioItemSelected(MenuItem* item) {
    541   // If this is a child item, we need to get a handle to the list from its
    542   // parent. Otherwise get a handle to the top-level list.
    543   const MenuItem::List* list = NULL;
    544   if (item->parent_id()) {
    545     MenuItem* parent = GetItemById(*item->parent_id());
    546     if (!parent) {
    547       NOTREACHED();
    548       return;
    549     }
    550     list = &(parent->children());
    551   } else {
    552     if (context_items_.find(item->extension_id()) == context_items_.end()) {
    553       NOTREACHED();
    554       return;
    555     }
    556     list = &context_items_[item->extension_id()];
    557   }
    558 
    559   // Find where |item| is in the list.
    560   MenuItem::List::const_iterator item_location;
    561   for (item_location = list->begin(); item_location != list->end();
    562        ++item_location) {
    563     if (*item_location == item)
    564       break;
    565   }
    566   if (item_location == list->end()) {
    567     NOTREACHED();  // We should have found the item.
    568     return;
    569   }
    570 
    571   // Iterate backwards from |item| and uncheck any adjacent radio items.
    572   MenuItem::List::const_iterator i;
    573   if (item_location != list->begin()) {
    574     i = item_location;
    575     do {
    576       --i;
    577       if ((*i)->type() != MenuItem::RADIO)
    578         break;
    579       (*i)->SetChecked(false);
    580     } while (i != list->begin());
    581   }
    582 
    583   // Now iterate forwards from |item| and uncheck any adjacent radio items.
    584   for (i = item_location + 1; i != list->end(); ++i) {
    585     if ((*i)->type() != MenuItem::RADIO)
    586       break;
    587     (*i)->SetChecked(false);
    588   }
    589 }
    590 
    591 static void AddURLProperty(DictionaryValue* dictionary,
    592                            const std::string& key, const GURL& url) {
    593   if (!url.is_empty())
    594     dictionary->SetString(key, url.possibly_invalid_spec());
    595 }
    596 
    597 void MenuManager::ExecuteCommand(Profile* profile,
    598                                  WebContents* web_contents,
    599                                  const content::ContextMenuParams& params,
    600                                  const MenuItem::Id& menu_item_id) {
    601   EventRouter* event_router = extensions::ExtensionSystem::Get(profile)->
    602       event_router();
    603   if (!event_router)
    604     return;
    605 
    606   MenuItem* item = GetItemById(menu_item_id);
    607   if (!item)
    608     return;
    609 
    610   // ExtensionService/Extension can be NULL in unit tests :(
    611   ExtensionService* service =
    612       ExtensionSystem::Get(profile_)->extension_service();
    613   const Extension* extension = service ?
    614       service->extensions()->GetByID(menu_item_id.extension_id) : NULL;
    615 
    616   if (item->type() == MenuItem::RADIO)
    617     RadioItemSelected(item);
    618 
    619   scoped_ptr<base::ListValue> args(new base::ListValue());
    620 
    621   DictionaryValue* properties = new DictionaryValue();
    622   SetIdKeyValue(properties, "menuItemId", item->id());
    623   if (item->parent_id())
    624     SetIdKeyValue(properties, "parentMenuItemId", *item->parent_id());
    625 
    626   switch (params.media_type) {
    627     case blink::WebContextMenuData::MediaTypeImage:
    628       properties->SetString("mediaType", "image");
    629       break;
    630     case blink::WebContextMenuData::MediaTypeVideo:
    631       properties->SetString("mediaType", "video");
    632       break;
    633     case blink::WebContextMenuData::MediaTypeAudio:
    634       properties->SetString("mediaType", "audio");
    635       break;
    636     default:  {}  // Do nothing.
    637   }
    638 
    639   AddURLProperty(properties, "linkUrl", params.unfiltered_link_url);
    640   AddURLProperty(properties, "srcUrl", params.src_url);
    641   AddURLProperty(properties, "pageUrl", params.page_url);
    642   AddURLProperty(properties, "frameUrl", params.frame_url);
    643 
    644   if (params.selection_text.length() > 0)
    645     properties->SetString("selectionText", params.selection_text);
    646 
    647   properties->SetBoolean("editable", params.is_editable);
    648 
    649   args->Append(properties);
    650 
    651   // Add the tab info to the argument list.
    652   // No tab info in a platform app.
    653   if (!extension || !extension->is_platform_app()) {
    654     // Note: web_contents are NULL in unit tests :(
    655     if (web_contents) {
    656       args->Append(ExtensionTabUtil::CreateTabValue(web_contents));
    657     } else {
    658       args->Append(new DictionaryValue());
    659     }
    660   }
    661 
    662   if (item->type() == MenuItem::CHECKBOX ||
    663       item->type() == MenuItem::RADIO) {
    664     bool was_checked = item->checked();
    665     properties->SetBoolean("wasChecked", was_checked);
    666 
    667     // RADIO items always get set to true when you click on them, but CHECKBOX
    668     // items get their state toggled.
    669     bool checked =
    670         (item->type() == MenuItem::RADIO) ? true : !was_checked;
    671 
    672     item->SetChecked(checked);
    673     properties->SetBoolean("checked", item->checked());
    674 
    675     if (extension)
    676       WriteToStorage(extension);
    677   }
    678 
    679   // Note: web_contents are NULL in unit tests :(
    680   if (web_contents && extensions::TabHelper::FromWebContents(web_contents)) {
    681     extensions::TabHelper::FromWebContents(web_contents)->
    682         active_tab_permission_granter()->GrantIfRequested(extension);
    683   }
    684 
    685   {
    686     scoped_ptr<Event> event(new Event(
    687         event_names::kOnContextMenus,
    688         scoped_ptr<base::ListValue>(args->DeepCopy())));
    689     event->restrict_to_browser_context = profile;
    690     event->user_gesture = EventRouter::USER_GESTURE_ENABLED;
    691     event_router->DispatchEventToExtension(item->extension_id(), event.Pass());
    692   }
    693   {
    694     scoped_ptr<Event> event(new Event(context_menus::OnClicked::kEventName,
    695                                       args.Pass()));
    696     event->restrict_to_browser_context = profile;
    697     event->user_gesture = EventRouter::USER_GESTURE_ENABLED;
    698     event_router->DispatchEventToExtension(item->extension_id(), event.Pass());
    699   }
    700 }
    701 
    702 void MenuManager::SanitizeRadioList(const MenuItem::List& item_list) {
    703   MenuItem::List::const_iterator i = item_list.begin();
    704   while (i != item_list.end()) {
    705     if ((*i)->type() != MenuItem::RADIO) {
    706       ++i;
    707       break;
    708     }
    709 
    710     // Uncheck any checked radio items in the run, and at the end reset
    711     // the appropriate one to checked. If no check radio items were found,
    712     // then check the first radio item in the run.
    713     MenuItem::List::const_iterator last_checked = item_list.end();
    714     MenuItem::List::const_iterator radio_run_iter;
    715     for (radio_run_iter = i; radio_run_iter != item_list.end();
    716         ++radio_run_iter) {
    717       if ((*radio_run_iter)->type() != MenuItem::RADIO) {
    718         break;
    719       }
    720 
    721       if ((*radio_run_iter)->checked()) {
    722         last_checked = radio_run_iter;
    723         (*radio_run_iter)->SetChecked(false);
    724       }
    725     }
    726 
    727     if (last_checked != item_list.end())
    728       (*last_checked)->SetChecked(true);
    729     else
    730       (*i)->SetChecked(true);
    731 
    732     i = radio_run_iter;
    733   }
    734 }
    735 
    736 bool MenuManager::ItemUpdated(const MenuItem::Id& id) {
    737   if (!ContainsKey(items_by_id_, id))
    738     return false;
    739 
    740   MenuItem* menu_item = GetItemById(id);
    741   DCHECK(menu_item);
    742 
    743   if (menu_item->parent_id()) {
    744     SanitizeRadioList(GetItemById(*menu_item->parent_id())->children());
    745   } else {
    746     std::string extension_id = menu_item->extension_id();
    747     MenuItemMap::iterator i = context_items_.find(extension_id);
    748     if (i == context_items_.end()) {
    749       NOTREACHED();
    750       return false;
    751     }
    752     SanitizeRadioList(i->second);
    753   }
    754 
    755   return true;
    756 }
    757 
    758 void MenuManager::WriteToStorage(const Extension* extension) {
    759   if (!BackgroundInfo::HasLazyBackgroundPage(extension))
    760     return;
    761   const MenuItem::List* top_items = MenuItems(extension->id());
    762   MenuItem::List all_items;
    763   if (top_items) {
    764     for (MenuItem::List::const_iterator i = top_items->begin();
    765          i != top_items->end(); ++i) {
    766       (*i)->GetFlattenedSubtree(&all_items);
    767     }
    768   }
    769 
    770   if (store_)
    771     store_->SetExtensionValue(extension->id(), kContextMenusKey,
    772                               MenuItemsToValue(all_items));
    773 }
    774 
    775 void MenuManager::ReadFromStorage(const std::string& extension_id,
    776                                   scoped_ptr<base::Value> value) {
    777   const Extension* extension =
    778       ExtensionSystem::Get(profile_)->extension_service()->extensions()->
    779           GetByID(extension_id);
    780   if (!extension)
    781     return;
    782 
    783   MenuItem::List items = MenuItemsFromValue(extension_id, value.get());
    784   for (size_t i = 0; i < items.size(); ++i) {
    785     if (items[i]->parent_id()) {
    786       // Parent IDs are stored in the parent_id field for convenience, but
    787       // they have not yet been validated. Separate them out here.
    788       // Because of the order in which we store items in the prefs, parents will
    789       // precede children, so we should already know about any parent items.
    790       scoped_ptr<MenuItem::Id> parent_id;
    791       parent_id.swap(items[i]->parent_id_);
    792       AddChildItem(*parent_id, items[i]);
    793     } else {
    794       AddContextItem(extension, items[i]);
    795     }
    796   }
    797 }
    798 
    799 void MenuManager::Observe(int type,
    800                           const content::NotificationSource& source,
    801                           const content::NotificationDetails& details) {
    802   switch (type) {
    803     case chrome::NOTIFICATION_EXTENSION_UNLOADED: {
    804       // Remove menu items for disabled/uninstalled extensions.
    805       const Extension* extension =
    806           content::Details<UnloadedExtensionInfo>(details)->extension;
    807       if (ContainsKey(context_items_, extension->id())) {
    808         RemoveAllContextItems(extension->id());
    809       }
    810       break;
    811     }
    812     case chrome::NOTIFICATION_EXTENSION_LOADED: {
    813       const Extension* extension =
    814           content::Details<const Extension>(details).ptr();
    815       if (store_ && BackgroundInfo::HasLazyBackgroundPage(extension)) {
    816         store_->GetExtensionValue(extension->id(), kContextMenusKey,
    817             base::Bind(&MenuManager::ReadFromStorage,
    818                        AsWeakPtr(), extension->id()));
    819       }
    820       break;
    821     }
    822     case chrome::NOTIFICATION_PROFILE_DESTROYED: {
    823       Profile* profile = content::Source<Profile>(source).ptr();
    824       // We cannot use profile_->HasOffTheRecordProfile as it may already be
    825       // false at this point, if for example the incognito profile was destroyed
    826       // using DestroyOffTheRecordProfile.
    827       if (profile->GetOriginalProfile() == profile_ &&
    828           profile->GetOriginalProfile() != profile) {
    829         RemoveAllIncognitoContextItems();
    830       }
    831       break;
    832     }
    833     default:
    834       NOTREACHED();
    835       break;
    836   }
    837 }
    838 
    839 const SkBitmap& MenuManager::GetIconForExtension(
    840     const std::string& extension_id) {
    841   return icon_manager_.GetIcon(extension_id);
    842 }
    843 
    844 void MenuManager::RemoveAllIncognitoContextItems() {
    845   // Get all context menu items with "incognito" set to "split".
    846   std::set<MenuItem::Id> items_to_remove;
    847   std::map<MenuItem::Id, MenuItem*>::const_iterator iter;
    848   for (iter = items_by_id_.begin();
    849        iter != items_by_id_.end();
    850        ++iter) {
    851     if (iter->first.incognito)
    852       items_to_remove.insert(iter->first);
    853   }
    854 
    855   std::set<MenuItem::Id>::iterator remove_iter;
    856   for (remove_iter = items_to_remove.begin();
    857        remove_iter != items_to_remove.end();
    858        ++remove_iter)
    859     RemoveContextMenuItem(*remove_iter);
    860 }
    861 
    862 MenuItem::Id::Id() : incognito(false), uid(0) {}
    863 
    864 MenuItem::Id::Id(bool incognito, const std::string& extension_id)
    865     : incognito(incognito), extension_id(extension_id), uid(0) {}
    866 
    867 MenuItem::Id::~Id() {
    868 }
    869 
    870 bool MenuItem::Id::operator==(const Id& other) const {
    871   return (incognito == other.incognito &&
    872           extension_id == other.extension_id &&
    873           uid == other.uid &&
    874           string_uid == other.string_uid);
    875 }
    876 
    877 bool MenuItem::Id::operator!=(const Id& other) const {
    878   return !(*this == other);
    879 }
    880 
    881 bool MenuItem::Id::operator<(const Id& other) const {
    882   if (incognito < other.incognito)
    883     return true;
    884   if (incognito == other.incognito) {
    885     if (extension_id < other.extension_id)
    886       return true;
    887     if (extension_id == other.extension_id) {
    888       if (uid < other.uid)
    889         return true;
    890       if (uid == other.uid)
    891         return string_uid < other.string_uid;
    892     }
    893   }
    894   return false;
    895 }
    896 
    897 }  // namespace extensions
    898