Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / extensions / menu_manager.cc
blobee997994e0089eba6ab640c0549dcab8fd910c90
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/extension_service.h"
18 #include "chrome/browser/extensions/extension_system.h"
19 #include "chrome/browser/extensions/extension_tab_util.h"
20 #include "chrome/browser/extensions/menu_manager_factory.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 "content/public/browser/notification_details.h"
26 #include "content/public/browser/notification_service.h"
27 #include "content/public/browser/notification_source.h"
28 #include "content/public/browser/web_contents.h"
29 #include "content/public/common/context_menu_params.h"
30 #include "extensions/browser/event_router.h"
31 #include "extensions/common/extension.h"
32 #include "extensions/common/manifest_handlers/background_info.h"
33 #include "ui/gfx/favicon_size.h"
34 #include "ui/gfx/text_elider.h"
36 using content::WebContents;
37 using extensions::ExtensionSystem;
39 namespace extensions {
41 namespace context_menus = api::context_menus;
43 namespace {
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,
60 const char* key,
61 const MenuItem::Id& id) {
62 if (id.uid == 0)
63 properties->SetString(key, id.string_uid);
64 else
65 properties->SetInteger(key, id.uid);
68 MenuItem::List MenuItemsFromValue(const std::string& extension_id,
69 base::Value* value) {
70 MenuItem::List items;
72 base::ListValue* list = NULL;
73 if (!value || !value->GetAsList(&list))
74 return items;
76 for (size_t i = 0; i < list->GetSize(); ++i) {
77 base::DictionaryValue* dict = NULL;
78 if (!list->GetDictionary(i, &dict))
79 continue;
80 MenuItem* item = MenuItem::Populate(
81 extension_id, *dict, NULL);
82 if (!item)
83 continue;
84 items.push_back(item);
86 return items;
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))
100 return true;
102 const base::ListValue* list = NULL;
103 if (!dict.GetListWithoutPathExpansion(key, &list))
104 return false;
106 for (size_t i = 0; i < list->GetSize(); ++i) {
107 std::string pattern;
108 if (!list->GetString(i, &pattern))
109 return false;
110 out->push_back(pattern);
113 return true;
116 } // namespace
118 MenuItem::MenuItem(const Id& id,
119 const std::string& title,
120 bool checked,
121 bool enabled,
122 Type type,
123 const ContextList& contexts)
124 : id_(id),
125 title_(title),
126 type_(type),
127 checked_(checked),
128 enabled_(enabled),
129 contexts_(contexts) {}
131 MenuItem::~MenuItem() {
132 STLDeleteElements(&children_);
135 MenuItem* MenuItem::ReleaseChild(const Id& child_id,
136 bool recursive) {
137 for (List::iterator i = children_.begin(); i != children_.end(); ++i) {
138 MenuItem* child = NULL;
139 if ((*i)->id() == child_id) {
140 child = *i;
141 children_.erase(i);
142 return child;
143 } else if (recursive) {
144 child = (*i)->ReleaseChild(child_id, recursive);
145 if (child)
146 return child;
149 return NULL;
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() {
159 std::set<Id> result;
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_);
167 return result;
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 ReplaceSubstringsAfterOffset(&result, 0, base::ASCIIToUTF16("%s"), selection);
177 if (result.length() > max_length)
178 result = gfx::TruncateString(result, max_length);
179 return result;
182 bool MenuItem::SetChecked(bool checked) {
183 if (type_ != CHECKBOX && type_ != RADIO)
184 return false;
185 checked_ = checked;
186 return true;
189 void MenuItem::AddChild(MenuItem* item) {
190 item->parent_id_.reset(new Id(id_));
191 children_.push_back(item);
194 scoped_ptr<base::DictionaryValue> MenuItem::ToValue() const {
195 scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue);
196 // Should only be called for extensions with event pages, which only have
197 // string IDs for items.
198 DCHECK_EQ(0, id_.uid);
199 value->SetString(kStringUIDKey, id_.string_uid);
200 value->SetBoolean(kIncognitoKey, id_.incognito);
201 value->SetInteger(kTypeKey, type_);
202 if (type_ != SEPARATOR)
203 value->SetString(kTitleKey, title_);
204 if (type_ == CHECKBOX || type_ == RADIO)
205 value->SetBoolean(kCheckedKey, checked_);
206 value->SetBoolean(kEnabledKey, enabled_);
207 value->Set(kContextsKey, contexts_.ToValue().release());
208 if (parent_id_) {
209 DCHECK_EQ(0, parent_id_->uid);
210 value->SetString(kParentUIDKey, parent_id_->string_uid);
212 value->Set(kDocumentURLPatternsKey,
213 document_url_patterns_.ToValue().release());
214 value->Set(kTargetURLPatternsKey, target_url_patterns_.ToValue().release());
215 return value.Pass();
218 // static
219 MenuItem* MenuItem::Populate(const std::string& extension_id,
220 const base::DictionaryValue& value,
221 std::string* error) {
222 bool incognito = false;
223 if (!value.GetBoolean(kIncognitoKey, &incognito))
224 return NULL;
225 Id id(incognito, extension_id);
226 if (!value.GetString(kStringUIDKey, &id.string_uid))
227 return NULL;
228 int type_int;
229 Type type = NORMAL;
230 if (!value.GetInteger(kTypeKey, &type_int))
231 return NULL;
232 type = static_cast<Type>(type_int);
233 std::string title;
234 if (type != SEPARATOR && !value.GetString(kTitleKey, &title))
235 return NULL;
236 bool checked = false;
237 if ((type == CHECKBOX || type == RADIO) &&
238 !value.GetBoolean(kCheckedKey, &checked)) {
239 return NULL;
241 bool enabled = true;
242 if (!value.GetBoolean(kEnabledKey, &enabled))
243 return NULL;
244 ContextList contexts;
245 const base::Value* contexts_value = NULL;
246 if (!value.Get(kContextsKey, &contexts_value))
247 return NULL;
248 if (!contexts.Populate(*contexts_value))
249 return NULL;
251 scoped_ptr<MenuItem> result(new MenuItem(
252 id, title, checked, enabled, type, contexts));
254 std::vector<std::string> document_url_patterns;
255 if (!GetStringList(value, kDocumentURLPatternsKey, &document_url_patterns))
256 return NULL;
257 std::vector<std::string> target_url_patterns;
258 if (!GetStringList(value, kTargetURLPatternsKey, &target_url_patterns))
259 return NULL;
261 if (!result->PopulateURLPatterns(&document_url_patterns,
262 &target_url_patterns,
263 error)) {
264 return NULL;
267 // parent_id is filled in from the value, but it might not be valid. It's left
268 // to be validated upon being added (via AddChildItem) to the menu manager.
269 scoped_ptr<Id> parent_id(new Id(incognito, extension_id));
270 if (value.HasKey(kParentUIDKey)) {
271 if (!value.GetString(kParentUIDKey, &parent_id->string_uid))
272 return NULL;
273 result->parent_id_.swap(parent_id);
275 return result.release();
278 bool MenuItem::PopulateURLPatterns(
279 std::vector<std::string>* document_url_patterns,
280 std::vector<std::string>* target_url_patterns,
281 std::string* error) {
282 if (document_url_patterns) {
283 if (!document_url_patterns_.Populate(
284 *document_url_patterns, URLPattern::SCHEME_ALL, true, error)) {
285 return false;
288 if (target_url_patterns) {
289 if (!target_url_patterns_.Populate(
290 *target_url_patterns, URLPattern::SCHEME_ALL, true, error)) {
291 return false;
294 return true;
297 MenuManager::MenuManager(Profile* profile, StateStore* store)
298 : profile_(profile), store_(store) {
299 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED,
300 content::Source<Profile>(profile));
301 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
302 content::Source<Profile>(profile));
303 registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED,
304 content::NotificationService::AllSources());
305 if (store_)
306 store_->RegisterKey(kContextMenusKey);
309 MenuManager::~MenuManager() {
310 MenuItemMap::iterator i;
311 for (i = context_items_.begin(); i != context_items_.end(); ++i) {
312 STLDeleteElements(&(i->second));
316 // static
317 MenuManager* MenuManager::Get(Profile* profile) {
318 return MenuManagerFactory::GetForProfile(profile);
321 std::set<std::string> MenuManager::ExtensionIds() {
322 std::set<std::string> id_set;
323 for (MenuItemMap::const_iterator i = context_items_.begin();
324 i != context_items_.end(); ++i) {
325 id_set.insert(i->first);
327 return id_set;
330 const MenuItem::List* MenuManager::MenuItems(
331 const std::string& extension_id) {
332 MenuItemMap::iterator i = context_items_.find(extension_id);
333 if (i != context_items_.end()) {
334 return &(i->second);
336 return NULL;
339 bool MenuManager::AddContextItem(
340 const Extension* extension,
341 MenuItem* item) {
342 const std::string& extension_id = item->extension_id();
343 // The item must have a non-empty extension id, and not have already been
344 // added.
345 if (extension_id.empty() || ContainsKey(items_by_id_, item->id()))
346 return false;
348 DCHECK_EQ(extension->id(), extension_id);
350 bool first_item = !ContainsKey(context_items_, extension_id);
351 context_items_[extension_id].push_back(item);
352 items_by_id_[item->id()] = item;
354 if (item->type() == MenuItem::RADIO) {
355 if (item->checked())
356 RadioItemSelected(item);
357 else
358 SanitizeRadioList(context_items_[extension_id]);
361 // If this is the first item for this extension, start loading its icon.
362 if (first_item)
363 icon_manager_.LoadIcon(profile_, extension);
365 return true;
368 bool MenuManager::AddChildItem(const MenuItem::Id& parent_id,
369 MenuItem* child) {
370 MenuItem* parent = GetItemById(parent_id);
371 if (!parent || parent->type() != MenuItem::NORMAL ||
372 parent->incognito() != child->incognito() ||
373 parent->extension_id() != child->extension_id() ||
374 ContainsKey(items_by_id_, child->id()))
375 return false;
376 parent->AddChild(child);
377 items_by_id_[child->id()] = child;
379 if (child->type() == MenuItem::RADIO)
380 SanitizeRadioList(parent->children());
381 return true;
384 bool MenuManager::DescendantOf(MenuItem* item,
385 const MenuItem::Id& ancestor_id) {
386 // Work our way up the tree until we find the ancestor or NULL.
387 MenuItem::Id* id = item->parent_id();
388 while (id != NULL) {
389 DCHECK(*id != item->id()); // Catch circular graphs.
390 if (*id == ancestor_id)
391 return true;
392 MenuItem* next = GetItemById(*id);
393 if (!next) {
394 NOTREACHED();
395 return false;
397 id = next->parent_id();
399 return false;
402 bool MenuManager::ChangeParent(const MenuItem::Id& child_id,
403 const MenuItem::Id* parent_id) {
404 MenuItem* child = GetItemById(child_id);
405 MenuItem* new_parent = parent_id ? GetItemById(*parent_id) : NULL;
406 if ((parent_id && (child_id == *parent_id)) || !child ||
407 (!new_parent && parent_id != NULL) ||
408 (new_parent && (DescendantOf(new_parent, child_id) ||
409 child->incognito() != new_parent->incognito() ||
410 child->extension_id() != new_parent->extension_id())))
411 return false;
413 MenuItem::Id* old_parent_id = child->parent_id();
414 if (old_parent_id != NULL) {
415 MenuItem* old_parent = GetItemById(*old_parent_id);
416 if (!old_parent) {
417 NOTREACHED();
418 return false;
420 MenuItem* taken =
421 old_parent->ReleaseChild(child_id, false /* non-recursive search*/);
422 DCHECK(taken == child);
423 SanitizeRadioList(old_parent->children());
424 } else {
425 // This is a top-level item, so we need to pull it out of our list of
426 // top-level items.
427 MenuItemMap::iterator i = context_items_.find(child->extension_id());
428 if (i == context_items_.end()) {
429 NOTREACHED();
430 return false;
432 MenuItem::List& list = i->second;
433 MenuItem::List::iterator j = std::find(list.begin(), list.end(),
434 child);
435 if (j == list.end()) {
436 NOTREACHED();
437 return false;
439 list.erase(j);
440 SanitizeRadioList(list);
443 if (new_parent) {
444 new_parent->AddChild(child);
445 SanitizeRadioList(new_parent->children());
446 } else {
447 context_items_[child->extension_id()].push_back(child);
448 child->parent_id_.reset(NULL);
449 SanitizeRadioList(context_items_[child->extension_id()]);
451 return true;
454 bool MenuManager::RemoveContextMenuItem(const MenuItem::Id& id) {
455 if (!ContainsKey(items_by_id_, id))
456 return false;
458 MenuItem* menu_item = GetItemById(id);
459 DCHECK(menu_item);
460 std::string extension_id = menu_item->extension_id();
461 MenuItemMap::iterator i = context_items_.find(extension_id);
462 if (i == context_items_.end()) {
463 NOTREACHED();
464 return false;
467 bool result = false;
468 std::set<MenuItem::Id> items_removed;
469 MenuItem::List& list = i->second;
470 MenuItem::List::iterator j;
471 for (j = list.begin(); j < list.end(); ++j) {
472 // See if the current top-level item is a match.
473 if ((*j)->id() == id) {
474 items_removed = (*j)->RemoveAllDescendants();
475 items_removed.insert(id);
476 delete *j;
477 list.erase(j);
478 result = true;
479 SanitizeRadioList(list);
480 break;
481 } else {
482 // See if the item to remove was found as a descendant of the current
483 // top-level item.
484 MenuItem* child = (*j)->ReleaseChild(id, true /* recursive */);
485 if (child) {
486 items_removed = child->RemoveAllDescendants();
487 items_removed.insert(id);
488 SanitizeRadioList(GetItemById(*child->parent_id())->children());
489 delete child;
490 result = true;
491 break;
495 DCHECK(result); // The check at the very top should have prevented this.
497 // Clear entries from the items_by_id_ map.
498 std::set<MenuItem::Id>::iterator removed_iter;
499 for (removed_iter = items_removed.begin();
500 removed_iter != items_removed.end();
501 ++removed_iter) {
502 items_by_id_.erase(*removed_iter);
505 if (list.empty()) {
506 context_items_.erase(extension_id);
507 icon_manager_.RemoveIcon(extension_id);
509 return result;
512 void MenuManager::RemoveAllContextItems(const std::string& extension_id) {
513 MenuItem::List::iterator i;
514 for (i = context_items_[extension_id].begin();
515 i != context_items_[extension_id].end(); ++i) {
516 MenuItem* item = *i;
517 items_by_id_.erase(item->id());
519 // Remove descendants from this item and erase them from the lookup cache.
520 std::set<MenuItem::Id> removed_ids = item->RemoveAllDescendants();
521 std::set<MenuItem::Id>::const_iterator j;
522 for (j = removed_ids.begin(); j != removed_ids.end(); ++j) {
523 items_by_id_.erase(*j);
526 STLDeleteElements(&context_items_[extension_id]);
527 context_items_.erase(extension_id);
528 icon_manager_.RemoveIcon(extension_id);
531 MenuItem* MenuManager::GetItemById(const MenuItem::Id& id) const {
532 std::map<MenuItem::Id, MenuItem*>::const_iterator i =
533 items_by_id_.find(id);
534 if (i != items_by_id_.end())
535 return i->second;
536 else
537 return NULL;
540 void MenuManager::RadioItemSelected(MenuItem* item) {
541 // If this is a child item, we need to get a handle to the list from its
542 // parent. Otherwise get a handle to the top-level list.
543 const MenuItem::List* list = NULL;
544 if (item->parent_id()) {
545 MenuItem* parent = GetItemById(*item->parent_id());
546 if (!parent) {
547 NOTREACHED();
548 return;
550 list = &(parent->children());
551 } else {
552 if (context_items_.find(item->extension_id()) == context_items_.end()) {
553 NOTREACHED();
554 return;
556 list = &context_items_[item->extension_id()];
559 // Find where |item| is in the list.
560 MenuItem::List::const_iterator item_location;
561 for (item_location = list->begin(); item_location != list->end();
562 ++item_location) {
563 if (*item_location == item)
564 break;
566 if (item_location == list->end()) {
567 NOTREACHED(); // We should have found the item.
568 return;
571 // Iterate backwards from |item| and uncheck any adjacent radio items.
572 MenuItem::List::const_iterator i;
573 if (item_location != list->begin()) {
574 i = item_location;
575 do {
576 --i;
577 if ((*i)->type() != MenuItem::RADIO)
578 break;
579 (*i)->SetChecked(false);
580 } while (i != list->begin());
583 // Now iterate forwards from |item| and uncheck any adjacent radio items.
584 for (i = item_location + 1; i != list->end(); ++i) {
585 if ((*i)->type() != MenuItem::RADIO)
586 break;
587 (*i)->SetChecked(false);
591 static void AddURLProperty(base::DictionaryValue* dictionary,
592 const std::string& key, const GURL& url) {
593 if (!url.is_empty())
594 dictionary->SetString(key, url.possibly_invalid_spec());
597 void MenuManager::ExecuteCommand(Profile* profile,
598 WebContents* web_contents,
599 const content::ContextMenuParams& params,
600 const MenuItem::Id& menu_item_id) {
601 EventRouter* event_router = extensions::ExtensionSystem::Get(profile)->
602 event_router();
603 if (!event_router)
604 return;
606 MenuItem* item = GetItemById(menu_item_id);
607 if (!item)
608 return;
610 // ExtensionService/Extension can be NULL in unit tests :(
611 ExtensionService* service =
612 ExtensionSystem::Get(profile_)->extension_service();
613 const Extension* extension = service ?
614 service->extensions()->GetByID(menu_item_id.extension_id) : NULL;
616 if (item->type() == MenuItem::RADIO)
617 RadioItemSelected(item);
619 scoped_ptr<base::ListValue> args(new base::ListValue());
621 base::DictionaryValue* properties = new base::DictionaryValue();
622 SetIdKeyValue(properties, "menuItemId", item->id());
623 if (item->parent_id())
624 SetIdKeyValue(properties, "parentMenuItemId", *item->parent_id());
626 switch (params.media_type) {
627 case blink::WebContextMenuData::MediaTypeImage:
628 properties->SetString("mediaType", "image");
629 break;
630 case blink::WebContextMenuData::MediaTypeVideo:
631 properties->SetString("mediaType", "video");
632 break;
633 case blink::WebContextMenuData::MediaTypeAudio:
634 properties->SetString("mediaType", "audio");
635 break;
636 default: {} // Do nothing.
639 AddURLProperty(properties, "linkUrl", params.unfiltered_link_url);
640 AddURLProperty(properties, "srcUrl", params.src_url);
641 AddURLProperty(properties, "pageUrl", params.page_url);
642 AddURLProperty(properties, "frameUrl", params.frame_url);
644 if (params.selection_text.length() > 0)
645 properties->SetString("selectionText", params.selection_text);
647 properties->SetBoolean("editable", params.is_editable);
649 args->Append(properties);
651 // Add the tab info to the argument list.
652 // No tab info in a platform app.
653 if (!extension || !extension->is_platform_app()) {
654 // Note: web_contents are NULL in unit tests :(
655 if (web_contents) {
656 args->Append(ExtensionTabUtil::CreateTabValue(web_contents));
657 } else {
658 args->Append(new base::DictionaryValue());
662 if (item->type() == MenuItem::CHECKBOX ||
663 item->type() == MenuItem::RADIO) {
664 bool was_checked = item->checked();
665 properties->SetBoolean("wasChecked", was_checked);
667 // RADIO items always get set to true when you click on them, but CHECKBOX
668 // items get their state toggled.
669 bool checked =
670 (item->type() == MenuItem::RADIO) ? true : !was_checked;
672 item->SetChecked(checked);
673 properties->SetBoolean("checked", item->checked());
675 if (extension)
676 WriteToStorage(extension);
679 // Note: web_contents are NULL in unit tests :(
680 if (web_contents && extensions::TabHelper::FromWebContents(web_contents)) {
681 extensions::TabHelper::FromWebContents(web_contents)->
682 active_tab_permission_granter()->GrantIfRequested(extension);
686 scoped_ptr<Event> event(new Event(
687 event_names::kOnContextMenus,
688 scoped_ptr<base::ListValue>(args->DeepCopy())));
689 event->restrict_to_browser_context = profile;
690 event->user_gesture = EventRouter::USER_GESTURE_ENABLED;
691 event_router->DispatchEventToExtension(item->extension_id(), event.Pass());
694 scoped_ptr<Event> event(new Event(context_menus::OnClicked::kEventName,
695 args.Pass()));
696 event->restrict_to_browser_context = profile;
697 event->user_gesture = EventRouter::USER_GESTURE_ENABLED;
698 event_router->DispatchEventToExtension(item->extension_id(), event.Pass());
702 void MenuManager::SanitizeRadioList(const MenuItem::List& item_list) {
703 MenuItem::List::const_iterator i = item_list.begin();
704 while (i != item_list.end()) {
705 if ((*i)->type() != MenuItem::RADIO) {
706 ++i;
707 break;
710 // Uncheck any checked radio items in the run, and at the end reset
711 // the appropriate one to checked. If no check radio items were found,
712 // then check the first radio item in the run.
713 MenuItem::List::const_iterator last_checked = item_list.end();
714 MenuItem::List::const_iterator radio_run_iter;
715 for (radio_run_iter = i; radio_run_iter != item_list.end();
716 ++radio_run_iter) {
717 if ((*radio_run_iter)->type() != MenuItem::RADIO) {
718 break;
721 if ((*radio_run_iter)->checked()) {
722 last_checked = radio_run_iter;
723 (*radio_run_iter)->SetChecked(false);
727 if (last_checked != item_list.end())
728 (*last_checked)->SetChecked(true);
729 else
730 (*i)->SetChecked(true);
732 i = radio_run_iter;
736 bool MenuManager::ItemUpdated(const MenuItem::Id& id) {
737 if (!ContainsKey(items_by_id_, id))
738 return false;
740 MenuItem* menu_item = GetItemById(id);
741 DCHECK(menu_item);
743 if (menu_item->parent_id()) {
744 SanitizeRadioList(GetItemById(*menu_item->parent_id())->children());
745 } else {
746 std::string extension_id = menu_item->extension_id();
747 MenuItemMap::iterator i = context_items_.find(extension_id);
748 if (i == context_items_.end()) {
749 NOTREACHED();
750 return false;
752 SanitizeRadioList(i->second);
755 return true;
758 void MenuManager::WriteToStorage(const Extension* extension) {
759 if (!BackgroundInfo::HasLazyBackgroundPage(extension))
760 return;
761 const MenuItem::List* top_items = MenuItems(extension->id());
762 MenuItem::List all_items;
763 if (top_items) {
764 for (MenuItem::List::const_iterator i = top_items->begin();
765 i != top_items->end(); ++i) {
766 (*i)->GetFlattenedSubtree(&all_items);
770 if (store_)
771 store_->SetExtensionValue(extension->id(), kContextMenusKey,
772 MenuItemsToValue(all_items));
775 void MenuManager::ReadFromStorage(const std::string& extension_id,
776 scoped_ptr<base::Value> value) {
777 const Extension* extension =
778 ExtensionSystem::Get(profile_)->extension_service()->extensions()->
779 GetByID(extension_id);
780 if (!extension)
781 return;
783 MenuItem::List items = MenuItemsFromValue(extension_id, value.get());
784 for (size_t i = 0; i < items.size(); ++i) {
785 if (items[i]->parent_id()) {
786 // Parent IDs are stored in the parent_id field for convenience, but
787 // they have not yet been validated. Separate them out here.
788 // Because of the order in which we store items in the prefs, parents will
789 // precede children, so we should already know about any parent items.
790 scoped_ptr<MenuItem::Id> parent_id;
791 parent_id.swap(items[i]->parent_id_);
792 AddChildItem(*parent_id, items[i]);
793 } else {
794 AddContextItem(extension, items[i]);
799 void MenuManager::Observe(int type,
800 const content::NotificationSource& source,
801 const content::NotificationDetails& details) {
802 switch (type) {
803 case chrome::NOTIFICATION_EXTENSION_UNLOADED: {
804 // Remove menu items for disabled/uninstalled extensions.
805 const Extension* extension =
806 content::Details<UnloadedExtensionInfo>(details)->extension;
807 if (ContainsKey(context_items_, extension->id())) {
808 RemoveAllContextItems(extension->id());
810 break;
812 case chrome::NOTIFICATION_EXTENSION_LOADED: {
813 const Extension* extension =
814 content::Details<const Extension>(details).ptr();
815 if (store_ && BackgroundInfo::HasLazyBackgroundPage(extension)) {
816 store_->GetExtensionValue(extension->id(), kContextMenusKey,
817 base::Bind(&MenuManager::ReadFromStorage,
818 AsWeakPtr(), extension->id()));
820 break;
822 case chrome::NOTIFICATION_PROFILE_DESTROYED: {
823 Profile* profile = content::Source<Profile>(source).ptr();
824 // We cannot use profile_->HasOffTheRecordProfile as it may already be
825 // false at this point, if for example the incognito profile was destroyed
826 // using DestroyOffTheRecordProfile.
827 if (profile->GetOriginalProfile() == profile_ &&
828 profile->GetOriginalProfile() != profile) {
829 RemoveAllIncognitoContextItems();
831 break;
833 default:
834 NOTREACHED();
835 break;
839 const SkBitmap& MenuManager::GetIconForExtension(
840 const std::string& extension_id) {
841 return icon_manager_.GetIcon(extension_id);
844 void MenuManager::RemoveAllIncognitoContextItems() {
845 // Get all context menu items with "incognito" set to "split".
846 std::set<MenuItem::Id> items_to_remove;
847 std::map<MenuItem::Id, MenuItem*>::const_iterator iter;
848 for (iter = items_by_id_.begin();
849 iter != items_by_id_.end();
850 ++iter) {
851 if (iter->first.incognito)
852 items_to_remove.insert(iter->first);
855 std::set<MenuItem::Id>::iterator remove_iter;
856 for (remove_iter = items_to_remove.begin();
857 remove_iter != items_to_remove.end();
858 ++remove_iter)
859 RemoveContextMenuItem(*remove_iter);
862 MenuItem::Id::Id() : incognito(false), uid(0) {}
864 MenuItem::Id::Id(bool incognito, const std::string& extension_id)
865 : incognito(incognito), extension_id(extension_id), uid(0) {}
867 MenuItem::Id::~Id() {
870 bool MenuItem::Id::operator==(const Id& other) const {
871 return (incognito == other.incognito &&
872 extension_id == other.extension_id &&
873 uid == other.uid &&
874 string_uid == other.string_uid);
877 bool MenuItem::Id::operator!=(const Id& other) const {
878 return !(*this == other);
881 bool MenuItem::Id::operator<(const Id& other) const {
882 if (incognito < other.incognito)
883 return true;
884 if (incognito == other.incognito) {
885 if (extension_id < other.extension_id)
886 return true;
887 if (extension_id == other.extension_id) {
888 if (uid < other.uid)
889 return true;
890 if (uid == other.uid)
891 return string_uid < other.string_uid;
894 return false;
897 } // namespace extensions