app_list: Re-enable people search.
[chromium-blink-merge.git] / chrome / browser / extensions / menu_manager.cc
blob95993c9906ec963d80a5a8d44210152a3ea07fcf
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/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/context_menu_params.h"
27 #include "extensions/browser/event_router.h"
28 #include "extensions/browser/extension_registry.h"
29 #include "extensions/browser/guest_view/web_view/web_view_guest.h"
30 #include "extensions/browser/state_store.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;
38 namespace extensions {
40 namespace context_menus = api::context_menus;
41 namespace chrome_web_view = api::chrome_web_view_internal;
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, gfx::WORD_BREAK);
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, MenuItem::ExtensionKey(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(
270 new Id(incognito, MenuItem::ExtensionKey(extension_id)));
271 if (value.HasKey(kParentUIDKey)) {
272 if (!value.GetString(kParentUIDKey, &parent_id->string_uid))
273 return NULL;
274 result->parent_id_.swap(parent_id);
276 return result.release();
279 bool MenuItem::PopulateURLPatterns(
280 std::vector<std::string>* document_url_patterns,
281 std::vector<std::string>* target_url_patterns,
282 std::string* error) {
283 if (document_url_patterns) {
284 if (!document_url_patterns_.Populate(
285 *document_url_patterns, URLPattern::SCHEME_ALL, true, error)) {
286 return false;
289 if (target_url_patterns) {
290 if (!target_url_patterns_.Populate(
291 *target_url_patterns, URLPattern::SCHEME_ALL, true, error)) {
292 return false;
295 return true;
298 // static
299 const char MenuManager::kOnContextMenus[] = "contextMenus";
300 const char MenuManager::kOnWebviewContextMenus[] =
301 "webViewInternal.contextMenus";
303 MenuManager::MenuManager(content::BrowserContext* context, StateStore* store)
304 : extension_registry_observer_(this),
305 browser_context_(context),
306 store_(store) {
307 extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
308 registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED,
309 content::NotificationService::AllSources());
310 if (store_)
311 store_->RegisterKey(kContextMenusKey);
314 MenuManager::~MenuManager() {
315 MenuItemMap::iterator i;
316 for (i = context_items_.begin(); i != context_items_.end(); ++i) {
317 STLDeleteElements(&(i->second));
321 // static
322 MenuManager* MenuManager::Get(content::BrowserContext* context) {
323 return MenuManagerFactory::GetForBrowserContext(context);
326 std::set<MenuItem::ExtensionKey> MenuManager::ExtensionIds() {
327 std::set<MenuItem::ExtensionKey> id_set;
328 for (MenuItemMap::const_iterator i = context_items_.begin();
329 i != context_items_.end(); ++i) {
330 id_set.insert(i->first);
332 return id_set;
335 const MenuItem::List* MenuManager::MenuItems(
336 const MenuItem::ExtensionKey& key) {
337 MenuItemMap::iterator i = context_items_.find(key);
338 if (i != context_items_.end()) {
339 return &(i->second);
341 return NULL;
344 bool MenuManager::AddContextItem(const Extension* extension, MenuItem* item) {
345 const MenuItem::ExtensionKey& key = item->id().extension_key;
346 // The item must have a non-empty extension id, and not have already been
347 // added.
348 if (key.empty() || ContainsKey(items_by_id_, item->id()))
349 return false;
351 DCHECK_EQ(extension->id(), key.extension_id);
353 bool first_item = !ContainsKey(context_items_, key);
354 context_items_[key].push_back(item);
355 items_by_id_[item->id()] = item;
357 if (item->type() == MenuItem::RADIO) {
358 if (item->checked())
359 RadioItemSelected(item);
360 else
361 SanitizeRadioList(context_items_[key]);
364 // If this is the first item for this extension, start loading its icon.
365 if (first_item)
366 icon_manager_.LoadIcon(browser_context_, extension);
368 return true;
371 bool MenuManager::AddChildItem(const MenuItem::Id& parent_id,
372 MenuItem* child) {
373 MenuItem* parent = GetItemById(parent_id);
374 if (!parent || parent->type() != MenuItem::NORMAL ||
375 parent->incognito() != child->incognito() ||
376 parent->extension_id() != child->extension_id() ||
377 ContainsKey(items_by_id_, child->id()))
378 return false;
379 parent->AddChild(child);
380 items_by_id_[child->id()] = child;
382 if (child->type() == MenuItem::RADIO)
383 SanitizeRadioList(parent->children());
384 return true;
387 bool MenuManager::DescendantOf(MenuItem* item,
388 const MenuItem::Id& ancestor_id) {
389 // Work our way up the tree until we find the ancestor or NULL.
390 MenuItem::Id* id = item->parent_id();
391 while (id != NULL) {
392 DCHECK(*id != item->id()); // Catch circular graphs.
393 if (*id == ancestor_id)
394 return true;
395 MenuItem* next = GetItemById(*id);
396 if (!next) {
397 NOTREACHED();
398 return false;
400 id = next->parent_id();
402 return false;
405 bool MenuManager::ChangeParent(const MenuItem::Id& child_id,
406 const MenuItem::Id* parent_id) {
407 MenuItem* child = GetItemById(child_id);
408 MenuItem* new_parent = parent_id ? GetItemById(*parent_id) : NULL;
409 if ((parent_id && (child_id == *parent_id)) || !child ||
410 (!new_parent && parent_id != NULL) ||
411 (new_parent && (DescendantOf(new_parent, child_id) ||
412 child->incognito() != new_parent->incognito() ||
413 child->extension_id() != new_parent->extension_id())))
414 return false;
416 MenuItem::Id* old_parent_id = child->parent_id();
417 if (old_parent_id != NULL) {
418 MenuItem* old_parent = GetItemById(*old_parent_id);
419 if (!old_parent) {
420 NOTREACHED();
421 return false;
423 MenuItem* taken =
424 old_parent->ReleaseChild(child_id, false /* non-recursive search*/);
425 DCHECK(taken == child);
426 SanitizeRadioList(old_parent->children());
427 } else {
428 // This is a top-level item, so we need to pull it out of our list of
429 // top-level items.
430 const MenuItem::ExtensionKey& child_key = child->id().extension_key;
431 MenuItemMap::iterator i = context_items_.find(child_key);
432 if (i == context_items_.end()) {
433 NOTREACHED();
434 return false;
436 MenuItem::List& list = i->second;
437 MenuItem::List::iterator j = std::find(list.begin(), list.end(), child);
438 if (j == list.end()) {
439 NOTREACHED();
440 return false;
442 list.erase(j);
443 SanitizeRadioList(list);
446 if (new_parent) {
447 new_parent->AddChild(child);
448 SanitizeRadioList(new_parent->children());
449 } else {
450 const MenuItem::ExtensionKey& child_key = child->id().extension_key;
451 context_items_[child_key].push_back(child);
452 child->parent_id_.reset(NULL);
453 SanitizeRadioList(context_items_[child_key]);
455 return true;
458 bool MenuManager::RemoveContextMenuItem(const MenuItem::Id& id) {
459 if (!ContainsKey(items_by_id_, id))
460 return false;
462 MenuItem* menu_item = GetItemById(id);
463 DCHECK(menu_item);
464 const MenuItem::ExtensionKey extension_key = id.extension_key;
465 MenuItemMap::iterator i = context_items_.find(extension_key);
466 if (i == context_items_.end()) {
467 NOTREACHED();
468 return false;
471 bool result = false;
472 std::set<MenuItem::Id> items_removed;
473 MenuItem::List& list = i->second;
474 MenuItem::List::iterator j;
475 for (j = list.begin(); j < list.end(); ++j) {
476 // See if the current top-level item is a match.
477 if ((*j)->id() == id) {
478 items_removed = (*j)->RemoveAllDescendants();
479 items_removed.insert(id);
480 delete *j;
481 list.erase(j);
482 result = true;
483 SanitizeRadioList(list);
484 break;
485 } else {
486 // See if the item to remove was found as a descendant of the current
487 // top-level item.
488 MenuItem* child = (*j)->ReleaseChild(id, true /* recursive */);
489 if (child) {
490 items_removed = child->RemoveAllDescendants();
491 items_removed.insert(id);
492 SanitizeRadioList(GetItemById(*child->parent_id())->children());
493 delete child;
494 result = true;
495 break;
499 DCHECK(result); // The check at the very top should have prevented this.
501 // Clear entries from the items_by_id_ map.
502 std::set<MenuItem::Id>::iterator removed_iter;
503 for (removed_iter = items_removed.begin();
504 removed_iter != items_removed.end();
505 ++removed_iter) {
506 items_by_id_.erase(*removed_iter);
509 if (list.empty()) {
510 context_items_.erase(extension_key);
511 icon_manager_.RemoveIcon(extension_key.extension_id);
513 return result;
516 void MenuManager::RemoveAllContextItems(
517 const MenuItem::ExtensionKey& extension_key) {
518 MenuItem::List::iterator i;
519 for (i = context_items_[extension_key].begin();
520 i != context_items_[extension_key].end();
521 ++i) {
522 MenuItem* item = *i;
523 items_by_id_.erase(item->id());
525 // Remove descendants from this item and erase them from the lookup cache.
526 std::set<MenuItem::Id> removed_ids = item->RemoveAllDescendants();
527 std::set<MenuItem::Id>::const_iterator j;
528 for (j = removed_ids.begin(); j != removed_ids.end(); ++j) {
529 items_by_id_.erase(*j);
532 STLDeleteElements(&context_items_[extension_key]);
533 context_items_.erase(extension_key);
534 icon_manager_.RemoveIcon(extension_key.extension_id);
537 MenuItem* MenuManager::GetItemById(const MenuItem::Id& id) const {
538 std::map<MenuItem::Id, MenuItem*>::const_iterator i =
539 items_by_id_.find(id);
540 if (i != items_by_id_.end())
541 return i->second;
542 else
543 return NULL;
546 void MenuManager::RadioItemSelected(MenuItem* item) {
547 // If this is a child item, we need to get a handle to the list from its
548 // parent. Otherwise get a handle to the top-level list.
549 const MenuItem::List* list = NULL;
550 if (item->parent_id()) {
551 MenuItem* parent = GetItemById(*item->parent_id());
552 if (!parent) {
553 NOTREACHED();
554 return;
556 list = &(parent->children());
557 } else {
558 const MenuItem::ExtensionKey& key = item->id().extension_key;
559 if (context_items_.find(key) == context_items_.end()) {
560 NOTREACHED();
561 return;
563 list = &context_items_[key];
566 // Find where |item| is in the list.
567 MenuItem::List::const_iterator item_location;
568 for (item_location = list->begin(); item_location != list->end();
569 ++item_location) {
570 if (*item_location == item)
571 break;
573 if (item_location == list->end()) {
574 NOTREACHED(); // We should have found the item.
575 return;
578 // Iterate backwards from |item| and uncheck any adjacent radio items.
579 MenuItem::List::const_iterator i;
580 if (item_location != list->begin()) {
581 i = item_location;
582 do {
583 --i;
584 if ((*i)->type() != MenuItem::RADIO)
585 break;
586 (*i)->SetChecked(false);
587 } while (i != list->begin());
590 // Now iterate forwards from |item| and uncheck any adjacent radio items.
591 for (i = item_location + 1; i != list->end(); ++i) {
592 if ((*i)->type() != MenuItem::RADIO)
593 break;
594 (*i)->SetChecked(false);
598 static void AddURLProperty(base::DictionaryValue* dictionary,
599 const std::string& key, const GURL& url) {
600 if (!url.is_empty())
601 dictionary->SetString(key, url.possibly_invalid_spec());
604 void MenuManager::ExecuteCommand(content::BrowserContext* context,
605 WebContents* web_contents,
606 const content::ContextMenuParams& params,
607 const MenuItem::Id& menu_item_id) {
608 EventRouter* event_router = EventRouter::Get(context);
609 if (!event_router)
610 return;
612 MenuItem* item = GetItemById(menu_item_id);
613 if (!item)
614 return;
616 ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context_);
617 const Extension* extension =
618 registry->enabled_extensions().GetByID(item->extension_id());
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 web_view_internalcustom_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(
700 new Event(webview_guest ? kOnWebviewContextMenus
701 : kOnContextMenus,
702 scoped_ptr<base::ListValue>(args->DeepCopy())));
703 event->restrict_to_browser_context = context;
704 event->user_gesture = EventRouter::USER_GESTURE_ENABLED;
705 event_router->DispatchEventToExtension(item->extension_id(), event.Pass());
708 // Dispatch to .contextMenus.onClicked handler.
709 scoped_ptr<Event> event(
710 new Event(webview_guest ? chrome_web_view::OnClicked::kEventName
711 : context_menus::OnClicked::kEventName,
712 args.Pass()));
713 event->restrict_to_browser_context = context;
714 event->user_gesture = EventRouter::USER_GESTURE_ENABLED;
715 if (webview_guest)
716 event->filter_info.SetInstanceID(webview_guest->view_instance_id());
717 event_router->DispatchEventToExtension(item->extension_id(), event.Pass());
721 void MenuManager::SanitizeRadioList(const MenuItem::List& item_list) {
722 MenuItem::List::const_iterator i = item_list.begin();
723 while (i != item_list.end()) {
724 if ((*i)->type() != MenuItem::RADIO) {
725 ++i;
726 break;
729 // Uncheck any checked radio items in the run, and at the end reset
730 // the appropriate one to checked. If no check radio items were found,
731 // then check the first radio item in the run.
732 MenuItem::List::const_iterator last_checked = item_list.end();
733 MenuItem::List::const_iterator radio_run_iter;
734 for (radio_run_iter = i; radio_run_iter != item_list.end();
735 ++radio_run_iter) {
736 if ((*radio_run_iter)->type() != MenuItem::RADIO) {
737 break;
740 if ((*radio_run_iter)->checked()) {
741 last_checked = radio_run_iter;
742 (*radio_run_iter)->SetChecked(false);
746 if (last_checked != item_list.end())
747 (*last_checked)->SetChecked(true);
748 else
749 (*i)->SetChecked(true);
751 i = radio_run_iter;
755 bool MenuManager::ItemUpdated(const MenuItem::Id& id) {
756 if (!ContainsKey(items_by_id_, id))
757 return false;
759 MenuItem* menu_item = GetItemById(id);
760 DCHECK(menu_item);
762 if (menu_item->parent_id()) {
763 SanitizeRadioList(GetItemById(*menu_item->parent_id())->children());
764 } else {
765 MenuItemMap::iterator i =
766 context_items_.find(menu_item->id().extension_key);
767 if (i == context_items_.end()) {
768 NOTREACHED();
769 return false;
771 SanitizeRadioList(i->second);
774 return true;
777 void MenuManager::WriteToStorage(const Extension* extension,
778 const MenuItem::ExtensionKey& extension_key) {
779 if (!BackgroundInfo::HasLazyBackgroundPage(extension))
780 return;
781 // <webview> menu items are transient and not stored in storage.
782 if (extension_key.webview_instance_id)
783 return;
784 const MenuItem::List* top_items = MenuItems(extension_key);
785 MenuItem::List all_items;
786 if (top_items) {
787 for (MenuItem::List::const_iterator i = top_items->begin();
788 i != top_items->end(); ++i) {
789 DCHECK(!(*i)->id().extension_key.webview_instance_id);
790 (*i)->GetFlattenedSubtree(&all_items);
794 if (store_) {
795 store_->SetExtensionValue(extension->id(), kContextMenusKey,
796 MenuItemsToValue(all_items));
800 void MenuManager::ReadFromStorage(const std::string& extension_id,
801 scoped_ptr<base::Value> value) {
802 const Extension* extension = ExtensionRegistry::Get(browser_context_)
803 ->enabled_extensions()
804 .GetByID(extension_id);
805 if (!extension)
806 return;
808 MenuItem::List items = MenuItemsFromValue(extension_id, value.get());
809 for (size_t i = 0; i < items.size(); ++i) {
810 bool added = false;
812 if (items[i]->parent_id()) {
813 // Parent IDs are stored in the parent_id field for convenience, but
814 // they have not yet been validated. Separate them out here.
815 // Because of the order in which we store items in the prefs, parents will
816 // precede children, so we should already know about any parent items.
817 scoped_ptr<MenuItem::Id> parent_id;
818 parent_id.swap(items[i]->parent_id_);
819 added = AddChildItem(*parent_id, items[i]);
820 } else {
821 added = AddContextItem(extension, items[i]);
824 if (!added)
825 delete items[i];
829 void MenuManager::OnExtensionLoaded(content::BrowserContext* browser_context,
830 const Extension* extension) {
831 if (store_ && BackgroundInfo::HasLazyBackgroundPage(extension)) {
832 store_->GetExtensionValue(
833 extension->id(),
834 kContextMenusKey,
835 base::Bind(
836 &MenuManager::ReadFromStorage, AsWeakPtr(), extension->id()));
840 void MenuManager::OnExtensionUnloaded(content::BrowserContext* browser_context,
841 const Extension* extension,
842 UnloadedExtensionInfo::Reason reason) {
843 MenuItem::ExtensionKey extension_key(extension->id());
844 if (ContainsKey(context_items_, extension_key)) {
845 RemoveAllContextItems(extension_key);
849 void MenuManager::Observe(int type,
850 const content::NotificationSource& source,
851 const content::NotificationDetails& details) {
852 DCHECK_EQ(chrome::NOTIFICATION_PROFILE_DESTROYED, type);
853 Profile* profile = content::Source<Profile>(source).ptr();
854 // We cannot use profile_->HasOffTheRecordProfile as it may already be
855 // false at this point, if for example the incognito profile was destroyed
856 // using DestroyOffTheRecordProfile.
857 if (profile->GetOriginalProfile() == browser_context_ &&
858 profile->GetOriginalProfile() != profile) {
859 RemoveAllIncognitoContextItems();
863 const SkBitmap& MenuManager::GetIconForExtension(
864 const std::string& extension_id) {
865 return icon_manager_.GetIcon(extension_id);
868 void MenuManager::RemoveAllIncognitoContextItems() {
869 // Get all context menu items with "incognito" set to "split".
870 std::set<MenuItem::Id> items_to_remove;
871 std::map<MenuItem::Id, MenuItem*>::const_iterator iter;
872 for (iter = items_by_id_.begin();
873 iter != items_by_id_.end();
874 ++iter) {
875 if (iter->first.incognito)
876 items_to_remove.insert(iter->first);
879 std::set<MenuItem::Id>::iterator remove_iter;
880 for (remove_iter = items_to_remove.begin();
881 remove_iter != items_to_remove.end();
882 ++remove_iter)
883 RemoveContextMenuItem(*remove_iter);
886 MenuItem::ExtensionKey::ExtensionKey() : webview_instance_id(0) {}
888 MenuItem::ExtensionKey::ExtensionKey(const std::string& extension_id,
889 int webview_instance_id)
890 : extension_id(extension_id), webview_instance_id(webview_instance_id) {}
892 MenuItem::ExtensionKey::ExtensionKey(const std::string& extension_id)
893 : extension_id(extension_id), webview_instance_id(0) {}
895 bool MenuItem::ExtensionKey::operator==(const ExtensionKey& other) const {
896 return extension_id == other.extension_id &&
897 webview_instance_id == other.webview_instance_id;
900 bool MenuItem::ExtensionKey::operator<(const ExtensionKey& other) const {
901 if (extension_id != other.extension_id)
902 return extension_id < other.extension_id;
904 return webview_instance_id < other.webview_instance_id;
907 bool MenuItem::ExtensionKey::operator!=(const ExtensionKey& other) const {
908 return !(*this == other);
911 bool MenuItem::ExtensionKey::empty() const {
912 return extension_id.empty() && !webview_instance_id;
915 MenuItem::Id::Id() : incognito(false), uid(0) {}
917 MenuItem::Id::Id(bool incognito, const MenuItem::ExtensionKey& extension_key)
918 : incognito(incognito), extension_key(extension_key), uid(0) {}
920 MenuItem::Id::~Id() {
923 bool MenuItem::Id::operator==(const Id& other) const {
924 return (incognito == other.incognito &&
925 extension_key == other.extension_key && uid == other.uid &&
926 string_uid == other.string_uid);
929 bool MenuItem::Id::operator!=(const Id& other) const {
930 return !(*this == other);
933 bool MenuItem::Id::operator<(const Id& other) const {
934 if (incognito < other.incognito)
935 return true;
936 if (incognito == other.incognito) {
937 if (extension_key < other.extension_key)
938 return true;
939 if (extension_key == other.extension_key) {
940 if (uid < other.uid)
941 return true;
942 if (uid == other.uid)
943 return string_uid < other.string_uid;
946 return false;
949 } // namespace extensions