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_tab_util.h"
17 #include "chrome/browser/extensions/menu_manager_factory.h"
18 #include "chrome/browser/extensions/tab_helper.h"
19 #include "chrome/browser/profiles/profile.h"
20 #include "chrome/common/extensions/api/chrome_web_view_internal.h"
21 #include "chrome/common/extensions/api/context_menus.h"
22 #include "content/public/browser/notification_details.h"
23 #include "content/public/browser/notification_service.h"
24 #include "content/public/browser/notification_source.h"
25 #include "content/public/browser/web_contents.h"
26 #include "content/public/common/child_process_host.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/guest_view/web_view/web_view_guest.h"
31 #include "extensions/browser/state_store.h"
32 #include "extensions/common/extension.h"
33 #include "extensions/common/manifest_handlers/background_info.h"
34 #include "ui/gfx/favicon_size.h"
35 #include "ui/gfx/text_elider.h"
37 using content::ChildProcessHost
;
38 using content::WebContents
;
39 using guest_view::kInstanceIDNone
;
41 namespace extensions
{
45 // Keys for serialization to and from Value to store in the preferences.
46 const char kContextMenusKey
[] = "context_menus";
48 const char kCheckedKey
[] = "checked";
49 const char kContextsKey
[] = "contexts";
50 const char kDocumentURLPatternsKey
[] = "document_url_patterns";
51 const char kEnabledKey
[] = "enabled";
52 const char kIncognitoKey
[] = "incognito";
53 const char kParentUIDKey
[] = "parent_uid";
54 const char kStringUIDKey
[] = "string_uid";
55 const char kTargetURLPatternsKey
[] = "target_url_patterns";
56 const char kTitleKey
[] = "title";
57 const char kTypeKey
[] = "type";
59 void SetIdKeyValue(base::DictionaryValue
* properties
,
61 const MenuItem::Id
& id
) {
63 properties
->SetString(key
, id
.string_uid
);
65 properties
->SetInteger(key
, id
.uid
);
68 MenuItem::List
MenuItemsFromValue(const std::string
& extension_id
,
72 base::ListValue
* list
= NULL
;
73 if (!value
|| !value
->GetAsList(&list
))
76 for (size_t i
= 0; i
< list
->GetSize(); ++i
) {
77 base::DictionaryValue
* dict
= NULL
;
78 if (!list
->GetDictionary(i
, &dict
))
80 MenuItem
* item
= MenuItem::Populate(
81 extension_id
, *dict
, NULL
);
84 items
.push_back(item
);
89 scoped_ptr
<base::Value
> MenuItemsToValue(const MenuItem::List
& items
) {
90 scoped_ptr
<base::ListValue
> list(new base::ListValue());
91 for (size_t i
= 0; i
< items
.size(); ++i
)
92 list
->Append(items
[i
]->ToValue().release());
93 return scoped_ptr
<base::Value
>(list
.release());
96 bool GetStringList(const base::DictionaryValue
& dict
,
97 const std::string
& key
,
98 std::vector
<std::string
>* out
) {
99 if (!dict
.HasKey(key
))
102 const base::ListValue
* list
= NULL
;
103 if (!dict
.GetListWithoutPathExpansion(key
, &list
))
106 for (size_t i
= 0; i
< list
->GetSize(); ++i
) {
108 if (!list
->GetString(i
, &pattern
))
110 out
->push_back(pattern
);
118 MenuItem::MenuItem(const Id
& id
,
119 const std::string
& title
,
123 const ContextList
& contexts
)
129 contexts_(contexts
) {}
131 MenuItem::~MenuItem() {
132 STLDeleteElements(&children_
);
135 MenuItem
* MenuItem::ReleaseChild(const Id
& child_id
,
137 for (List::iterator i
= children_
.begin(); i
!= children_
.end(); ++i
) {
138 MenuItem
* child
= NULL
;
139 if ((*i
)->id() == child_id
) {
143 } else if (recursive
) {
144 child
= (*i
)->ReleaseChild(child_id
, recursive
);
152 void MenuItem::GetFlattenedSubtree(MenuItem::List
* list
) {
153 list
->push_back(this);
154 for (List::iterator i
= children_
.begin(); i
!= children_
.end(); ++i
)
155 (*i
)->GetFlattenedSubtree(list
);
158 std::set
<MenuItem::Id
> MenuItem::RemoveAllDescendants() {
160 for (List::iterator i
= children_
.begin(); i
!= children_
.end(); ++i
) {
161 MenuItem
* child
= *i
;
162 result
.insert(child
->id());
163 std::set
<Id
> removed
= child
->RemoveAllDescendants();
164 result
.insert(removed
.begin(), removed
.end());
166 STLDeleteElements(&children_
);
170 base::string16
MenuItem::TitleWithReplacement(const base::string16
& selection
,
171 size_t max_length
) const {
172 base::string16 result
= base::UTF8ToUTF16(title_
);
173 // TODO(asargent) - Change this to properly handle %% escaping so you can
174 // put "%s" in titles that won't get substituted.
175 base::ReplaceSubstringsAfterOffset(
176 &result
, 0, base::ASCIIToUTF16("%s"), selection
);
178 if (result
.length() > max_length
)
179 result
= gfx::TruncateString(result
, max_length
, gfx::WORD_BREAK
);
183 bool MenuItem::SetChecked(bool checked
) {
184 if (type_
!= CHECKBOX
&& type_
!= RADIO
)
190 void MenuItem::AddChild(MenuItem
* item
) {
191 item
->parent_id_
.reset(new Id(id_
));
192 children_
.push_back(item
);
195 scoped_ptr
<base::DictionaryValue
> MenuItem::ToValue() const {
196 scoped_ptr
<base::DictionaryValue
> value(new base::DictionaryValue
);
197 // Should only be called for extensions with event pages, which only have
198 // string IDs for items.
199 DCHECK_EQ(0, id_
.uid
);
200 value
->SetString(kStringUIDKey
, id_
.string_uid
);
201 value
->SetBoolean(kIncognitoKey
, id_
.incognito
);
202 value
->SetInteger(kTypeKey
, type_
);
203 if (type_
!= SEPARATOR
)
204 value
->SetString(kTitleKey
, title_
);
205 if (type_
== CHECKBOX
|| type_
== RADIO
)
206 value
->SetBoolean(kCheckedKey
, checked_
);
207 value
->SetBoolean(kEnabledKey
, enabled_
);
208 value
->Set(kContextsKey
, contexts_
.ToValue().release());
210 DCHECK_EQ(0, parent_id_
->uid
);
211 value
->SetString(kParentUIDKey
, parent_id_
->string_uid
);
213 value
->Set(kDocumentURLPatternsKey
,
214 document_url_patterns_
.ToValue().release());
215 value
->Set(kTargetURLPatternsKey
, target_url_patterns_
.ToValue().release());
220 MenuItem
* MenuItem::Populate(const std::string
& extension_id
,
221 const base::DictionaryValue
& value
,
222 std::string
* error
) {
223 bool incognito
= false;
224 if (!value
.GetBoolean(kIncognitoKey
, &incognito
))
226 Id
id(incognito
, MenuItem::ExtensionKey(extension_id
));
227 if (!value
.GetString(kStringUIDKey
, &id
.string_uid
))
231 if (!value
.GetInteger(kTypeKey
, &type_int
))
233 type
= static_cast<Type
>(type_int
);
235 if (type
!= SEPARATOR
&& !value
.GetString(kTitleKey
, &title
))
237 bool checked
= false;
238 if ((type
== CHECKBOX
|| type
== RADIO
) &&
239 !value
.GetBoolean(kCheckedKey
, &checked
)) {
243 if (!value
.GetBoolean(kEnabledKey
, &enabled
))
245 ContextList contexts
;
246 const base::Value
* contexts_value
= NULL
;
247 if (!value
.Get(kContextsKey
, &contexts_value
))
249 if (!contexts
.Populate(*contexts_value
))
252 scoped_ptr
<MenuItem
> result(new MenuItem(
253 id
, title
, checked
, enabled
, type
, contexts
));
255 std::vector
<std::string
> document_url_patterns
;
256 if (!GetStringList(value
, kDocumentURLPatternsKey
, &document_url_patterns
))
258 std::vector
<std::string
> target_url_patterns
;
259 if (!GetStringList(value
, kTargetURLPatternsKey
, &target_url_patterns
))
262 if (!result
->PopulateURLPatterns(&document_url_patterns
,
263 &target_url_patterns
,
268 // parent_id is filled in from the value, but it might not be valid. It's left
269 // to be validated upon being added (via AddChildItem) to the menu manager.
270 scoped_ptr
<Id
> parent_id(
271 new Id(incognito
, MenuItem::ExtensionKey(extension_id
)));
272 if (value
.HasKey(kParentUIDKey
)) {
273 if (!value
.GetString(kParentUIDKey
, &parent_id
->string_uid
))
275 result
->parent_id_
.swap(parent_id
);
277 return result
.release();
280 bool MenuItem::PopulateURLPatterns(
281 std::vector
<std::string
>* document_url_patterns
,
282 std::vector
<std::string
>* target_url_patterns
,
283 std::string
* error
) {
284 if (document_url_patterns
) {
285 if (!document_url_patterns_
.Populate(
286 *document_url_patterns
, URLPattern::SCHEME_ALL
, true, error
)) {
290 if (target_url_patterns
) {
291 if (!target_url_patterns_
.Populate(
292 *target_url_patterns
, URLPattern::SCHEME_ALL
, true, error
)) {
300 const char MenuManager::kOnContextMenus
[] = "contextMenus";
301 const char MenuManager::kOnWebviewContextMenus
[] =
302 "webViewInternal.contextMenus";
304 MenuManager::MenuManager(content::BrowserContext
* context
, StateStore
* store
)
305 : extension_registry_observer_(this),
306 browser_context_(context
),
308 extension_registry_observer_
.Add(ExtensionRegistry::Get(browser_context_
));
309 registrar_
.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED
,
310 content::NotificationService::AllSources());
312 store_
->RegisterKey(kContextMenusKey
);
315 MenuManager::~MenuManager() {
316 MenuItemMap::iterator i
;
317 for (i
= context_items_
.begin(); i
!= context_items_
.end(); ++i
) {
318 STLDeleteElements(&(i
->second
));
323 MenuManager
* MenuManager::Get(content::BrowserContext
* context
) {
324 return MenuManagerFactory::GetForBrowserContext(context
);
327 std::set
<MenuItem::ExtensionKey
> MenuManager::ExtensionIds() {
328 std::set
<MenuItem::ExtensionKey
> id_set
;
329 for (MenuItemMap::const_iterator i
= context_items_
.begin();
330 i
!= context_items_
.end(); ++i
) {
331 id_set
.insert(i
->first
);
336 const MenuItem::List
* MenuManager::MenuItems(
337 const MenuItem::ExtensionKey
& key
) {
338 MenuItemMap::iterator i
= context_items_
.find(key
);
339 if (i
!= context_items_
.end()) {
345 bool MenuManager::AddContextItem(const Extension
* extension
, MenuItem
* item
) {
346 const MenuItem::ExtensionKey
& key
= item
->id().extension_key
;
348 // The item must have a non-empty key, and not have already been added.
349 if (key
.empty() || ContainsKey(items_by_id_
, item
->id()))
352 DCHECK_EQ(extension
->id(), key
.extension_id
);
354 bool first_item
= !ContainsKey(context_items_
, key
);
355 context_items_
[key
].push_back(item
);
356 items_by_id_
[item
->id()] = item
;
358 if (item
->type() == MenuItem::RADIO
) {
360 RadioItemSelected(item
);
362 SanitizeRadioList(context_items_
[key
]);
365 // If this is the first item for this extension, start loading its icon.
367 icon_manager_
.LoadIcon(browser_context_
, extension
);
372 bool MenuManager::AddChildItem(const MenuItem::Id
& parent_id
,
374 MenuItem
* parent
= GetItemById(parent_id
);
375 if (!parent
|| parent
->type() != MenuItem::NORMAL
||
376 parent
->incognito() != child
->incognito() ||
377 parent
->extension_id() != child
->extension_id() ||
378 ContainsKey(items_by_id_
, child
->id()))
380 parent
->AddChild(child
);
381 items_by_id_
[child
->id()] = child
;
383 if (child
->type() == MenuItem::RADIO
)
384 SanitizeRadioList(parent
->children());
388 bool MenuManager::DescendantOf(MenuItem
* item
,
389 const MenuItem::Id
& ancestor_id
) {
390 // Work our way up the tree until we find the ancestor or NULL.
391 MenuItem::Id
* id
= item
->parent_id();
393 DCHECK(*id
!= item
->id()); // Catch circular graphs.
394 if (*id
== ancestor_id
)
396 MenuItem
* next
= GetItemById(*id
);
401 id
= next
->parent_id();
406 bool MenuManager::ChangeParent(const MenuItem::Id
& child_id
,
407 const MenuItem::Id
* parent_id
) {
408 MenuItem
* child
= GetItemById(child_id
);
409 MenuItem
* new_parent
= parent_id
? GetItemById(*parent_id
) : NULL
;
410 if ((parent_id
&& (child_id
== *parent_id
)) || !child
||
411 (!new_parent
&& parent_id
!= NULL
) ||
412 (new_parent
&& (DescendantOf(new_parent
, child_id
) ||
413 child
->incognito() != new_parent
->incognito() ||
414 child
->extension_id() != new_parent
->extension_id())))
417 MenuItem::Id
* old_parent_id
= child
->parent_id();
418 if (old_parent_id
!= NULL
) {
419 MenuItem
* old_parent
= GetItemById(*old_parent_id
);
425 old_parent
->ReleaseChild(child_id
, false /* non-recursive search*/);
426 DCHECK(taken
== child
);
427 SanitizeRadioList(old_parent
->children());
429 // This is a top-level item, so we need to pull it out of our list of
431 const MenuItem::ExtensionKey
& child_key
= child
->id().extension_key
;
432 MenuItemMap::iterator i
= context_items_
.find(child_key
);
433 if (i
== context_items_
.end()) {
437 MenuItem::List
& list
= i
->second
;
438 MenuItem::List::iterator j
= std::find(list
.begin(), list
.end(), child
);
439 if (j
== list
.end()) {
444 SanitizeRadioList(list
);
448 new_parent
->AddChild(child
);
449 SanitizeRadioList(new_parent
->children());
451 const MenuItem::ExtensionKey
& child_key
= child
->id().extension_key
;
452 context_items_
[child_key
].push_back(child
);
453 child
->parent_id_
.reset(NULL
);
454 SanitizeRadioList(context_items_
[child_key
]);
459 bool MenuManager::RemoveContextMenuItem(const MenuItem::Id
& id
) {
460 if (!ContainsKey(items_by_id_
, id
))
463 MenuItem
* menu_item
= GetItemById(id
);
465 const MenuItem::ExtensionKey extension_key
= id
.extension_key
;
466 MenuItemMap::iterator i
= context_items_
.find(extension_key
);
467 if (i
== context_items_
.end()) {
473 std::set
<MenuItem::Id
> items_removed
;
474 MenuItem::List
& list
= i
->second
;
475 MenuItem::List::iterator j
;
476 for (j
= list
.begin(); j
< list
.end(); ++j
) {
477 // See if the current top-level item is a match.
478 if ((*j
)->id() == id
) {
479 items_removed
= (*j
)->RemoveAllDescendants();
480 items_removed
.insert(id
);
484 SanitizeRadioList(list
);
487 // See if the item to remove was found as a descendant of the current
489 MenuItem
* child
= (*j
)->ReleaseChild(id
, true /* recursive */);
491 items_removed
= child
->RemoveAllDescendants();
492 items_removed
.insert(id
);
493 SanitizeRadioList(GetItemById(*child
->parent_id())->children());
500 DCHECK(result
); // The check at the very top should have prevented this.
502 // Clear entries from the items_by_id_ map.
503 std::set
<MenuItem::Id
>::iterator removed_iter
;
504 for (removed_iter
= items_removed
.begin();
505 removed_iter
!= items_removed
.end();
507 items_by_id_
.erase(*removed_iter
);
511 context_items_
.erase(extension_key
);
512 icon_manager_
.RemoveIcon(extension_key
.extension_id
);
517 void MenuManager::RemoveAllContextItems(
518 const MenuItem::ExtensionKey
& extension_key
) {
519 auto it
= context_items_
.find(extension_key
);
520 if (it
== context_items_
.end())
523 // We use the |extension_id| from the stored ExtensionKey, since the provided
524 // |extension_key| may leave it empty (if matching solely basted on the
526 // TODO(paulmeyer): We can get rid of this hack if/when we reliably track
527 // extension IDs at WebView cleanup.
528 std::string extension_id
= it
->first
.extension_id
;
529 MenuItem::List
& context_items_for_key
= it
->second
;
530 MenuItem::List::iterator i
;
531 for (i
= context_items_for_key
.begin();
532 i
!= context_items_for_key
.end();
535 items_by_id_
.erase(item
->id());
537 // Remove descendants from this item and erase them from the lookup cache.
538 std::set
<MenuItem::Id
> removed_ids
= item
->RemoveAllDescendants();
539 std::set
<MenuItem::Id
>::const_iterator j
;
540 for (j
= removed_ids
.begin(); j
!= removed_ids
.end(); ++j
) {
541 items_by_id_
.erase(*j
);
544 STLDeleteElements(&context_items_for_key
);
545 context_items_
.erase(extension_key
);
546 icon_manager_
.RemoveIcon(extension_id
);
549 MenuItem
* MenuManager::GetItemById(const MenuItem::Id
& id
) const {
550 std::map
<MenuItem::Id
, MenuItem
*>::const_iterator i
=
551 items_by_id_
.find(id
);
552 if (i
!= items_by_id_
.end())
558 void MenuManager::RadioItemSelected(MenuItem
* item
) {
559 // If this is a child item, we need to get a handle to the list from its
560 // parent. Otherwise get a handle to the top-level list.
561 const MenuItem::List
* list
= NULL
;
562 if (item
->parent_id()) {
563 MenuItem
* parent
= GetItemById(*item
->parent_id());
568 list
= &(parent
->children());
570 const MenuItem::ExtensionKey
& key
= item
->id().extension_key
;
571 if (context_items_
.find(key
) == context_items_
.end()) {
575 list
= &context_items_
[key
];
578 // Find where |item| is in the list.
579 MenuItem::List::const_iterator item_location
;
580 for (item_location
= list
->begin(); item_location
!= list
->end();
582 if (*item_location
== item
)
585 if (item_location
== list
->end()) {
586 NOTREACHED(); // We should have found the item.
590 // Iterate backwards from |item| and uncheck any adjacent radio items.
591 MenuItem::List::const_iterator i
;
592 if (item_location
!= list
->begin()) {
596 if ((*i
)->type() != MenuItem::RADIO
)
598 (*i
)->SetChecked(false);
599 } while (i
!= list
->begin());
602 // Now iterate forwards from |item| and uncheck any adjacent radio items.
603 for (i
= item_location
+ 1; i
!= list
->end(); ++i
) {
604 if ((*i
)->type() != MenuItem::RADIO
)
606 (*i
)->SetChecked(false);
610 static void AddURLProperty(base::DictionaryValue
* dictionary
,
611 const std::string
& key
, const GURL
& url
) {
613 dictionary
->SetString(key
, url
.possibly_invalid_spec());
616 void MenuManager::ExecuteCommand(content::BrowserContext
* context
,
617 WebContents
* web_contents
,
618 const content::ContextMenuParams
& params
,
619 const MenuItem::Id
& menu_item_id
) {
620 EventRouter
* event_router
= EventRouter::Get(context
);
624 MenuItem
* item
= GetItemById(menu_item_id
);
628 ExtensionRegistry
* registry
= ExtensionRegistry::Get(browser_context_
);
629 const Extension
* extension
=
630 registry
->enabled_extensions().GetByID(item
->extension_id());
632 if (item
->type() == MenuItem::RADIO
)
633 RadioItemSelected(item
);
635 scoped_ptr
<base::ListValue
> args(new base::ListValue());
637 base::DictionaryValue
* properties
= new base::DictionaryValue();
638 SetIdKeyValue(properties
, "menuItemId", item
->id());
639 if (item
->parent_id())
640 SetIdKeyValue(properties
, "parentMenuItemId", *item
->parent_id());
642 switch (params
.media_type
) {
643 case blink::WebContextMenuData::MediaTypeImage
:
644 properties
->SetString("mediaType", "image");
646 case blink::WebContextMenuData::MediaTypeVideo
:
647 properties
->SetString("mediaType", "video");
649 case blink::WebContextMenuData::MediaTypeAudio
:
650 properties
->SetString("mediaType", "audio");
652 default: {} // Do nothing.
655 AddURLProperty(properties
, "linkUrl", params
.unfiltered_link_url
);
656 AddURLProperty(properties
, "srcUrl", params
.src_url
);
657 AddURLProperty(properties
, "pageUrl", params
.page_url
);
658 AddURLProperty(properties
, "frameUrl", params
.frame_url
);
660 if (params
.selection_text
.length() > 0)
661 properties
->SetString("selectionText", params
.selection_text
);
663 properties
->SetBoolean("editable", params
.is_editable
);
665 WebViewGuest
* webview_guest
= WebViewGuest::FromWebContents(web_contents
);
667 // This is used in web_view_internalcustom_bindings.js.
668 // The property is not exposed to developer API.
669 properties
->SetInteger("webviewInstanceId",
670 webview_guest
->view_instance_id());
673 args
->Append(properties
);
675 // Add the tab info to the argument list.
676 // No tab info in a platform app.
677 if (!extension
|| !extension
->is_platform_app()) {
678 // Note: web_contents are NULL in unit tests :(
680 args
->Append(ExtensionTabUtil::CreateTabValue(web_contents
));
682 args
->Append(new base::DictionaryValue());
686 if (item
->type() == MenuItem::CHECKBOX
||
687 item
->type() == MenuItem::RADIO
) {
688 bool was_checked
= item
->checked();
689 properties
->SetBoolean("wasChecked", was_checked
);
691 // RADIO items always get set to true when you click on them, but CHECKBOX
692 // items get their state toggled.
694 (item
->type() == MenuItem::RADIO
) ? true : !was_checked
;
696 item
->SetChecked(checked
);
697 properties
->SetBoolean("checked", item
->checked());
700 WriteToStorage(extension
, item
->id().extension_key
);
703 // Note: web_contents are NULL in unit tests :(
704 if (web_contents
&& TabHelper::FromWebContents(web_contents
)) {
705 TabHelper::FromWebContents(web_contents
)
706 ->active_tab_permission_granter()
707 ->GrantIfRequested(extension
);
711 // Dispatch to menu item's .onclick handler (this is the legacy API, from
712 // before chrome.contextMenus.onClicked existed).
713 scoped_ptr
<Event
> event(
714 new Event(webview_guest
? events::WEB_VIEW_INTERNAL_CONTEXT_MENUS
715 : events::CONTEXT_MENUS
,
716 webview_guest
? kOnWebviewContextMenus
: kOnContextMenus
,
717 scoped_ptr
<base::ListValue
>(args
->DeepCopy())));
718 event
->restrict_to_browser_context
= context
;
719 event
->user_gesture
= EventRouter::USER_GESTURE_ENABLED
;
720 event_router
->DispatchEventToExtension(item
->extension_id(), event
.Pass());
723 // Dispatch to .contextMenus.onClicked handler.
724 scoped_ptr
<Event
> event(new Event(
725 webview_guest
? events::CHROME_WEB_VIEW_INTERNAL_ON_CLICKED
726 : events::CONTEXT_MENUS_ON_CLICKED
,
727 webview_guest
? api::chrome_web_view_internal::OnClicked::kEventName
728 : api::context_menus::OnClicked::kEventName
,
730 event
->restrict_to_browser_context
= context
;
731 event
->user_gesture
= EventRouter::USER_GESTURE_ENABLED
;
733 event
->filter_info
.SetInstanceID(webview_guest
->view_instance_id());
734 event_router
->DispatchEventToExtension(item
->extension_id(), event
.Pass());
738 void MenuManager::SanitizeRadioList(const MenuItem::List
& item_list
) {
739 MenuItem::List::const_iterator i
= item_list
.begin();
740 while (i
!= item_list
.end()) {
741 if ((*i
)->type() != MenuItem::RADIO
) {
746 // Uncheck any checked radio items in the run, and at the end reset
747 // the appropriate one to checked. If no check radio items were found,
748 // then check the first radio item in the run.
749 MenuItem::List::const_iterator last_checked
= item_list
.end();
750 MenuItem::List::const_iterator radio_run_iter
;
751 for (radio_run_iter
= i
; radio_run_iter
!= item_list
.end();
753 if ((*radio_run_iter
)->type() != MenuItem::RADIO
) {
757 if ((*radio_run_iter
)->checked()) {
758 last_checked
= radio_run_iter
;
759 (*radio_run_iter
)->SetChecked(false);
763 if (last_checked
!= item_list
.end())
764 (*last_checked
)->SetChecked(true);
766 (*i
)->SetChecked(true);
772 bool MenuManager::ItemUpdated(const MenuItem::Id
& id
) {
773 if (!ContainsKey(items_by_id_
, id
))
776 MenuItem
* menu_item
= GetItemById(id
);
779 if (menu_item
->parent_id()) {
780 SanitizeRadioList(GetItemById(*menu_item
->parent_id())->children());
782 MenuItemMap::iterator i
=
783 context_items_
.find(menu_item
->id().extension_key
);
784 if (i
== context_items_
.end()) {
788 SanitizeRadioList(i
->second
);
794 void MenuManager::WriteToStorage(const Extension
* extension
,
795 const MenuItem::ExtensionKey
& extension_key
) {
796 if (!BackgroundInfo::HasLazyBackgroundPage(extension
))
798 // <webview> menu items are transient and not stored in storage.
799 if (extension_key
.webview_instance_id
)
801 const MenuItem::List
* top_items
= MenuItems(extension_key
);
802 MenuItem::List all_items
;
804 for (MenuItem::List::const_iterator i
= top_items
->begin();
805 i
!= top_items
->end(); ++i
) {
806 DCHECK(!(*i
)->id().extension_key
.webview_instance_id
);
807 (*i
)->GetFlattenedSubtree(&all_items
);
812 store_
->SetExtensionValue(extension
->id(), kContextMenusKey
,
813 MenuItemsToValue(all_items
));
817 void MenuManager::ReadFromStorage(const std::string
& extension_id
,
818 scoped_ptr
<base::Value
> value
) {
819 const Extension
* extension
= ExtensionRegistry::Get(browser_context_
)
820 ->enabled_extensions()
821 .GetByID(extension_id
);
825 MenuItem::List items
= MenuItemsFromValue(extension_id
, value
.get());
826 for (size_t i
= 0; i
< items
.size(); ++i
) {
829 if (items
[i
]->parent_id()) {
830 // Parent IDs are stored in the parent_id field for convenience, but
831 // they have not yet been validated. Separate them out here.
832 // Because of the order in which we store items in the prefs, parents will
833 // precede children, so we should already know about any parent items.
834 scoped_ptr
<MenuItem::Id
> parent_id
;
835 parent_id
.swap(items
[i
]->parent_id_
);
836 added
= AddChildItem(*parent_id
, items
[i
]);
838 added
= AddContextItem(extension
, items
[i
]);
846 void MenuManager::OnExtensionLoaded(content::BrowserContext
* browser_context
,
847 const Extension
* extension
) {
848 if (store_
&& BackgroundInfo::HasLazyBackgroundPage(extension
)) {
849 store_
->GetExtensionValue(
853 &MenuManager::ReadFromStorage
, AsWeakPtr(), extension
->id()));
857 void MenuManager::OnExtensionUnloaded(content::BrowserContext
* browser_context
,
858 const Extension
* extension
,
859 UnloadedExtensionInfo::Reason reason
) {
860 MenuItem::ExtensionKey
extension_key(extension
->id());
861 if (ContainsKey(context_items_
, extension_key
)) {
862 RemoveAllContextItems(extension_key
);
866 void MenuManager::Observe(int type
,
867 const content::NotificationSource
& source
,
868 const content::NotificationDetails
& details
) {
869 DCHECK_EQ(chrome::NOTIFICATION_PROFILE_DESTROYED
, type
);
870 Profile
* profile
= content::Source
<Profile
>(source
).ptr();
871 // We cannot use profile_->HasOffTheRecordProfile as it may already be
872 // false at this point, if for example the incognito profile was destroyed
873 // using DestroyOffTheRecordProfile.
874 if (profile
->GetOriginalProfile() == browser_context_
&&
875 profile
->GetOriginalProfile() != profile
) {
876 RemoveAllIncognitoContextItems();
880 const SkBitmap
& MenuManager::GetIconForExtension(
881 const std::string
& extension_id
) {
882 return icon_manager_
.GetIcon(extension_id
);
885 void MenuManager::RemoveAllIncognitoContextItems() {
886 // Get all context menu items with "incognito" set to "split".
887 std::set
<MenuItem::Id
> items_to_remove
;
888 std::map
<MenuItem::Id
, MenuItem
*>::const_iterator iter
;
889 for (iter
= items_by_id_
.begin();
890 iter
!= items_by_id_
.end();
892 if (iter
->first
.incognito
)
893 items_to_remove
.insert(iter
->first
);
896 std::set
<MenuItem::Id
>::iterator remove_iter
;
897 for (remove_iter
= items_to_remove
.begin();
898 remove_iter
!= items_to_remove
.end();
900 RemoveContextMenuItem(*remove_iter
);
903 MenuItem::ExtensionKey::ExtensionKey()
904 : webview_embedder_process_id(ChildProcessHost::kInvalidUniqueID
),
905 webview_instance_id(kInstanceIDNone
) {}
907 MenuItem::ExtensionKey::ExtensionKey(const std::string
& extension_id
)
908 : extension_id(extension_id
),
909 webview_embedder_process_id(ChildProcessHost::kInvalidUniqueID
),
910 webview_instance_id(kInstanceIDNone
) {
911 DCHECK(!extension_id
.empty());
914 MenuItem::ExtensionKey::ExtensionKey(const std::string
& extension_id
,
915 int webview_embedder_process_id
,
916 int webview_instance_id
)
917 : extension_id(extension_id
),
918 webview_embedder_process_id(webview_embedder_process_id
),
919 webview_instance_id(webview_instance_id
) {
920 DCHECK(webview_embedder_process_id
!= ChildProcessHost::kInvalidUniqueID
&&
921 webview_instance_id
!= kInstanceIDNone
);
924 bool MenuItem::ExtensionKey::operator==(const ExtensionKey
& other
) const {
925 bool webview_ids_match
= webview_instance_id
== other
.webview_instance_id
&&
926 webview_embedder_process_id
== other
.webview_embedder_process_id
;
928 // If either extension ID is empty, then these ExtensionKeys will be matched
929 // only based on the other IDs.
930 if (extension_id
.empty() || other
.extension_id
.empty())
931 return webview_ids_match
;
933 return extension_id
== other
.extension_id
&& webview_ids_match
;
936 bool MenuItem::ExtensionKey::operator<(const ExtensionKey
& other
) const {
937 if (webview_embedder_process_id
!= other
.webview_embedder_process_id
)
938 return webview_embedder_process_id
< other
.webview_embedder_process_id
;
940 if (webview_instance_id
!= other
.webview_instance_id
)
941 return webview_instance_id
< other
.webview_instance_id
;
943 // If either extension ID is empty, then these ExtensionKeys will be compared
944 // only based on the other IDs.
945 if (extension_id
.empty() || other
.extension_id
.empty())
948 return extension_id
< other
.extension_id
;
951 bool MenuItem::ExtensionKey::operator!=(const ExtensionKey
& other
) const {
952 return !(*this == other
);
955 bool MenuItem::ExtensionKey::empty() const {
956 return extension_id
.empty() &&
957 webview_embedder_process_id
== ChildProcessHost::kInvalidUniqueID
&&
958 webview_instance_id
== kInstanceIDNone
;
961 MenuItem::Id::Id() : incognito(false), uid(0) {}
963 MenuItem::Id::Id(bool incognito
, const MenuItem::ExtensionKey
& extension_key
)
964 : incognito(incognito
), extension_key(extension_key
), uid(0) {}
966 MenuItem::Id::~Id() {
969 bool MenuItem::Id::operator==(const Id
& other
) const {
970 return (incognito
== other
.incognito
&&
971 extension_key
== other
.extension_key
&& uid
== other
.uid
&&
972 string_uid
== other
.string_uid
);
975 bool MenuItem::Id::operator!=(const Id
& other
) const {
976 return !(*this == other
);
979 bool MenuItem::Id::operator<(const Id
& other
) const {
980 if (incognito
< other
.incognito
)
982 if (incognito
== other
.incognito
) {
983 if (extension_key
< other
.extension_key
)
985 if (extension_key
== other
.extension_key
) {
988 if (uid
== other
.uid
)
989 return string_uid
< other
.string_uid
;
995 } // namespace extensions