Make default apps cache multiprofile friendly
[chromium-blink-merge.git] / chrome / browser / extensions / menu_manager.cc
blobd1ef97589b3b48f15dc660e8c11c09a1774490d5
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.
5 #include "chrome/browser/extensions/menu_manager.h"
7 #include <algorithm>
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"
34 using content::WebContents;
35 using extensions::ExtensionSystem;
37 namespace extensions {
39 namespace context_menus = api::context_menus;
41 namespace {
43 // Keys for serialization to and from Value to store in the preferences.
44 const char kContextMenusKey[] = "context_menus";
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";
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);
66 MenuItem::List MenuItemsFromValue(const std::string& extension_id,
67 base::Value* value) {
68 MenuItem::List items;
70 base::ListValue* list = NULL;
71 if (!value || !value->GetAsList(&list))
72 return items;
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);
84 return items;
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());
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;
100 const base::ListValue* list = NULL;
101 if (!dict.GetListWithoutPathExpansion(key, &list))
102 return false;
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);
111 return true;
114 } // namespace
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) {}
129 MenuItem::~MenuItem() {
130 STLDeleteElements(&children_);
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;
147 return NULL;
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);
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());
164 STLDeleteElements(&children_);
165 return result;
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);
175 if (result.length() > max_length)
176 result = gfx::TruncateString(result, max_length);
177 return result;
180 bool MenuItem::SetChecked(bool checked) {
181 if (type_ != CHECKBOX && type_ != RADIO)
182 return false;
183 checked_ = checked;
184 return true;
187 void MenuItem::AddChild(MenuItem* item) {
188 item->parent_id_.reset(new Id(id_));
189 children_.push_back(item);
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);
210 value->Set(kDocumentURLPatternsKey,
211 document_url_patterns_.ToValue().release());
212 value->Set(kTargetURLPatternsKey, target_url_patterns_.ToValue().release());
213 return value.Pass();
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;
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;
249 scoped_ptr<MenuItem> result(new MenuItem(
250 id, title, checked, enabled, type, contexts));
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;
259 if (!result->PopulateURLPatterns(&document_url_patterns,
260 &target_url_patterns,
261 error)) {
262 return NULL;
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);
273 return result.release();
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;
286 if (target_url_patterns) {
287 if (!target_url_patterns_.Populate(
288 *target_url_patterns, URLPattern::SCHEME_ALL, true, error)) {
289 return false;
292 return true;
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));
302 StateStore* store = ExtensionSystem::Get(profile_)->state_store();
303 if (store)
304 store->RegisterKey(kContextMenusKey);
307 MenuManager::~MenuManager() {
308 MenuItemMap::iterator i;
309 for (i = context_items_.begin(); i != context_items_.end(); ++i) {
310 STLDeleteElements(&(i->second));
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);
320 return id_set;
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);
329 return NULL;
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;
341 DCHECK_EQ(extension->id(), extension_id);
343 bool first_item = !ContainsKey(context_items_, extension_id);
344 context_items_[extension_id].push_back(item);
345 items_by_id_[item->id()] = item;
347 if (item->type() == MenuItem::RADIO) {
348 if (item->checked())
349 RadioItemSelected(item);
350 else
351 SanitizeRadioList(context_items_[extension_id]);
354 // If this is the first item for this extension, start loading its icon.
355 if (first_item)
356 icon_manager_.LoadIcon(profile_, extension);
358 return true;
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;
372 if (child->type() == MenuItem::RADIO)
373 SanitizeRadioList(parent->children());
374 return true;
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;
390 id = next->parent_id();
392 return false;
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;
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;
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;
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;
432 list.erase(j);
433 SanitizeRadioList(list);
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()]);
444 return true;
447 bool MenuManager::RemoveContextMenuItem(const MenuItem::Id& id) {
448 if (!ContainsKey(items_by_id_, id))
449 return false;
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;
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;
488 DCHECK(result); // The check at the very top should have prevented this.
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);
498 if (list.empty()) {
499 context_items_.erase(extension_id);
500 icon_manager_.RemoveIcon(extension_id);
502 return result;
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());
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);
519 STLDeleteElements(&context_items_[extension_id]);
520 context_items_.erase(extension_id);
521 icon_manager_.RemoveIcon(extension_id);
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;
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;
543 list = &(parent->children());
544 } else {
545 if (context_items_.find(item->extension_id()) == context_items_.end()) {
546 NOTREACHED();
547 return;
549 list = &context_items_[item->extension_id()];
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;
559 if (item_location == list->end()) {
560 NOTREACHED(); // We should have found the item.
561 return;
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());
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);
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());
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;
599 MenuItem* item = GetItemById(menu_item_id);
600 if (!item)
601 return;
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;
609 if (item->type() == MenuItem::RADIO)
610 RadioItemSelected(item);
612 scoped_ptr<base::ListValue> args(new base::ListValue());
614 DictionaryValue* properties = new DictionaryValue();
615 SetIdKeyValue(properties, "menuItemId", item->id());
616 if (item->parent_id())
617 SetIdKeyValue(properties, "parentMenuItemId", *item->parent_id());
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.
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);
637 if (params.selection_text.length() > 0)
638 properties->SetString("selectionText", params.selection_text);
640 properties->SetBoolean("editable", params.is_editable);
642 args->Append(properties);
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());
655 if (item->type() == MenuItem::CHECKBOX ||
656 item->type() == MenuItem::RADIO) {
657 bool was_checked = item->checked();
658 properties->SetBoolean("wasChecked", was_checked);
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;
665 item->SetChecked(checked);
666 properties->SetBoolean("checked", item->checked());
668 if (extension)
669 WriteToStorage(extension);
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);
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());
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());
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;
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;
714 if ((*radio_run_iter)->checked()) {
715 last_checked = radio_run_iter;
716 (*radio_run_iter)->SetChecked(false);
720 if (last_checked != item_list.end())
721 (*last_checked)->SetChecked(true);
722 else
723 (*i)->SetChecked(true);
725 i = radio_run_iter;
729 bool MenuManager::ItemUpdated(const MenuItem::Id& id) {
730 if (!ContainsKey(items_by_id_, id))
731 return false;
733 MenuItem* menu_item = GetItemById(id);
734 DCHECK(menu_item);
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;
745 SanitizeRadioList(i->second);
748 return true;
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);
763 StateStore* store = ExtensionSystem::Get(profile_)->state_store();
764 if (store)
765 store->SetExtensionValue(extension->id(), kContextMenusKey,
766 MenuItemsToValue(all_items));
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;
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]);
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());
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()));
815 const SkBitmap& MenuManager::GetIconForExtension(
816 const std::string& extension_id) {
817 return icon_manager_.GetIcon(extension_id);
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);
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);
838 MenuItem::Id::Id() : incognito(false), uid(0) {}
840 MenuItem::Id::Id(bool incognito, const std::string& extension_id)
841 : incognito(incognito), extension_id(extension_id), uid(0) {}
843 MenuItem::Id::~Id() {
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);
853 bool MenuItem::Id::operator!=(const Id& other) const {
854 return !(*this == other);
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;
870 return false;
873 } // namespace extensions