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