Add ICU message format support
[chromium-blink-merge.git] / chrome / browser / extensions / menu_manager.cc
blobe49ab629d3637e2b1466ad8b681c88592db96088
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/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 {
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 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);
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 // static
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),
307 store_(store) {
308 extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
309 registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED,
310 content::NotificationService::AllSources());
311 if (store_)
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));
322 // static
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);
333 return id_set;
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()) {
340 return &(i->second);
342 return NULL;
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()))
350 return false;
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) {
359 if (item->checked())
360 RadioItemSelected(item);
361 else
362 SanitizeRadioList(context_items_[key]);
365 // If this is the first item for this extension, start loading its icon.
366 if (first_item)
367 icon_manager_.LoadIcon(browser_context_, extension);
369 return true;
372 bool MenuManager::AddChildItem(const MenuItem::Id& parent_id,
373 MenuItem* child) {
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()))
379 return false;
380 parent->AddChild(child);
381 items_by_id_[child->id()] = child;
383 if (child->type() == MenuItem::RADIO)
384 SanitizeRadioList(parent->children());
385 return true;
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();
392 while (id != NULL) {
393 DCHECK(*id != item->id()); // Catch circular graphs.
394 if (*id == ancestor_id)
395 return true;
396 MenuItem* next = GetItemById(*id);
397 if (!next) {
398 NOTREACHED();
399 return false;
401 id = next->parent_id();
403 return false;
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())))
415 return false;
417 MenuItem::Id* old_parent_id = child->parent_id();
418 if (old_parent_id != NULL) {
419 MenuItem* old_parent = GetItemById(*old_parent_id);
420 if (!old_parent) {
421 NOTREACHED();
422 return false;
424 MenuItem* taken =
425 old_parent->ReleaseChild(child_id, false /* non-recursive search*/);
426 DCHECK(taken == child);
427 SanitizeRadioList(old_parent->children());
428 } else {
429 // This is a top-level item, so we need to pull it out of our list of
430 // top-level items.
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()) {
434 NOTREACHED();
435 return false;
437 MenuItem::List& list = i->second;
438 MenuItem::List::iterator j = std::find(list.begin(), list.end(), child);
439 if (j == list.end()) {
440 NOTREACHED();
441 return false;
443 list.erase(j);
444 SanitizeRadioList(list);
447 if (new_parent) {
448 new_parent->AddChild(child);
449 SanitizeRadioList(new_parent->children());
450 } else {
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]);
456 return true;
459 bool MenuManager::RemoveContextMenuItem(const MenuItem::Id& id) {
460 if (!ContainsKey(items_by_id_, id))
461 return false;
463 MenuItem* menu_item = GetItemById(id);
464 DCHECK(menu_item);
465 const MenuItem::ExtensionKey extension_key = id.extension_key;
466 MenuItemMap::iterator i = context_items_.find(extension_key);
467 if (i == context_items_.end()) {
468 NOTREACHED();
469 return false;
472 bool result = false;
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);
481 delete *j;
482 list.erase(j);
483 result = true;
484 SanitizeRadioList(list);
485 break;
486 } else {
487 // See if the item to remove was found as a descendant of the current
488 // top-level item.
489 MenuItem* child = (*j)->ReleaseChild(id, true /* recursive */);
490 if (child) {
491 items_removed = child->RemoveAllDescendants();
492 items_removed.insert(id);
493 SanitizeRadioList(GetItemById(*child->parent_id())->children());
494 delete child;
495 result = true;
496 break;
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();
506 ++removed_iter) {
507 items_by_id_.erase(*removed_iter);
510 if (list.empty()) {
511 context_items_.erase(extension_key);
512 icon_manager_.RemoveIcon(extension_key.extension_id);
514 return result;
517 void MenuManager::RemoveAllContextItems(
518 const MenuItem::ExtensionKey& extension_key) {
519 auto it = context_items_.find(extension_key);
520 if (it == context_items_.end())
521 return;
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
525 // webview IDs).
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();
533 ++i) {
534 MenuItem* item = *i;
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())
553 return i->second;
554 else
555 return NULL;
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());
564 if (!parent) {
565 NOTREACHED();
566 return;
568 list = &(parent->children());
569 } else {
570 const MenuItem::ExtensionKey& key = item->id().extension_key;
571 if (context_items_.find(key) == context_items_.end()) {
572 NOTREACHED();
573 return;
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();
581 ++item_location) {
582 if (*item_location == item)
583 break;
585 if (item_location == list->end()) {
586 NOTREACHED(); // We should have found the item.
587 return;
590 // Iterate backwards from |item| and uncheck any adjacent radio items.
591 MenuItem::List::const_iterator i;
592 if (item_location != list->begin()) {
593 i = item_location;
594 do {
595 --i;
596 if ((*i)->type() != MenuItem::RADIO)
597 break;
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)
605 break;
606 (*i)->SetChecked(false);
610 static void AddURLProperty(base::DictionaryValue* dictionary,
611 const std::string& key, const GURL& url) {
612 if (!url.is_empty())
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);
621 if (!event_router)
622 return;
624 MenuItem* item = GetItemById(menu_item_id);
625 if (!item)
626 return;
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");
645 break;
646 case blink::WebContextMenuData::MediaTypeVideo:
647 properties->SetString("mediaType", "video");
648 break;
649 case blink::WebContextMenuData::MediaTypeAudio:
650 properties->SetString("mediaType", "audio");
651 break;
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);
666 if (webview_guest) {
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 :(
679 if (web_contents) {
680 args->Append(ExtensionTabUtil::CreateTabValue(web_contents));
681 } else {
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.
693 bool checked =
694 (item->type() == MenuItem::RADIO) ? true : !was_checked;
696 item->SetChecked(checked);
697 properties->SetBoolean("checked", item->checked());
699 if (extension)
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,
729 args.Pass()));
730 event->restrict_to_browser_context = context;
731 event->user_gesture = EventRouter::USER_GESTURE_ENABLED;
732 if (webview_guest)
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) {
742 ++i;
743 break;
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();
752 ++radio_run_iter) {
753 if ((*radio_run_iter)->type() != MenuItem::RADIO) {
754 break;
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);
765 else
766 (*i)->SetChecked(true);
768 i = radio_run_iter;
772 bool MenuManager::ItemUpdated(const MenuItem::Id& id) {
773 if (!ContainsKey(items_by_id_, id))
774 return false;
776 MenuItem* menu_item = GetItemById(id);
777 DCHECK(menu_item);
779 if (menu_item->parent_id()) {
780 SanitizeRadioList(GetItemById(*menu_item->parent_id())->children());
781 } else {
782 MenuItemMap::iterator i =
783 context_items_.find(menu_item->id().extension_key);
784 if (i == context_items_.end()) {
785 NOTREACHED();
786 return false;
788 SanitizeRadioList(i->second);
791 return true;
794 void MenuManager::WriteToStorage(const Extension* extension,
795 const MenuItem::ExtensionKey& extension_key) {
796 if (!BackgroundInfo::HasLazyBackgroundPage(extension))
797 return;
798 // <webview> menu items are transient and not stored in storage.
799 if (extension_key.webview_instance_id)
800 return;
801 const MenuItem::List* top_items = MenuItems(extension_key);
802 MenuItem::List all_items;
803 if (top_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);
811 if (store_) {
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);
822 if (!extension)
823 return;
825 MenuItem::List items = MenuItemsFromValue(extension_id, value.get());
826 for (size_t i = 0; i < items.size(); ++i) {
827 bool added = false;
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]);
837 } else {
838 added = AddContextItem(extension, items[i]);
841 if (!added)
842 delete items[i];
846 void MenuManager::OnExtensionLoaded(content::BrowserContext* browser_context,
847 const Extension* extension) {
848 if (store_ && BackgroundInfo::HasLazyBackgroundPage(extension)) {
849 store_->GetExtensionValue(
850 extension->id(),
851 kContextMenusKey,
852 base::Bind(
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();
891 ++iter) {
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();
899 ++remove_iter)
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())
946 return false;
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)
981 return true;
982 if (incognito == other.incognito) {
983 if (extension_key < other.extension_key)
984 return true;
985 if (extension_key == other.extension_key) {
986 if (uid < other.uid)
987 return true;
988 if (uid == other.uid)
989 return string_uid < other.string_uid;
992 return false;
995 } // namespace extensions