Upstream version 11.40.277.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/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