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