<webview>: Context menu API implementation CL.
[chromium-blink-merge.git] / chrome / browser / extensions / menu_manager.cc
blob12097669f618e80f9efeffaa06eda9d833e8a7b5
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_tab_util.h"
19 #include "chrome/browser/extensions/menu_manager_factory.h"
20 #include "chrome/browser/extensions/state_store.h"
21 #include "chrome/browser/extensions/tab_helper.h"
22 #include "chrome/browser/guestview/webview/webview_guest.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/browser/extension_system.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::WebContents;
38 using extensions::ExtensionSystem;
40 namespace extensions {
42 namespace context_menus = api::context_menus;
44 namespace {
46 // Keys for serialization to and from Value to store in the preferences.
47 const char kContextMenusKey[] = "context_menus";
49 const char kCheckedKey[] = "checked";
50 const char kContextsKey[] = "contexts";
51 const char kDocumentURLPatternsKey[] = "document_url_patterns";
52 const char kEnabledKey[] = "enabled";
53 const char kIncognitoKey[] = "incognito";
54 const char kParentUIDKey[] = "parent_uid";
55 const char kStringUIDKey[] = "string_uid";
56 const char kTargetURLPatternsKey[] = "target_url_patterns";
57 const char kTitleKey[] = "title";
58 const char kTypeKey[] = "type";
60 void SetIdKeyValue(base::DictionaryValue* properties,
61 const char* key,
62 const MenuItem::Id& id) {
63 if (id.uid == 0)
64 properties->SetString(key, id.string_uid);
65 else
66 properties->SetInteger(key, id.uid);
69 MenuItem::List MenuItemsFromValue(const std::string& extension_id,
70 base::Value* value) {
71 MenuItem::List items;
73 base::ListValue* list = NULL;
74 if (!value || !value->GetAsList(&list))
75 return items;
77 for (size_t i = 0; i < list->GetSize(); ++i) {
78 base::DictionaryValue* dict = NULL;
79 if (!list->GetDictionary(i, &dict))
80 continue;
81 MenuItem* item = MenuItem::Populate(
82 extension_id, *dict, NULL);
83 if (!item)
84 continue;
85 items.push_back(item);
87 return items;
90 scoped_ptr<base::Value> MenuItemsToValue(const MenuItem::List& items) {
91 scoped_ptr<base::ListValue> list(new base::ListValue());
92 for (size_t i = 0; i < items.size(); ++i)
93 list->Append(items[i]->ToValue().release());
94 return scoped_ptr<base::Value>(list.release());
97 bool GetStringList(const base::DictionaryValue& dict,
98 const std::string& key,
99 std::vector<std::string>* out) {
100 if (!dict.HasKey(key))
101 return true;
103 const base::ListValue* list = NULL;
104 if (!dict.GetListWithoutPathExpansion(key, &list))
105 return false;
107 for (size_t i = 0; i < list->GetSize(); ++i) {
108 std::string pattern;
109 if (!list->GetString(i, &pattern))
110 return false;
111 out->push_back(pattern);
114 return true;
117 } // namespace
119 MenuItem::MenuItem(const Id& id,
120 const std::string& title,
121 bool checked,
122 bool enabled,
123 Type type,
124 const ContextList& contexts)
125 : id_(id),
126 title_(title),
127 type_(type),
128 checked_(checked),
129 enabled_(enabled),
130 contexts_(contexts) {}
132 MenuItem::~MenuItem() {
133 STLDeleteElements(&children_);
136 MenuItem* MenuItem::ReleaseChild(const Id& child_id,
137 bool recursive) {
138 for (List::iterator i = children_.begin(); i != children_.end(); ++i) {
139 MenuItem* child = NULL;
140 if ((*i)->id() == child_id) {
141 child = *i;
142 children_.erase(i);
143 return child;
144 } else if (recursive) {
145 child = (*i)->ReleaseChild(child_id, recursive);
146 if (child)
147 return child;
150 return NULL;
153 void MenuItem::GetFlattenedSubtree(MenuItem::List* list) {
154 list->push_back(this);
155 for (List::iterator i = children_.begin(); i != children_.end(); ++i)
156 (*i)->GetFlattenedSubtree(list);
159 std::set<MenuItem::Id> MenuItem::RemoveAllDescendants() {
160 std::set<Id> result;
161 for (List::iterator i = children_.begin(); i != children_.end(); ++i) {
162 MenuItem* child = *i;
163 result.insert(child->id());
164 std::set<Id> removed = child->RemoveAllDescendants();
165 result.insert(removed.begin(), removed.end());
167 STLDeleteElements(&children_);
168 return result;
171 base::string16 MenuItem::TitleWithReplacement(const base::string16& selection,
172 size_t max_length) const {
173 base::string16 result = base::UTF8ToUTF16(title_);
174 // TODO(asargent) - Change this to properly handle %% escaping so you can
175 // put "%s" in titles that won't get substituted.
176 ReplaceSubstringsAfterOffset(&result, 0, base::ASCIIToUTF16("%s"), selection);
178 if (result.length() > max_length)
179 result = gfx::TruncateString(result, max_length);
180 return result;
183 bool MenuItem::SetChecked(bool checked) {
184 if (type_ != CHECKBOX && type_ != RADIO)
185 return false;
186 checked_ = checked;
187 return true;
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());
209 if (parent_id_) {
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());
216 return value.Pass();
219 // static
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))
225 return NULL;
226 Id id(incognito, MenuItem::ExtensionKey(extension_id));
227 if (!value.GetString(kStringUIDKey, &id.string_uid))
228 return NULL;
229 int type_int;
230 Type type = NORMAL;
231 if (!value.GetInteger(kTypeKey, &type_int))
232 return NULL;
233 type = static_cast<Type>(type_int);
234 std::string title;
235 if (type != SEPARATOR && !value.GetString(kTitleKey, &title))
236 return NULL;
237 bool checked = false;
238 if ((type == CHECKBOX || type == RADIO) &&
239 !value.GetBoolean(kCheckedKey, &checked)) {
240 return NULL;
242 bool enabled = true;
243 if (!value.GetBoolean(kEnabledKey, &enabled))
244 return NULL;
245 ContextList contexts;
246 const base::Value* contexts_value = NULL;
247 if (!value.Get(kContextsKey, &contexts_value))
248 return NULL;
249 if (!contexts.Populate(*contexts_value))
250 return NULL;
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))
257 return NULL;
258 std::vector<std::string> target_url_patterns;
259 if (!GetStringList(value, kTargetURLPatternsKey, &target_url_patterns))
260 return NULL;
262 if (!result->PopulateURLPatterns(&document_url_patterns,
263 &target_url_patterns,
264 error)) {
265 return NULL;
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))
274 return NULL;
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)) {
287 return false;
290 if (target_url_patterns) {
291 if (!target_url_patterns_.Populate(
292 *target_url_patterns, URLPattern::SCHEME_ALL, true, error)) {
293 return false;
296 return true;
299 MenuManager::MenuManager(Profile* profile, StateStore* store)
300 : profile_(profile), store_(store) {
301 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED,
302 content::Source<Profile>(profile));
303 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
304 content::Source<Profile>(profile));
305 registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED,
306 content::NotificationService::AllSources());
307 if (store_)
308 store_->RegisterKey(kContextMenusKey);
311 MenuManager::~MenuManager() {
312 MenuItemMap::iterator i;
313 for (i = context_items_.begin(); i != context_items_.end(); ++i) {
314 STLDeleteElements(&(i->second));
318 // static
319 MenuManager* MenuManager::Get(Profile* profile) {
320 return MenuManagerFactory::GetForProfile(profile);
323 std::set<MenuItem::ExtensionKey> MenuManager::ExtensionIds() {
324 std::set<MenuItem::ExtensionKey> id_set;
325 for (MenuItemMap::const_iterator i = context_items_.begin();
326 i != context_items_.end(); ++i) {
327 id_set.insert(i->first);
329 return id_set;
332 const MenuItem::List* MenuManager::MenuItems(
333 const MenuItem::ExtensionKey& key) {
334 MenuItemMap::iterator i = context_items_.find(key);
335 if (i != context_items_.end()) {
336 return &(i->second);
338 return NULL;
341 bool MenuManager::AddContextItem(const Extension* extension, MenuItem* item) {
342 const MenuItem::ExtensionKey& key = item->id().extension_key;
343 // The item must have a non-empty extension id, and not have already been
344 // added.
345 if (key.empty() || ContainsKey(items_by_id_, item->id()))
346 return false;
348 DCHECK_EQ(extension->id(), key.extension_id);
350 bool first_item = !ContainsKey(context_items_, key);
351 context_items_[key].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_[key]);
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 const MenuItem::ExtensionKey& child_key = child->id().extension_key;
428 MenuItemMap::iterator i = context_items_.find(child_key);
429 if (i == context_items_.end()) {
430 NOTREACHED();
431 return false;
433 MenuItem::List& list = i->second;
434 MenuItem::List::iterator j = std::find(list.begin(), list.end(), 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 const MenuItem::ExtensionKey& child_key = child->id().extension_key;
448 context_items_[child_key].push_back(child);
449 child->parent_id_.reset(NULL);
450 SanitizeRadioList(context_items_[child_key]);
452 return true;
455 bool MenuManager::RemoveContextMenuItem(const MenuItem::Id& id) {
456 if (!ContainsKey(items_by_id_, id))
457 return false;
459 MenuItem* menu_item = GetItemById(id);
460 DCHECK(menu_item);
461 const MenuItem::ExtensionKey extension_key = id.extension_key;
462 MenuItemMap::iterator i = context_items_.find(extension_key);
463 if (i == context_items_.end()) {
464 NOTREACHED();
465 return false;
468 bool result = false;
469 std::set<MenuItem::Id> items_removed;
470 MenuItem::List& list = i->second;
471 MenuItem::List::iterator j;
472 for (j = list.begin(); j < list.end(); ++j) {
473 // See if the current top-level item is a match.
474 if ((*j)->id() == id) {
475 items_removed = (*j)->RemoveAllDescendants();
476 items_removed.insert(id);
477 delete *j;
478 list.erase(j);
479 result = true;
480 SanitizeRadioList(list);
481 break;
482 } else {
483 // See if the item to remove was found as a descendant of the current
484 // top-level item.
485 MenuItem* child = (*j)->ReleaseChild(id, true /* recursive */);
486 if (child) {
487 items_removed = child->RemoveAllDescendants();
488 items_removed.insert(id);
489 SanitizeRadioList(GetItemById(*child->parent_id())->children());
490 delete child;
491 result = true;
492 break;
496 DCHECK(result); // The check at the very top should have prevented this.
498 // Clear entries from the items_by_id_ map.
499 std::set<MenuItem::Id>::iterator removed_iter;
500 for (removed_iter = items_removed.begin();
501 removed_iter != items_removed.end();
502 ++removed_iter) {
503 items_by_id_.erase(*removed_iter);
506 if (list.empty()) {
507 context_items_.erase(extension_key);
508 icon_manager_.RemoveIcon(extension_key.extension_id);
510 return result;
513 void MenuManager::RemoveAllContextItems(
514 const MenuItem::ExtensionKey& extension_key) {
515 MenuItem::List::iterator i;
516 for (i = context_items_[extension_key].begin();
517 i != context_items_[extension_key].end();
518 ++i) {
519 MenuItem* item = *i;
520 items_by_id_.erase(item->id());
522 // Remove descendants from this item and erase them from the lookup cache.
523 std::set<MenuItem::Id> removed_ids = item->RemoveAllDescendants();
524 std::set<MenuItem::Id>::const_iterator j;
525 for (j = removed_ids.begin(); j != removed_ids.end(); ++j) {
526 items_by_id_.erase(*j);
529 STLDeleteElements(&context_items_[extension_key]);
530 context_items_.erase(extension_key);
531 icon_manager_.RemoveIcon(extension_key.extension_id);
534 MenuItem* MenuManager::GetItemById(const MenuItem::Id& id) const {
535 std::map<MenuItem::Id, MenuItem*>::const_iterator i =
536 items_by_id_.find(id);
537 if (i != items_by_id_.end())
538 return i->second;
539 else
540 return NULL;
543 void MenuManager::RadioItemSelected(MenuItem* item) {
544 // If this is a child item, we need to get a handle to the list from its
545 // parent. Otherwise get a handle to the top-level list.
546 const MenuItem::List* list = NULL;
547 if (item->parent_id()) {
548 MenuItem* parent = GetItemById(*item->parent_id());
549 if (!parent) {
550 NOTREACHED();
551 return;
553 list = &(parent->children());
554 } else {
555 const MenuItem::ExtensionKey& key = item->id().extension_key;
556 if (context_items_.find(key) == context_items_.end()) {
557 NOTREACHED();
558 return;
560 list = &context_items_[key];
563 // Find where |item| is in the list.
564 MenuItem::List::const_iterator item_location;
565 for (item_location = list->begin(); item_location != list->end();
566 ++item_location) {
567 if (*item_location == item)
568 break;
570 if (item_location == list->end()) {
571 NOTREACHED(); // We should have found the item.
572 return;
575 // Iterate backwards from |item| and uncheck any adjacent radio items.
576 MenuItem::List::const_iterator i;
577 if (item_location != list->begin()) {
578 i = item_location;
579 do {
580 --i;
581 if ((*i)->type() != MenuItem::RADIO)
582 break;
583 (*i)->SetChecked(false);
584 } while (i != list->begin());
587 // Now iterate forwards from |item| and uncheck any adjacent radio items.
588 for (i = item_location + 1; i != list->end(); ++i) {
589 if ((*i)->type() != MenuItem::RADIO)
590 break;
591 (*i)->SetChecked(false);
595 static void AddURLProperty(base::DictionaryValue* dictionary,
596 const std::string& key, const GURL& url) {
597 if (!url.is_empty())
598 dictionary->SetString(key, url.possibly_invalid_spec());
601 void MenuManager::ExecuteCommand(Profile* profile,
602 WebContents* web_contents,
603 const content::ContextMenuParams& params,
604 const MenuItem::Id& menu_item_id) {
605 EventRouter* event_router = extensions::ExtensionSystem::Get(profile)->
606 event_router();
607 if (!event_router)
608 return;
610 MenuItem* item = GetItemById(menu_item_id);
611 if (!item)
612 return;
614 // ExtensionService/Extension can be NULL in unit tests :(
615 ExtensionService* service =
616 ExtensionSystem::Get(profile_)->extension_service();
617 const Extension* extension =
618 service ? service->extensions()->GetByID(item->extension_id()) : NULL;
620 if (item->type() == MenuItem::RADIO)
621 RadioItemSelected(item);
623 scoped_ptr<base::ListValue> args(new base::ListValue());
625 base::DictionaryValue* properties = new base::DictionaryValue();
626 SetIdKeyValue(properties, "menuItemId", item->id());
627 if (item->parent_id())
628 SetIdKeyValue(properties, "parentMenuItemId", *item->parent_id());
630 switch (params.media_type) {
631 case blink::WebContextMenuData::MediaTypeImage:
632 properties->SetString("mediaType", "image");
633 break;
634 case blink::WebContextMenuData::MediaTypeVideo:
635 properties->SetString("mediaType", "video");
636 break;
637 case blink::WebContextMenuData::MediaTypeAudio:
638 properties->SetString("mediaType", "audio");
639 break;
640 default: {} // Do nothing.
643 AddURLProperty(properties, "linkUrl", params.unfiltered_link_url);
644 AddURLProperty(properties, "srcUrl", params.src_url);
645 AddURLProperty(properties, "pageUrl", params.page_url);
646 AddURLProperty(properties, "frameUrl", params.frame_url);
648 if (params.selection_text.length() > 0)
649 properties->SetString("selectionText", params.selection_text);
651 properties->SetBoolean("editable", params.is_editable);
653 WebViewGuest* webview_guest = WebViewGuest::FromWebContents(web_contents);
654 if (webview_guest) {
655 // This is used in webview_custom_bindings.js.
656 // The property is not exposed to developer API.
657 properties->SetInteger("webviewInstanceId",
658 webview_guest->view_instance_id());
661 args->Append(properties);
663 // Add the tab info to the argument list.
664 // No tab info in a platform app.
665 if (!extension || !extension->is_platform_app()) {
666 // Note: web_contents are NULL in unit tests :(
667 if (web_contents) {
668 args->Append(ExtensionTabUtil::CreateTabValue(web_contents));
669 } else {
670 args->Append(new base::DictionaryValue());
674 if (item->type() == MenuItem::CHECKBOX ||
675 item->type() == MenuItem::RADIO) {
676 bool was_checked = item->checked();
677 properties->SetBoolean("wasChecked", was_checked);
679 // RADIO items always get set to true when you click on them, but CHECKBOX
680 // items get their state toggled.
681 bool checked =
682 (item->type() == MenuItem::RADIO) ? true : !was_checked;
684 item->SetChecked(checked);
685 properties->SetBoolean("checked", item->checked());
687 if (extension)
688 WriteToStorage(extension, item->id().extension_key);
691 // Note: web_contents are NULL in unit tests :(
692 if (web_contents && extensions::TabHelper::FromWebContents(web_contents)) {
693 extensions::TabHelper::FromWebContents(web_contents)->
694 active_tab_permission_granter()->GrantIfRequested(extension);
698 // Dispatch to menu item's .onclick handler.
699 scoped_ptr<Event> event(new Event(
700 event_names::kOnContextMenus,
701 scoped_ptr<base::ListValue>(args->DeepCopy())));
702 event->restrict_to_browser_context = profile;
703 event->user_gesture = EventRouter::USER_GESTURE_ENABLED;
704 event_router->DispatchEventToExtension(item->extension_id(), event.Pass());
707 // Dispatch to .contextMenus.onClicked handler.
708 scoped_ptr<Event> event(
709 new Event(webview_guest ? event_names::kOnWebviewContextMenus
710 : context_menus::OnClicked::kEventName,
711 args.Pass()));
712 event->restrict_to_browser_context = profile;
713 event->user_gesture = EventRouter::USER_GESTURE_ENABLED;
714 if (webview_guest)
715 event->filter_info.SetInstanceID(webview_guest->view_instance_id());
716 event_router->DispatchEventToExtension(item->extension_id(), event.Pass());
720 void MenuManager::SanitizeRadioList(const MenuItem::List& item_list) {
721 MenuItem::List::const_iterator i = item_list.begin();
722 while (i != item_list.end()) {
723 if ((*i)->type() != MenuItem::RADIO) {
724 ++i;
725 break;
728 // Uncheck any checked radio items in the run, and at the end reset
729 // the appropriate one to checked. If no check radio items were found,
730 // then check the first radio item in the run.
731 MenuItem::List::const_iterator last_checked = item_list.end();
732 MenuItem::List::const_iterator radio_run_iter;
733 for (radio_run_iter = i; radio_run_iter != item_list.end();
734 ++radio_run_iter) {
735 if ((*radio_run_iter)->type() != MenuItem::RADIO) {
736 break;
739 if ((*radio_run_iter)->checked()) {
740 last_checked = radio_run_iter;
741 (*radio_run_iter)->SetChecked(false);
745 if (last_checked != item_list.end())
746 (*last_checked)->SetChecked(true);
747 else
748 (*i)->SetChecked(true);
750 i = radio_run_iter;
754 bool MenuManager::ItemUpdated(const MenuItem::Id& id) {
755 if (!ContainsKey(items_by_id_, id))
756 return false;
758 MenuItem* menu_item = GetItemById(id);
759 DCHECK(menu_item);
761 if (menu_item->parent_id()) {
762 SanitizeRadioList(GetItemById(*menu_item->parent_id())->children());
763 } else {
764 MenuItemMap::iterator i =
765 context_items_.find(menu_item->id().extension_key);
766 if (i == context_items_.end()) {
767 NOTREACHED();
768 return false;
770 SanitizeRadioList(i->second);
773 return true;
776 void MenuManager::WriteToStorage(const Extension* extension,
777 const MenuItem::ExtensionKey& extension_key) {
778 if (!BackgroundInfo::HasLazyBackgroundPage(extension))
779 return;
780 // <webview> menu items are transient and not stored in storage.
781 if (extension_key.webview_instance_id)
782 return;
783 const MenuItem::List* top_items = MenuItems(extension_key);
784 MenuItem::List all_items;
785 if (top_items) {
786 for (MenuItem::List::const_iterator i = top_items->begin();
787 i != top_items->end(); ++i) {
788 DCHECK(!(*i)->id().extension_key.webview_instance_id);
789 (*i)->GetFlattenedSubtree(&all_items);
793 if (store_) {
794 store_->SetExtensionValue(extension->id(), kContextMenusKey,
795 MenuItemsToValue(all_items));
799 void MenuManager::ReadFromStorage(const std::string& extension_id,
800 scoped_ptr<base::Value> value) {
801 const Extension* extension =
802 ExtensionSystem::Get(profile_)->extension_service()->extensions()->
803 GetByID(extension_id);
804 if (!extension)
805 return;
807 MenuItem::List items = MenuItemsFromValue(extension_id, value.get());
808 for (size_t i = 0; i < items.size(); ++i) {
809 if (items[i]->parent_id()) {
810 // Parent IDs are stored in the parent_id field for convenience, but
811 // they have not yet been validated. Separate them out here.
812 // Because of the order in which we store items in the prefs, parents will
813 // precede children, so we should already know about any parent items.
814 scoped_ptr<MenuItem::Id> parent_id;
815 parent_id.swap(items[i]->parent_id_);
816 AddChildItem(*parent_id, items[i]);
817 } else {
818 AddContextItem(extension, items[i]);
823 void MenuManager::Observe(int type,
824 const content::NotificationSource& source,
825 const content::NotificationDetails& details) {
826 switch (type) {
827 case chrome::NOTIFICATION_EXTENSION_UNLOADED: {
828 // Remove menu items for disabled/uninstalled extensions.
829 const Extension* extension =
830 content::Details<UnloadedExtensionInfo>(details)->extension;
831 MenuItem::ExtensionKey extension_key(extension->id());
832 if (ContainsKey(context_items_, extension_key)) {
833 RemoveAllContextItems(extension_key);
835 break;
837 case chrome::NOTIFICATION_EXTENSION_LOADED: {
838 const Extension* extension =
839 content::Details<const Extension>(details).ptr();
840 if (store_ && BackgroundInfo::HasLazyBackgroundPage(extension)) {
841 store_->GetExtensionValue(extension->id(), kContextMenusKey,
842 base::Bind(&MenuManager::ReadFromStorage,
843 AsWeakPtr(), extension->id()));
845 break;
847 case chrome::NOTIFICATION_PROFILE_DESTROYED: {
848 Profile* profile = content::Source<Profile>(source).ptr();
849 // We cannot use profile_->HasOffTheRecordProfile as it may already be
850 // false at this point, if for example the incognito profile was destroyed
851 // using DestroyOffTheRecordProfile.
852 if (profile->GetOriginalProfile() == profile_ &&
853 profile->GetOriginalProfile() != profile) {
854 RemoveAllIncognitoContextItems();
856 break;
858 default:
859 NOTREACHED();
860 break;
864 const SkBitmap& MenuManager::GetIconForExtension(
865 const std::string& extension_id) {
866 return icon_manager_.GetIcon(extension_id);
869 void MenuManager::RemoveAllIncognitoContextItems() {
870 // Get all context menu items with "incognito" set to "split".
871 std::set<MenuItem::Id> items_to_remove;
872 std::map<MenuItem::Id, MenuItem*>::const_iterator iter;
873 for (iter = items_by_id_.begin();
874 iter != items_by_id_.end();
875 ++iter) {
876 if (iter->first.incognito)
877 items_to_remove.insert(iter->first);
880 std::set<MenuItem::Id>::iterator remove_iter;
881 for (remove_iter = items_to_remove.begin();
882 remove_iter != items_to_remove.end();
883 ++remove_iter)
884 RemoveContextMenuItem(*remove_iter);
887 MenuItem::ExtensionKey::ExtensionKey() : webview_instance_id(0) {}
889 MenuItem::ExtensionKey::ExtensionKey(const std::string& extension_id,
890 int webview_instance_id)
891 : extension_id(extension_id), webview_instance_id(webview_instance_id) {}
893 MenuItem::ExtensionKey::ExtensionKey(const std::string& extension_id)
894 : extension_id(extension_id), webview_instance_id(0) {}
896 bool MenuItem::ExtensionKey::operator==(const ExtensionKey& other) const {
897 return extension_id == other.extension_id &&
898 webview_instance_id == other.webview_instance_id;
901 bool MenuItem::ExtensionKey::operator<(const ExtensionKey& other) const {
902 if (extension_id != other.extension_id)
903 return extension_id < other.extension_id;
905 return webview_instance_id < other.webview_instance_id;
908 bool MenuItem::ExtensionKey::operator!=(const ExtensionKey& other) const {
909 return !(*this == other);
912 bool MenuItem::ExtensionKey::empty() const {
913 return extension_id.empty() && !webview_instance_id;
916 MenuItem::Id::Id() : incognito(false), uid(0) {}
918 MenuItem::Id::Id(bool incognito, const MenuItem::ExtensionKey& extension_key)
919 : incognito(incognito), extension_key(extension_key), uid(0) {}
921 MenuItem::Id::~Id() {
924 bool MenuItem::Id::operator==(const Id& other) const {
925 return (incognito == other.incognito &&
926 extension_key == other.extension_key && uid == other.uid &&
927 string_uid == other.string_uid);
930 bool MenuItem::Id::operator!=(const Id& other) const {
931 return !(*this == other);
934 bool MenuItem::Id::operator<(const Id& other) const {
935 if (incognito < other.incognito)
936 return true;
937 if (incognito == other.incognito) {
938 if (extension_key < other.extension_key)
939 return true;
940 if (extension_key == other.extension_key) {
941 if (uid < other.uid)
942 return true;
943 if (uid == other.uid)
944 return string_uid < other.string_uid;
947 return false;
950 } // namespace extensions