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/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"
38 using content::WebContents;
39 using extensions::ExtensionSystem;
41 namespace extensions {
43 namespace context_menus = api::context_menus;
44 namespace chrome_web_view = api::chrome_web_view_internal;
48 // Keys for serialization to and from Value to store in the preferences.
49 const char kContextMenusKey[] = "context_menus";
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";
62 void SetIdKeyValue(base::DictionaryValue* properties,
64 const MenuItem::Id& id) {
66 properties->SetString(key, id.string_uid);
68 properties->SetInteger(key, id.uid);
71 MenuItem::List MenuItemsFromValue(const std::string& extension_id,
75 base::ListValue* list = NULL;
76 if (!value || !value->GetAsList(&list))
79 for (size_t i = 0; i < list->GetSize(); ++i) {
80 base::DictionaryValue* dict = NULL;
81 if (!list->GetDictionary(i, &dict))
83 MenuItem* item = MenuItem::Populate(
84 extension_id, *dict, NULL);
87 items.push_back(item);
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());
99 bool GetStringList(const base::DictionaryValue& dict,
100 const std::string& key,
101 std::vector<std::string>* out) {
102 if (!dict.HasKey(key))
105 const base::ListValue* list = NULL;
106 if (!dict.GetListWithoutPathExpansion(key, &list))
109 for (size_t i = 0; i < list->GetSize(); ++i) {
111 if (!list->GetString(i, &pattern))
113 out->push_back(pattern);
121 MenuItem::MenuItem(const Id& id,
122 const std::string& title,
126 const ContextList& contexts)
132 contexts_(contexts) {}
134 MenuItem::~MenuItem() {
135 STLDeleteElements(&children_);
138 MenuItem* MenuItem::ReleaseChild(const Id& child_id,
140 for (List::iterator i = children_.begin(); i != children_.end(); ++i) {
141 MenuItem* child = NULL;
142 if ((*i)->id() == child_id) {
146 } else if (recursive) {
147 child = (*i)->ReleaseChild(child_id, recursive);
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);
161 std::set<MenuItem::Id> MenuItem::RemoveAllDescendants() {
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());
169 STLDeleteElements(&children_);
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);
180 if (result.length() > max_length)
181 result = gfx::TruncateString(result, max_length, gfx::WORD_BREAK);
185 bool MenuItem::SetChecked(bool checked) {
186 if (type_ != CHECKBOX && type_ != RADIO)
192 void MenuItem::AddChild(MenuItem* item) {
193 item->parent_id_.reset(new Id(id_));
194 children_.push_back(item);
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());
212 DCHECK_EQ(0, parent_id_->uid);
213 value->SetString(kParentUIDKey, parent_id_->string_uid);
215 value->Set(kDocumentURLPatternsKey,
216 document_url_patterns_.ToValue().release());
217 value->Set(kTargetURLPatternsKey, target_url_patterns_.ToValue().release());
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))
228 Id id(incognito, MenuItem::ExtensionKey(extension_id));
229 if (!value.GetString(kStringUIDKey, &id.string_uid))
233 if (!value.GetInteger(kTypeKey, &type_int))
235 type = static_cast<Type>(type_int);
237 if (type != SEPARATOR && !value.GetString(kTitleKey, &title))
239 bool checked = false;
240 if ((type == CHECKBOX || type == RADIO) &&
241 !value.GetBoolean(kCheckedKey, &checked)) {
245 if (!value.GetBoolean(kEnabledKey, &enabled))
247 ContextList contexts;
248 const base::Value* contexts_value = NULL;
249 if (!value.Get(kContextsKey, &contexts_value))
251 if (!contexts.Populate(*contexts_value))
254 scoped_ptr<MenuItem> result(new MenuItem(
255 id, title, checked, enabled, type, contexts));
257 std::vector<std::string> document_url_patterns;
258 if (!GetStringList(value, kDocumentURLPatternsKey, &document_url_patterns))
260 std::vector<std::string> target_url_patterns;
261 if (!GetStringList(value, kTargetURLPatternsKey, &target_url_patterns))
264 if (!result->PopulateURLPatterns(&document_url_patterns,
265 &target_url_patterns,
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))
277 result->parent_id_.swap(parent_id);
279 return result.release();
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)) {
292 if (target_url_patterns) {
293 if (!target_url_patterns_.Populate(
294 *target_url_patterns, URLPattern::SCHEME_ALL, true, error)) {
302 const char MenuManager::kOnContextMenus[] = "contextMenus";
303 const char MenuManager::kOnWebviewContextMenus[] =
304 "webViewInternal.contextMenus";
306 MenuManager::MenuManager(content::BrowserContext* context, StateStore* store)
307 : extension_registry_observer_(this),
308 browser_context_(context),
310 extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
311 registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED,
312 content::NotificationService::AllSources());
314 store_->RegisterKey(kContextMenusKey);
317 MenuManager::~MenuManager() {
318 MenuItemMap::iterator i;
319 for (i = context_items_.begin(); i != context_items_.end(); ++i) {
320 STLDeleteElements(&(i->second));
325 MenuManager* MenuManager::Get(content::BrowserContext* context) {
326 return MenuManagerFactory::GetForBrowserContext(context);
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);
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()) {
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
351 if (key.empty() || ContainsKey(items_by_id_, item->id()))
354 DCHECK_EQ(extension->id(), key.extension_id);
356 bool first_item = !ContainsKey(context_items_, key);
357 context_items_[key].push_back(item);
358 items_by_id_[item->id()] = item;
360 if (item->type() == MenuItem::RADIO) {
362 RadioItemSelected(item);
364 SanitizeRadioList(context_items_[key]);
367 // If this is the first item for this extension, start loading its icon.
369 icon_manager_.LoadIcon(browser_context_, extension);
374 bool MenuManager::AddChildItem(const MenuItem::Id& parent_id,
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()))
382 parent->AddChild(child);
383 items_by_id_[child->id()] = child;
385 if (child->type() == MenuItem::RADIO)
386 SanitizeRadioList(parent->children());
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();
395 DCHECK(*id != item->id()); // Catch circular graphs.
396 if (*id == ancestor_id)
398 MenuItem* next = GetItemById(*id);
403 id = next->parent_id();
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())))
419 MenuItem::Id* old_parent_id = child->parent_id();
420 if (old_parent_id != NULL) {
421 MenuItem* old_parent = GetItemById(*old_parent_id);
427 old_parent->ReleaseChild(child_id, false /* non-recursive search*/);
428 DCHECK(taken == child);
429 SanitizeRadioList(old_parent->children());
431 // This is a top-level item, so we need to pull it out of our list of
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()) {
439 MenuItem::List& list = i->second;
440 MenuItem::List::iterator j = std::find(list.begin(), list.end(), child);
441 if (j == list.end()) {
446 SanitizeRadioList(list);
450 new_parent->AddChild(child);
451 SanitizeRadioList(new_parent->children());
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]);
461 bool MenuManager::RemoveContextMenuItem(const MenuItem::Id& id) {
462 if (!ContainsKey(items_by_id_, id))
465 MenuItem* menu_item = GetItemById(id);
467 const MenuItem::ExtensionKey extension_key = id.extension_key;
468 MenuItemMap::iterator i = context_items_.find(extension_key);
469 if (i == context_items_.end()) {
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);
486 SanitizeRadioList(list);
489 // See if the item to remove was found as a descendant of the current
491 MenuItem* child = (*j)->ReleaseChild(id, true /* recursive */);
493 items_removed = child->RemoveAllDescendants();
494 items_removed.insert(id);
495 SanitizeRadioList(GetItemById(*child->parent_id())->children());
502 DCHECK(result); // The check at the very top should have prevented this.
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();
509 items_by_id_.erase(*removed_iter);
513 context_items_.erase(extension_key);
514 icon_manager_.RemoveIcon(extension_key.extension_id);
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();
526 items_by_id_.erase(item->id());
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);
535 STLDeleteElements(&context_items_[extension_key]);
536 context_items_.erase(extension_key);
537 icon_manager_.RemoveIcon(extension_key.extension_id);
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())
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());
559 list = &(parent->children());
561 const MenuItem::ExtensionKey& key = item->id().extension_key;
562 if (context_items_.find(key) == context_items_.end()) {
566 list = &context_items_[key];
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();
573 if (*item_location == item)
576 if (item_location == list->end()) {
577 NOTREACHED(); // We should have found the item.
581 // Iterate backwards from |item| and uncheck any adjacent radio items.
582 MenuItem::List::const_iterator i;
583 if (item_location != list->begin()) {
587 if ((*i)->type() != MenuItem::RADIO)
589 (*i)->SetChecked(false);
590 } while (i != list->begin());
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)
597 (*i)->SetChecked(false);
601 static void AddURLProperty(base::DictionaryValue* dictionary,
602 const std::string& key, const GURL& url) {
604 dictionary->SetString(key, url.possibly_invalid_spec());
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);
615 MenuItem* item = GetItemById(menu_item_id);
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;
625 if (item->type() == MenuItem::RADIO)
626 RadioItemSelected(item);
628 scoped_ptr<base::ListValue> args(new base::ListValue());
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());
635 switch (params.media_type) {
636 case blink::WebContextMenuData::MediaTypeImage:
637 properties->SetString("mediaType", "image");
639 case blink::WebContextMenuData::MediaTypeVideo:
640 properties->SetString("mediaType", "video");
642 case blink::WebContextMenuData::MediaTypeAudio:
643 properties->SetString("mediaType", "audio");
645 default: {} // Do nothing.
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);
653 if (params.selection_text.length() > 0)
654 properties->SetString("selectionText", params.selection_text);
656 properties->SetBoolean("editable", params.is_editable);
658 WebViewGuest* webview_guest = WebViewGuest::FromWebContents(web_contents);
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());
666 args->Append(properties);
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 :(
673 args->Append(ExtensionTabUtil::CreateTabValue(web_contents));
675 args->Append(new base::DictionaryValue());
679 if (item->type() == MenuItem::CHECKBOX ||
680 item->type() == MenuItem::RADIO) {
681 bool was_checked = item->checked();
682 properties->SetBoolean("wasChecked", was_checked);
684 // RADIO items always get set to true when you click on them, but CHECKBOX
685 // items get their state toggled.
687 (item->type() == MenuItem::RADIO) ? true : !was_checked;
689 item->SetChecked(checked);
690 properties->SetBoolean("checked", item->checked());
693 WriteToStorage(extension, item->id().extension_key);
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);
703 // Dispatch to menu item's .onclick handler.
704 scoped_ptr<Event> event(
705 new Event(webview_guest ? kOnWebviewContextMenus
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());
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,
718 event->restrict_to_browser_context = context;
719 event->user_gesture = EventRouter::USER_GESTURE_ENABLED;
721 event->filter_info.SetInstanceID(webview_guest->view_instance_id());
722 event_router->DispatchEventToExtension(item->extension_id(), event.Pass());
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) {
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();
741 if ((*radio_run_iter)->type() != MenuItem::RADIO) {
745 if ((*radio_run_iter)->checked()) {
746 last_checked = radio_run_iter;
747 (*radio_run_iter)->SetChecked(false);
751 if (last_checked != item_list.end())
752 (*last_checked)->SetChecked(true);
754 (*i)->SetChecked(true);
760 bool MenuManager::ItemUpdated(const MenuItem::Id& id) {
761 if (!ContainsKey(items_by_id_, id))
764 MenuItem* menu_item = GetItemById(id);
767 if (menu_item->parent_id()) {
768 SanitizeRadioList(GetItemById(*menu_item->parent_id())->children());
770 MenuItemMap::iterator i =
771 context_items_.find(menu_item->id().extension_key);
772 if (i == context_items_.end()) {
776 SanitizeRadioList(i->second);
782 void MenuManager::WriteToStorage(const Extension* extension,
783 const MenuItem::ExtensionKey& extension_key) {
784 if (!BackgroundInfo::HasLazyBackgroundPage(extension))
786 // <webview> menu items are transient and not stored in storage.
787 if (extension_key.webview_instance_id)
789 const MenuItem::List* top_items = MenuItems(extension_key);
790 MenuItem::List all_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);
800 store_->SetExtensionValue(extension->id(), kContextMenusKey,
801 MenuItemsToValue(all_items));
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()
810 ->GetByID(extension_id);
814 MenuItem::List items = MenuItemsFromValue(extension_id, value.get());
815 for (size_t i = 0; i < items.size(); ++i) {
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]);
827 added = AddContextItem(extension, items[i]);
835 void MenuManager::OnExtensionLoaded(content::BrowserContext* browser_context,
836 const Extension* extension) {
837 if (store_ && BackgroundInfo::HasLazyBackgroundPage(extension)) {
838 store_->GetExtensionValue(
842 &MenuManager::ReadFromStorage, AsWeakPtr(), extension->id()));
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);
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();
869 const SkBitmap& MenuManager::GetIconForExtension(
870 const std::string& extension_id) {
871 return icon_manager_.GetIcon(extension_id);
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();
881 if (iter->first.incognito)
882 items_to_remove.insert(iter->first);
885 std::set<MenuItem::Id>::iterator remove_iter;
886 for (remove_iter = items_to_remove.begin();
887 remove_iter != items_to_remove.end();
889 RemoveContextMenuItem(*remove_iter);
892 MenuItem::ExtensionKey::ExtensionKey() : webview_instance_id(0) {}
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) {}
898 MenuItem::ExtensionKey::ExtensionKey(const std::string& extension_id)
899 : extension_id(extension_id), webview_instance_id(0) {}
901 bool MenuItem::ExtensionKey::operator==(const ExtensionKey& other) const {
902 return extension_id == other.extension_id &&
903 webview_instance_id == other.webview_instance_id;
906 bool MenuItem::ExtensionKey::operator<(const ExtensionKey& other) const {
907 if (extension_id != other.extension_id)
908 return extension_id < other.extension_id;
910 return webview_instance_id < other.webview_instance_id;
913 bool MenuItem::ExtensionKey::operator!=(const ExtensionKey& other) const {
914 return !(*this == other);
917 bool MenuItem::ExtensionKey::empty() const {
918 return extension_id.empty() && !webview_instance_id;
921 MenuItem::Id::Id() : incognito(false), uid(0) {}
923 MenuItem::Id::Id(bool incognito, const MenuItem::ExtensionKey& extension_key)
924 : incognito(incognito), extension_key(extension_key), uid(0) {}
926 MenuItem::Id::~Id() {
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);
935 bool MenuItem::Id::operator!=(const Id& other) const {
936 return !(*this == other);
939 bool MenuItem::Id::operator<(const Id& other) const {
940 if (incognito < other.incognito)
942 if (incognito == other.incognito) {
943 if (extension_key < other.extension_key)
945 if (extension_key == other.extension_key) {
948 if (uid == other.uid)
949 return string_uid < other.string_uid;
955 } // namespace extensions