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"
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
;
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
,
59 const MenuItem::Id
& id
) {
61 properties
->SetString(key
, id
.string_uid
);
63 properties
->SetInteger(key
, id
.uid
);
66 MenuItem::List
MenuItemsFromValue(const std::string
& extension_id
,
70 base::ListValue
* list
= NULL
;
71 if (!value
|| !value
->GetAsList(&list
))
74 for (size_t i
= 0; i
< list
->GetSize(); ++i
) {
75 base::DictionaryValue
* dict
= NULL
;
76 if (!list
->GetDictionary(i
, &dict
))
78 MenuItem
* item
= MenuItem::Populate(
79 extension_id
, *dict
, NULL
);
82 items
.push_back(item
);
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
))
100 const base::ListValue
* list
= NULL
;
101 if (!dict
.GetListWithoutPathExpansion(key
, &list
))
104 for (size_t i
= 0; i
< list
->GetSize(); ++i
) {
106 if (!list
->GetString(i
, &pattern
))
108 out
->push_back(pattern
);
116 MenuItem::MenuItem(const Id
& id
,
117 const std::string
& title
,
121 const ContextList
& contexts
)
127 contexts_(contexts
) {}
129 MenuItem::~MenuItem() {
130 STLDeleteElements(&children_
);
133 MenuItem
* MenuItem::ReleaseChild(const Id
& child_id
,
135 for (List::iterator i
= children_
.begin(); i
!= children_
.end(); ++i
) {
136 MenuItem
* child
= NULL
;
137 if ((*i
)->id() == child_id
) {
141 } else if (recursive
) {
142 child
= (*i
)->ReleaseChild(child_id
, recursive
);
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() {
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_
);
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
);
180 bool MenuItem::SetChecked(bool checked
) {
181 if (type_
!= CHECKBOX
&& type_
!= RADIO
)
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());
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());
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
))
223 Id
id(incognito
, extension_id
);
224 if (!value
.GetString(kStringUIDKey
, &id
.string_uid
))
228 if (!value
.GetInteger(kTypeKey
, &type_int
))
230 type
= static_cast<Type
>(type_int
);
232 if (type
!= SEPARATOR
&& !value
.GetString(kTitleKey
, &title
))
234 bool checked
= false;
235 if ((type
== CHECKBOX
|| type
== RADIO
) &&
236 !value
.GetBoolean(kCheckedKey
, &checked
)) {
240 if (!value
.GetBoolean(kEnabledKey
, &enabled
))
242 ContextList contexts
;
243 const Value
* contexts_value
= NULL
;
244 if (!value
.Get(kContextsKey
, &contexts_value
))
246 if (!contexts
.Populate(*contexts_value
))
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
))
255 std::vector
<std::string
> target_url_patterns
;
256 if (!GetStringList(value
, kTargetURLPatternsKey
, &target_url_patterns
))
259 if (!result
->PopulateURLPatterns(&document_url_patterns
,
260 &target_url_patterns
,
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
))
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
)) {
286 if (target_url_patterns
) {
287 if (!target_url_patterns_
.Populate(
288 *target_url_patterns
, URLPattern::SCHEME_ALL
, true, error
)) {
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();
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
);
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()) {
332 bool MenuManager::AddContextItem(
333 const Extension
* extension
,
335 const std::string
& extension_id
= item
->extension_id();
336 // The item must have a non-empty extension id, and not have already been
338 if (extension_id
.empty() || ContainsKey(items_by_id_
, item
->id()))
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
) {
349 RadioItemSelected(item
);
351 SanitizeRadioList(context_items_
[extension_id
]);
354 // If this is the first item for this extension, start loading its icon.
356 icon_manager_
.LoadIcon(profile_
, extension
);
361 bool MenuManager::AddChildItem(const MenuItem::Id
& parent_id
,
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()))
369 parent
->AddChild(child
);
370 items_by_id_
[child
->id()] = child
;
372 if (child
->type() == MenuItem::RADIO
)
373 SanitizeRadioList(parent
->children());
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();
382 DCHECK(*id
!= item
->id()); // Catch circular graphs.
383 if (*id
== ancestor_id
)
385 MenuItem
* next
= GetItemById(*id
);
390 id
= next
->parent_id();
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())))
406 MenuItem::Id
* old_parent_id
= child
->parent_id();
407 if (old_parent_id
!= NULL
) {
408 MenuItem
* old_parent
= GetItemById(*old_parent_id
);
414 old_parent
->ReleaseChild(child_id
, false /* non-recursive search*/);
415 DCHECK(taken
== child
);
416 SanitizeRadioList(old_parent
->children());
418 // This is a top-level item, so we need to pull it out of our list of
420 MenuItemMap::iterator i
= context_items_
.find(child
->extension_id());
421 if (i
== context_items_
.end()) {
425 MenuItem::List
& list
= i
->second
;
426 MenuItem::List::iterator j
= std::find(list
.begin(), list
.end(),
428 if (j
== list
.end()) {
433 SanitizeRadioList(list
);
437 new_parent
->AddChild(child
);
438 SanitizeRadioList(new_parent
->children());
440 context_items_
[child
->extension_id()].push_back(child
);
441 child
->parent_id_
.reset(NULL
);
442 SanitizeRadioList(context_items_
[child
->extension_id()]);
447 bool MenuManager::RemoveContextMenuItem(const MenuItem::Id
& id
) {
448 if (!ContainsKey(items_by_id_
, id
))
451 MenuItem
* menu_item
= GetItemById(id
);
453 std::string extension_id
= menu_item
->extension_id();
454 MenuItemMap::iterator i
= context_items_
.find(extension_id
);
455 if (i
== context_items_
.end()) {
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
);
472 SanitizeRadioList(list
);
475 // See if the item to remove was found as a descendant of the current
477 MenuItem
* child
= (*j
)->ReleaseChild(id
, true /* recursive */);
479 items_removed
= child
->RemoveAllDescendants();
480 items_removed
.insert(id
);
481 SanitizeRadioList(GetItemById(*child
->parent_id())->children());
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();
495 items_by_id_
.erase(*removed_iter
);
499 context_items_
.erase(extension_id
);
500 icon_manager_
.RemoveIcon(extension_id
);
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
) {
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())
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());
543 list
= &(parent
->children());
545 if (context_items_
.find(item
->extension_id()) == context_items_
.end()) {
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();
556 if (*item_location
== item
)
559 if (item_location
== list
->end()) {
560 NOTREACHED(); // We should have found the item.
564 // Iterate backwards from |item| and uncheck any adjacent radio items.
565 MenuItem::List::const_iterator i
;
566 if (item_location
!= list
->begin()) {
570 if ((*i
)->type() != MenuItem::RADIO
)
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
)
580 (*i
)->SetChecked(false);
584 static void AddURLProperty(DictionaryValue
* dictionary
,
585 const std::string
& key
, const GURL
& url
) {
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
)->
599 MenuItem
* item
= GetItemById(menu_item_id
);
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");
623 case WebKit::WebContextMenuData::MediaTypeVideo
:
624 properties
->SetString("mediaType", "video");
626 case WebKit::WebContextMenuData::MediaTypeAudio
:
627 properties
->SetString("mediaType", "audio");
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 :(
649 args
->Append(ExtensionTabUtil::CreateTabValue(web_contents
));
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.
663 (item
->type() == MenuItem::RADIO
) ? true : !was_checked
;
665 item
->SetChecked(checked
);
666 properties
->SetBoolean("checked", item
->checked());
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
,
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
) {
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();
710 if ((*radio_run_iter
)->type() != MenuItem::RADIO
) {
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);
723 (*i
)->SetChecked(true);
729 bool MenuManager::ItemUpdated(const MenuItem::Id
& id
) {
730 if (!ContainsKey(items_by_id_
, id
))
733 MenuItem
* menu_item
= GetItemById(id
);
736 if (menu_item
->parent_id()) {
737 SanitizeRadioList(GetItemById(*menu_item
->parent_id())->children());
739 std::string extension_id
= menu_item
->extension_id();
740 MenuItemMap::iterator i
= context_items_
.find(extension_id
);
741 if (i
== context_items_
.end()) {
745 SanitizeRadioList(i
->second
);
751 void MenuManager::WriteToStorage(const Extension
* extension
) {
752 if (!BackgroundInfo::HasLazyBackgroundPage(extension
))
754 const MenuItem::List
* top_items
= MenuItems(extension
->id());
755 MenuItem::List all_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();
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
);
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
]);
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();
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();
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
&&
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
)
860 if (incognito
== other
.incognito
) {
861 if (extension_id
< other
.extension_id
)
863 if (extension_id
== other
.extension_id
) {
866 if (uid
== other
.uid
)
867 return string_uid
< other
.string_uid
;
873 } // namespace extensions