Upstream version 5.34.104.0
[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/extension_service.h"
18 #include "chrome/browser/extensions/extension_tab_util.h"
19 #include "chrome/browser/extensions/menu_manager_factory.h"
20 #include "chrome/browser/extensions/state_store.h"
21 #include "chrome/browser/extensions/tab_helper.h"
22 #include "chrome/browser/profiles/profile.h"
23 #include "chrome/common/extensions/api/context_menus.h"
24 #include "content/public/browser/notification_details.h"
25 #include "content/public/browser/notification_service.h"
26 #include "content/public/browser/notification_source.h"
27 #include "content/public/browser/web_contents.h"
28 #include "content/public/common/context_menu_params.h"
29 #include "extensions/browser/event_router.h"
30 #include "extensions/browser/extension_system.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<base::Value>(list.release());
94 }
95
96 bool GetStringList(const base::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 = base::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, base::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<base::DictionaryValue> MenuItem::ToValue() const {
195   scoped_ptr<base::DictionaryValue> value(new base::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 base::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 base::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(base::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   base::DictionaryValue* properties = new base::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 base::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