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.
4
5#include "chrome/browser/extensions/menu_manager.h"
6
7#include <algorithm>
8
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_service.h"
17#include "chrome/browser/extensions/extension_tab_util.h"
18#include "chrome/browser/extensions/menu_manager_factory.h"
19#include "chrome/browser/extensions/tab_helper.h"
20#include "chrome/browser/profiles/profile.h"
21#include "chrome/common/extensions/api/chrome_web_view_internal.h"
22#include "chrome/common/extensions/api/context_menus.h"
23#include "content/public/browser/notification_details.h"
24#include "content/public/browser/notification_service.h"
25#include "content/public/browser/notification_source.h"
26#include "content/public/browser/web_contents.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/extension_system.h"
31#include "extensions/browser/guest_view/web_view/web_view_guest.h"
32#include "extensions/browser/state_store.h"
33#include "extensions/common/extension.h"
34#include "extensions/common/manifest_handlers/background_info.h"
35#include "ui/gfx/favicon_size.h"
36#include "ui/gfx/text_elider.h"
37
38using content::WebContents;
39using extensions::ExtensionSystem;
40
41namespace extensions {
42
43namespace context_menus = api::context_menus;
44namespace chrome_web_view = api::chrome_web_view_internal;
45
46namespace {
47
48// Keys for serialization to and from Value to store in the preferences.
49const char kContextMenusKey[] = "context_menus";
50
51const char kCheckedKey[] = "checked";
52const char kContextsKey[] = "contexts";
53const char kDocumentURLPatternsKey[] = "document_url_patterns";
54const char kEnabledKey[] = "enabled";
55const char kIncognitoKey[] = "incognito";
56const char kParentUIDKey[] = "parent_uid";
57const char kStringUIDKey[] = "string_uid";
58const char kTargetURLPatternsKey[] = "target_url_patterns";
59const char kTitleKey[] = "title";
60const char kTypeKey[] = "type";
61
62void SetIdKeyValue(base::DictionaryValue* properties,
63                   const char* key,
64                   const MenuItem::Id& id) {
65  if (id.uid == 0)
66    properties->SetString(key, id.string_uid);
67  else
68    properties->SetInteger(key, id.uid);
69}
70
71MenuItem::List MenuItemsFromValue(const std::string& extension_id,
72                                  base::Value* value) {
73  MenuItem::List items;
74
75  base::ListValue* list = NULL;
76  if (!value || !value->GetAsList(&list))
77    return items;
78
79  for (size_t i = 0; i < list->GetSize(); ++i) {
80    base::DictionaryValue* dict = NULL;
81    if (!list->GetDictionary(i, &dict))
82      continue;
83    MenuItem* item = MenuItem::Populate(
84        extension_id, *dict, NULL);
85    if (!item)
86      continue;
87    items.push_back(item);
88  }
89  return items;
90}
91
92scoped_ptr<base::Value> MenuItemsToValue(const MenuItem::List& items) {
93  scoped_ptr<base::ListValue> list(new base::ListValue());
94  for (size_t i = 0; i < items.size(); ++i)
95    list->Append(items[i]->ToValue().release());
96  return scoped_ptr<base::Value>(list.release());
97}
98
99bool GetStringList(const base::DictionaryValue& dict,
100                   const std::string& key,
101                   std::vector<std::string>* out) {
102  if (!dict.HasKey(key))
103    return true;
104
105  const base::ListValue* list = NULL;
106  if (!dict.GetListWithoutPathExpansion(key, &list))
107    return false;
108
109  for (size_t i = 0; i < list->GetSize(); ++i) {
110    std::string pattern;
111    if (!list->GetString(i, &pattern))
112      return false;
113    out->push_back(pattern);
114  }
115
116  return true;
117}
118
119}  // namespace
120
121MenuItem::MenuItem(const Id& id,
122                   const std::string& title,
123                   bool checked,
124                   bool enabled,
125                   Type type,
126                   const ContextList& contexts)
127    : id_(id),
128      title_(title),
129      type_(type),
130      checked_(checked),
131      enabled_(enabled),
132      contexts_(contexts) {}
133
134MenuItem::~MenuItem() {
135  STLDeleteElements(&children_);
136}
137
138MenuItem* MenuItem::ReleaseChild(const Id& child_id,
139                                 bool recursive) {
140  for (List::iterator i = children_.begin(); i != children_.end(); ++i) {
141    MenuItem* child = NULL;
142    if ((*i)->id() == child_id) {
143      child = *i;
144      children_.erase(i);
145      return child;
146    } else if (recursive) {
147      child = (*i)->ReleaseChild(child_id, recursive);
148      if (child)
149        return child;
150    }
151  }
152  return NULL;
153}
154
155void MenuItem::GetFlattenedSubtree(MenuItem::List* list) {
156  list->push_back(this);
157  for (List::iterator i = children_.begin(); i != children_.end(); ++i)
158    (*i)->GetFlattenedSubtree(list);
159}
160
161std::set<MenuItem::Id> MenuItem::RemoveAllDescendants() {
162  std::set<Id> result;
163  for (List::iterator i = children_.begin(); i != children_.end(); ++i) {
164    MenuItem* child = *i;
165    result.insert(child->id());
166    std::set<Id> removed = child->RemoveAllDescendants();
167    result.insert(removed.begin(), removed.end());
168  }
169  STLDeleteElements(&children_);
170  return result;
171}
172
173base::string16 MenuItem::TitleWithReplacement(const base::string16& selection,
174                                              size_t max_length) const {
175  base::string16 result = base::UTF8ToUTF16(title_);
176  // TODO(asargent) - Change this to properly handle %% escaping so you can
177  // put "%s" in titles that won't get substituted.
178  ReplaceSubstringsAfterOffset(&result, 0, base::ASCIIToUTF16("%s"), selection);
179
180  if (result.length() > max_length)
181    result = gfx::TruncateString(result, max_length, gfx::WORD_BREAK);
182  return result;
183}
184
185bool MenuItem::SetChecked(bool checked) {
186  if (type_ != CHECKBOX && type_ != RADIO)
187    return false;
188  checked_ = checked;
189  return true;
190}
191
192void MenuItem::AddChild(MenuItem* item) {
193  item->parent_id_.reset(new Id(id_));
194  children_.push_back(item);
195}
196
197scoped_ptr<base::DictionaryValue> MenuItem::ToValue() const {
198  scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue);
199  // Should only be called for extensions with event pages, which only have
200  // string IDs for items.
201  DCHECK_EQ(0, id_.uid);
202  value->SetString(kStringUIDKey, id_.string_uid);
203  value->SetBoolean(kIncognitoKey, id_.incognito);
204  value->SetInteger(kTypeKey, type_);
205  if (type_ != SEPARATOR)
206    value->SetString(kTitleKey, title_);
207  if (type_ == CHECKBOX || type_ == RADIO)
208    value->SetBoolean(kCheckedKey, checked_);
209  value->SetBoolean(kEnabledKey, enabled_);
210  value->Set(kContextsKey, contexts_.ToValue().release());
211  if (parent_id_) {
212    DCHECK_EQ(0, parent_id_->uid);
213    value->SetString(kParentUIDKey, parent_id_->string_uid);
214  }
215  value->Set(kDocumentURLPatternsKey,
216             document_url_patterns_.ToValue().release());
217  value->Set(kTargetURLPatternsKey, target_url_patterns_.ToValue().release());
218  return value.Pass();
219}
220
221// static
222MenuItem* MenuItem::Populate(const std::string& extension_id,
223                             const base::DictionaryValue& value,
224                             std::string* error) {
225  bool incognito = false;
226  if (!value.GetBoolean(kIncognitoKey, &incognito))
227    return NULL;
228  Id id(incognito, MenuItem::ExtensionKey(extension_id));
229  if (!value.GetString(kStringUIDKey, &id.string_uid))
230    return NULL;
231  int type_int;
232  Type type = NORMAL;
233  if (!value.GetInteger(kTypeKey, &type_int))
234    return NULL;
235  type = static_cast<Type>(type_int);
236  std::string title;
237  if (type != SEPARATOR && !value.GetString(kTitleKey, &title))
238    return NULL;
239  bool checked = false;
240  if ((type == CHECKBOX || type == RADIO) &&
241      !value.GetBoolean(kCheckedKey, &checked)) {
242    return NULL;
243  }
244  bool enabled = true;
245  if (!value.GetBoolean(kEnabledKey, &enabled))
246    return NULL;
247  ContextList contexts;
248  const base::Value* contexts_value = NULL;
249  if (!value.Get(kContextsKey, &contexts_value))
250    return NULL;
251  if (!contexts.Populate(*contexts_value))
252    return NULL;
253
254  scoped_ptr<MenuItem> result(new MenuItem(
255      id, title, checked, enabled, type, contexts));
256
257  std::vector<std::string> document_url_patterns;
258  if (!GetStringList(value, kDocumentURLPatternsKey, &document_url_patterns))
259    return NULL;
260  std::vector<std::string> target_url_patterns;
261  if (!GetStringList(value, kTargetURLPatternsKey, &target_url_patterns))
262    return NULL;
263
264  if (!result->PopulateURLPatterns(&document_url_patterns,
265                                   &target_url_patterns,
266                                   error)) {
267    return NULL;
268  }
269
270  // parent_id is filled in from the value, but it might not be valid. It's left
271  // to be validated upon being added (via AddChildItem) to the menu manager.
272  scoped_ptr<Id> parent_id(
273      new Id(incognito, MenuItem::ExtensionKey(extension_id)));
274  if (value.HasKey(kParentUIDKey)) {
275    if (!value.GetString(kParentUIDKey, &parent_id->string_uid))
276      return NULL;
277    result->parent_id_.swap(parent_id);
278  }
279  return result.release();
280}
281
282bool MenuItem::PopulateURLPatterns(
283    std::vector<std::string>* document_url_patterns,
284    std::vector<std::string>* target_url_patterns,
285    std::string* error) {
286  if (document_url_patterns) {
287    if (!document_url_patterns_.Populate(
288            *document_url_patterns, URLPattern::SCHEME_ALL, true, error)) {
289      return false;
290    }
291  }
292  if (target_url_patterns) {
293    if (!target_url_patterns_.Populate(
294            *target_url_patterns, URLPattern::SCHEME_ALL, true, error)) {
295      return false;
296    }
297  }
298  return true;
299}
300
301// static
302const char MenuManager::kOnContextMenus[] = "contextMenus";
303const char MenuManager::kOnWebviewContextMenus[] =
304    "webViewInternal.contextMenus";
305
306MenuManager::MenuManager(content::BrowserContext* context, StateStore* store)
307    : extension_registry_observer_(this),
308      browser_context_(context),
309      store_(store) {
310  extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
311  registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED,
312                 content::NotificationService::AllSources());
313  if (store_)
314    store_->RegisterKey(kContextMenusKey);
315}
316
317MenuManager::~MenuManager() {
318  MenuItemMap::iterator i;
319  for (i = context_items_.begin(); i != context_items_.end(); ++i) {
320    STLDeleteElements(&(i->second));
321  }
322}
323
324// static
325MenuManager* MenuManager::Get(content::BrowserContext* context) {
326  return MenuManagerFactory::GetForBrowserContext(context);
327}
328
329std::set<MenuItem::ExtensionKey> MenuManager::ExtensionIds() {
330  std::set<MenuItem::ExtensionKey> id_set;
331  for (MenuItemMap::const_iterator i = context_items_.begin();
332       i != context_items_.end(); ++i) {
333    id_set.insert(i->first);
334  }
335  return id_set;
336}
337
338const MenuItem::List* MenuManager::MenuItems(
339    const MenuItem::ExtensionKey& key) {
340  MenuItemMap::iterator i = context_items_.find(key);
341  if (i != context_items_.end()) {
342    return &(i->second);
343  }
344  return NULL;
345}
346
347bool MenuManager::AddContextItem(const Extension* extension, MenuItem* item) {
348  const MenuItem::ExtensionKey& key = item->id().extension_key;
349  // The item must have a non-empty extension id, and not have already been
350  // added.
351  if (key.empty() || ContainsKey(items_by_id_, item->id()))
352    return false;
353
354  DCHECK_EQ(extension->id(), key.extension_id);
355
356  bool first_item = !ContainsKey(context_items_, key);
357  context_items_[key].push_back(item);
358  items_by_id_[item->id()] = item;
359
360  if (item->type() == MenuItem::RADIO) {
361    if (item->checked())
362      RadioItemSelected(item);
363    else
364      SanitizeRadioList(context_items_[key]);
365  }
366
367  // If this is the first item for this extension, start loading its icon.
368  if (first_item)
369    icon_manager_.LoadIcon(browser_context_, extension);
370
371  return true;
372}
373
374bool MenuManager::AddChildItem(const MenuItem::Id& parent_id,
375                               MenuItem* child) {
376  MenuItem* parent = GetItemById(parent_id);
377  if (!parent || parent->type() != MenuItem::NORMAL ||
378      parent->incognito() != child->incognito() ||
379      parent->extension_id() != child->extension_id() ||
380      ContainsKey(items_by_id_, child->id()))
381    return false;
382  parent->AddChild(child);
383  items_by_id_[child->id()] = child;
384
385  if (child->type() == MenuItem::RADIO)
386    SanitizeRadioList(parent->children());
387  return true;
388}
389
390bool MenuManager::DescendantOf(MenuItem* item,
391                               const MenuItem::Id& ancestor_id) {
392  // Work our way up the tree until we find the ancestor or NULL.
393  MenuItem::Id* id = item->parent_id();
394  while (id != NULL) {
395    DCHECK(*id != item->id());  // Catch circular graphs.
396    if (*id == ancestor_id)
397      return true;
398    MenuItem* next = GetItemById(*id);
399    if (!next) {
400      NOTREACHED();
401      return false;
402    }
403    id = next->parent_id();
404  }
405  return false;
406}
407
408bool MenuManager::ChangeParent(const MenuItem::Id& child_id,
409                               const MenuItem::Id* parent_id) {
410  MenuItem* child = GetItemById(child_id);
411  MenuItem* new_parent = parent_id ? GetItemById(*parent_id) : NULL;
412  if ((parent_id && (child_id == *parent_id)) || !child ||
413      (!new_parent && parent_id != NULL) ||
414      (new_parent && (DescendantOf(new_parent, child_id) ||
415                      child->incognito() != new_parent->incognito() ||
416                      child->extension_id() != new_parent->extension_id())))
417    return false;
418
419  MenuItem::Id* old_parent_id = child->parent_id();
420  if (old_parent_id != NULL) {
421    MenuItem* old_parent = GetItemById(*old_parent_id);
422    if (!old_parent) {
423      NOTREACHED();
424      return false;
425    }
426    MenuItem* taken =
427      old_parent->ReleaseChild(child_id, false /* non-recursive search*/);
428    DCHECK(taken == child);
429    SanitizeRadioList(old_parent->children());
430  } else {
431    // This is a top-level item, so we need to pull it out of our list of
432    // top-level items.
433    const MenuItem::ExtensionKey& child_key = child->id().extension_key;
434    MenuItemMap::iterator i = context_items_.find(child_key);
435    if (i == context_items_.end()) {
436      NOTREACHED();
437      return false;
438    }
439    MenuItem::List& list = i->second;
440    MenuItem::List::iterator j = std::find(list.begin(), list.end(), child);
441    if (j == list.end()) {
442      NOTREACHED();
443      return false;
444    }
445    list.erase(j);
446    SanitizeRadioList(list);
447  }
448
449  if (new_parent) {
450    new_parent->AddChild(child);
451    SanitizeRadioList(new_parent->children());
452  } else {
453    const MenuItem::ExtensionKey& child_key = child->id().extension_key;
454    context_items_[child_key].push_back(child);
455    child->parent_id_.reset(NULL);
456    SanitizeRadioList(context_items_[child_key]);
457  }
458  return true;
459}
460
461bool MenuManager::RemoveContextMenuItem(const MenuItem::Id& id) {
462  if (!ContainsKey(items_by_id_, id))
463    return false;
464
465  MenuItem* menu_item = GetItemById(id);
466  DCHECK(menu_item);
467  const MenuItem::ExtensionKey extension_key = id.extension_key;
468  MenuItemMap::iterator i = context_items_.find(extension_key);
469  if (i == context_items_.end()) {
470    NOTREACHED();
471    return false;
472  }
473
474  bool result = false;
475  std::set<MenuItem::Id> items_removed;
476  MenuItem::List& list = i->second;
477  MenuItem::List::iterator j;
478  for (j = list.begin(); j < list.end(); ++j) {
479    // See if the current top-level item is a match.
480    if ((*j)->id() == id) {
481      items_removed = (*j)->RemoveAllDescendants();
482      items_removed.insert(id);
483      delete *j;
484      list.erase(j);
485      result = true;
486      SanitizeRadioList(list);
487      break;
488    } else {
489      // See if the item to remove was found as a descendant of the current
490      // top-level item.
491      MenuItem* child = (*j)->ReleaseChild(id, true /* recursive */);
492      if (child) {
493        items_removed = child->RemoveAllDescendants();
494        items_removed.insert(id);
495        SanitizeRadioList(GetItemById(*child->parent_id())->children());
496        delete child;
497        result = true;
498        break;
499      }
500    }
501  }
502  DCHECK(result);  // The check at the very top should have prevented this.
503
504  // Clear entries from the items_by_id_ map.
505  std::set<MenuItem::Id>::iterator removed_iter;
506  for (removed_iter = items_removed.begin();
507       removed_iter != items_removed.end();
508       ++removed_iter) {
509    items_by_id_.erase(*removed_iter);
510  }
511
512  if (list.empty()) {
513    context_items_.erase(extension_key);
514    icon_manager_.RemoveIcon(extension_key.extension_id);
515  }
516  return result;
517}
518
519void MenuManager::RemoveAllContextItems(
520    const MenuItem::ExtensionKey& extension_key) {
521  MenuItem::List::iterator i;
522  for (i = context_items_[extension_key].begin();
523       i != context_items_[extension_key].end();
524       ++i) {
525    MenuItem* item = *i;
526    items_by_id_.erase(item->id());
527
528    // Remove descendants from this item and erase them from the lookup cache.
529    std::set<MenuItem::Id> removed_ids = item->RemoveAllDescendants();
530    std::set<MenuItem::Id>::const_iterator j;
531    for (j = removed_ids.begin(); j != removed_ids.end(); ++j) {
532      items_by_id_.erase(*j);
533    }
534  }
535  STLDeleteElements(&context_items_[extension_key]);
536  context_items_.erase(extension_key);
537  icon_manager_.RemoveIcon(extension_key.extension_id);
538}
539
540MenuItem* MenuManager::GetItemById(const MenuItem::Id& id) const {
541  std::map<MenuItem::Id, MenuItem*>::const_iterator i =
542      items_by_id_.find(id);
543  if (i != items_by_id_.end())
544    return i->second;
545  else
546    return NULL;
547}
548
549void MenuManager::RadioItemSelected(MenuItem* item) {
550  // If this is a child item, we need to get a handle to the list from its
551  // parent. Otherwise get a handle to the top-level list.
552  const MenuItem::List* list = NULL;
553  if (item->parent_id()) {
554    MenuItem* parent = GetItemById(*item->parent_id());
555    if (!parent) {
556      NOTREACHED();
557      return;
558    }
559    list = &(parent->children());
560  } else {
561    const MenuItem::ExtensionKey& key = item->id().extension_key;
562    if (context_items_.find(key) == context_items_.end()) {
563      NOTREACHED();
564      return;
565    }
566    list = &context_items_[key];
567  }
568
569  // Find where |item| is in the list.
570  MenuItem::List::const_iterator item_location;
571  for (item_location = list->begin(); item_location != list->end();
572       ++item_location) {
573    if (*item_location == item)
574      break;
575  }
576  if (item_location == list->end()) {
577    NOTREACHED();  // We should have found the item.
578    return;
579  }
580
581  // Iterate backwards from |item| and uncheck any adjacent radio items.
582  MenuItem::List::const_iterator i;
583  if (item_location != list->begin()) {
584    i = item_location;
585    do {
586      --i;
587      if ((*i)->type() != MenuItem::RADIO)
588        break;
589      (*i)->SetChecked(false);
590    } while (i != list->begin());
591  }
592
593  // Now iterate forwards from |item| and uncheck any adjacent radio items.
594  for (i = item_location + 1; i != list->end(); ++i) {
595    if ((*i)->type() != MenuItem::RADIO)
596      break;
597    (*i)->SetChecked(false);
598  }
599}
600
601static void AddURLProperty(base::DictionaryValue* dictionary,
602                           const std::string& key, const GURL& url) {
603  if (!url.is_empty())
604    dictionary->SetString(key, url.possibly_invalid_spec());
605}
606
607void MenuManager::ExecuteCommand(content::BrowserContext* context,
608                                 WebContents* web_contents,
609                                 const content::ContextMenuParams& params,
610                                 const MenuItem::Id& menu_item_id) {
611  EventRouter* event_router = EventRouter::Get(context);
612  if (!event_router)
613    return;
614
615  MenuItem* item = GetItemById(menu_item_id);
616  if (!item)
617    return;
618
619  // ExtensionService/Extension can be NULL in unit tests :(
620  ExtensionService* service =
621      ExtensionSystem::Get(browser_context_)->extension_service();
622  const Extension* extension =
623      service ? service->extensions()->GetByID(item->extension_id()) : NULL;
624
625  if (item->type() == MenuItem::RADIO)
626    RadioItemSelected(item);
627
628  scoped_ptr<base::ListValue> args(new base::ListValue());
629
630  base::DictionaryValue* properties = new base::DictionaryValue();
631  SetIdKeyValue(properties, "menuItemId", item->id());
632  if (item->parent_id())
633    SetIdKeyValue(properties, "parentMenuItemId", *item->parent_id());
634
635  switch (params.media_type) {
636    case blink::WebContextMenuData::MediaTypeImage:
637      properties->SetString("mediaType", "image");
638      break;
639    case blink::WebContextMenuData::MediaTypeVideo:
640      properties->SetString("mediaType", "video");
641      break;
642    case blink::WebContextMenuData::MediaTypeAudio:
643      properties->SetString("mediaType", "audio");
644      break;
645    default:  {}  // Do nothing.
646  }
647
648  AddURLProperty(properties, "linkUrl", params.unfiltered_link_url);
649  AddURLProperty(properties, "srcUrl", params.src_url);
650  AddURLProperty(properties, "pageUrl", params.page_url);
651  AddURLProperty(properties, "frameUrl", params.frame_url);
652
653  if (params.selection_text.length() > 0)
654    properties->SetString("selectionText", params.selection_text);
655
656  properties->SetBoolean("editable", params.is_editable);
657
658  WebViewGuest* webview_guest = WebViewGuest::FromWebContents(web_contents);
659  if (webview_guest) {
660    // This is used in web_view_internalcustom_bindings.js.
661    // The property is not exposed to developer API.
662    properties->SetInteger("webviewInstanceId",
663                           webview_guest->view_instance_id());
664  }
665
666  args->Append(properties);
667
668  // Add the tab info to the argument list.
669  // No tab info in a platform app.
670  if (!extension || !extension->is_platform_app()) {
671    // Note: web_contents are NULL in unit tests :(
672    if (web_contents) {
673      args->Append(ExtensionTabUtil::CreateTabValue(web_contents));
674    } else {
675      args->Append(new base::DictionaryValue());
676    }
677  }
678
679  if (item->type() == MenuItem::CHECKBOX ||
680      item->type() == MenuItem::RADIO) {
681    bool was_checked = item->checked();
682    properties->SetBoolean("wasChecked", was_checked);
683
684    // RADIO items always get set to true when you click on them, but CHECKBOX
685    // items get their state toggled.
686    bool checked =
687        (item->type() == MenuItem::RADIO) ? true : !was_checked;
688
689    item->SetChecked(checked);
690    properties->SetBoolean("checked", item->checked());
691
692    if (extension)
693      WriteToStorage(extension, item->id().extension_key);
694  }
695
696  // Note: web_contents are NULL in unit tests :(
697  if (web_contents && extensions::TabHelper::FromWebContents(web_contents)) {
698    extensions::TabHelper::FromWebContents(web_contents)->
699        active_tab_permission_granter()->GrantIfRequested(extension);
700  }
701
702  {
703    // Dispatch to menu item's .onclick handler.
704    scoped_ptr<Event> event(
705        new Event(webview_guest ? kOnWebviewContextMenus
706                                : kOnContextMenus,
707                  scoped_ptr<base::ListValue>(args->DeepCopy())));
708    event->restrict_to_browser_context = context;
709    event->user_gesture = EventRouter::USER_GESTURE_ENABLED;
710    event_router->DispatchEventToExtension(item->extension_id(), event.Pass());
711  }
712  {
713    // Dispatch to .contextMenus.onClicked handler.
714    scoped_ptr<Event> event(
715        new Event(webview_guest ? chrome_web_view::OnClicked::kEventName
716                                : context_menus::OnClicked::kEventName,
717                  args.Pass()));
718    event->restrict_to_browser_context = context;
719    event->user_gesture = EventRouter::USER_GESTURE_ENABLED;
720    if (webview_guest)
721      event->filter_info.SetInstanceID(webview_guest->view_instance_id());
722    event_router->DispatchEventToExtension(item->extension_id(), event.Pass());
723  }
724}
725
726void MenuManager::SanitizeRadioList(const MenuItem::List& item_list) {
727  MenuItem::List::const_iterator i = item_list.begin();
728  while (i != item_list.end()) {
729    if ((*i)->type() != MenuItem::RADIO) {
730      ++i;
731      break;
732    }
733
734    // Uncheck any checked radio items in the run, and at the end reset
735    // the appropriate one to checked. If no check radio items were found,
736    // then check the first radio item in the run.
737    MenuItem::List::const_iterator last_checked = item_list.end();
738    MenuItem::List::const_iterator radio_run_iter;
739    for (radio_run_iter = i; radio_run_iter != item_list.end();
740        ++radio_run_iter) {
741      if ((*radio_run_iter)->type() != MenuItem::RADIO) {
742        break;
743      }
744
745      if ((*radio_run_iter)->checked()) {
746        last_checked = radio_run_iter;
747        (*radio_run_iter)->SetChecked(false);
748      }
749    }
750
751    if (last_checked != item_list.end())
752      (*last_checked)->SetChecked(true);
753    else
754      (*i)->SetChecked(true);
755
756    i = radio_run_iter;
757  }
758}
759
760bool MenuManager::ItemUpdated(const MenuItem::Id& id) {
761  if (!ContainsKey(items_by_id_, id))
762    return false;
763
764  MenuItem* menu_item = GetItemById(id);
765  DCHECK(menu_item);
766
767  if (menu_item->parent_id()) {
768    SanitizeRadioList(GetItemById(*menu_item->parent_id())->children());
769  } else {
770    MenuItemMap::iterator i =
771        context_items_.find(menu_item->id().extension_key);
772    if (i == context_items_.end()) {
773      NOTREACHED();
774      return false;
775    }
776    SanitizeRadioList(i->second);
777  }
778
779  return true;
780}
781
782void MenuManager::WriteToStorage(const Extension* extension,
783                                 const MenuItem::ExtensionKey& extension_key) {
784  if (!BackgroundInfo::HasLazyBackgroundPage(extension))
785    return;
786  // <webview> menu items are transient and not stored in storage.
787  if (extension_key.webview_instance_id)
788    return;
789  const MenuItem::List* top_items = MenuItems(extension_key);
790  MenuItem::List all_items;
791  if (top_items) {
792    for (MenuItem::List::const_iterator i = top_items->begin();
793         i != top_items->end(); ++i) {
794      DCHECK(!(*i)->id().extension_key.webview_instance_id);
795      (*i)->GetFlattenedSubtree(&all_items);
796    }
797  }
798
799  if (store_) {
800    store_->SetExtensionValue(extension->id(), kContextMenusKey,
801                              MenuItemsToValue(all_items));
802  }
803}
804
805void MenuManager::ReadFromStorage(const std::string& extension_id,
806                                  scoped_ptr<base::Value> value) {
807  const Extension* extension = ExtensionSystem::Get(browser_context_)
808                                   ->extension_service()
809                                   ->extensions()
810                                   ->GetByID(extension_id);
811  if (!extension)
812    return;
813
814  MenuItem::List items = MenuItemsFromValue(extension_id, value.get());
815  for (size_t i = 0; i < items.size(); ++i) {
816    bool added = false;
817
818    if (items[i]->parent_id()) {
819      // Parent IDs are stored in the parent_id field for convenience, but
820      // they have not yet been validated. Separate them out here.
821      // Because of the order in which we store items in the prefs, parents will
822      // precede children, so we should already know about any parent items.
823      scoped_ptr<MenuItem::Id> parent_id;
824      parent_id.swap(items[i]->parent_id_);
825      added = AddChildItem(*parent_id, items[i]);
826    } else {
827      added = AddContextItem(extension, items[i]);
828    }
829
830    if (!added)
831      delete items[i];
832  }
833}
834
835void MenuManager::OnExtensionLoaded(content::BrowserContext* browser_context,
836                                    const Extension* extension) {
837  if (store_ && BackgroundInfo::HasLazyBackgroundPage(extension)) {
838    store_->GetExtensionValue(
839        extension->id(),
840        kContextMenusKey,
841        base::Bind(
842            &MenuManager::ReadFromStorage, AsWeakPtr(), extension->id()));
843  }
844}
845
846void MenuManager::OnExtensionUnloaded(content::BrowserContext* browser_context,
847                                      const Extension* extension,
848                                      UnloadedExtensionInfo::Reason reason) {
849  MenuItem::ExtensionKey extension_key(extension->id());
850  if (ContainsKey(context_items_, extension_key)) {
851    RemoveAllContextItems(extension_key);
852  }
853}
854
855void MenuManager::Observe(int type,
856                          const content::NotificationSource& source,
857                          const content::NotificationDetails& details) {
858  DCHECK_EQ(chrome::NOTIFICATION_PROFILE_DESTROYED, type);
859  Profile* profile = content::Source<Profile>(source).ptr();
860  // We cannot use profile_->HasOffTheRecordProfile as it may already be
861  // false at this point, if for example the incognito profile was destroyed
862  // using DestroyOffTheRecordProfile.
863  if (profile->GetOriginalProfile() == browser_context_ &&
864      profile->GetOriginalProfile() != profile) {
865    RemoveAllIncognitoContextItems();
866  }
867}
868
869const SkBitmap& MenuManager::GetIconForExtension(
870    const std::string& extension_id) {
871  return icon_manager_.GetIcon(extension_id);
872}
873
874void MenuManager::RemoveAllIncognitoContextItems() {
875  // Get all context menu items with "incognito" set to "split".
876  std::set<MenuItem::Id> items_to_remove;
877  std::map<MenuItem::Id, MenuItem*>::const_iterator iter;
878  for (iter = items_by_id_.begin();
879       iter != items_by_id_.end();
880       ++iter) {
881    if (iter->first.incognito)
882      items_to_remove.insert(iter->first);
883  }
884
885  std::set<MenuItem::Id>::iterator remove_iter;
886  for (remove_iter = items_to_remove.begin();
887       remove_iter != items_to_remove.end();
888       ++remove_iter)
889    RemoveContextMenuItem(*remove_iter);
890}
891
892MenuItem::ExtensionKey::ExtensionKey() : webview_instance_id(0) {}
893
894MenuItem::ExtensionKey::ExtensionKey(const std::string& extension_id,
895                                     int webview_instance_id)
896    : extension_id(extension_id), webview_instance_id(webview_instance_id) {}
897
898MenuItem::ExtensionKey::ExtensionKey(const std::string& extension_id)
899    : extension_id(extension_id), webview_instance_id(0) {}
900
901bool MenuItem::ExtensionKey::operator==(const ExtensionKey& other) const {
902  return extension_id == other.extension_id &&
903         webview_instance_id == other.webview_instance_id;
904}
905
906bool MenuItem::ExtensionKey::operator<(const ExtensionKey& other) const {
907  if (extension_id != other.extension_id)
908    return extension_id < other.extension_id;
909
910  return webview_instance_id < other.webview_instance_id;
911}
912
913bool MenuItem::ExtensionKey::operator!=(const ExtensionKey& other) const {
914  return !(*this == other);
915}
916
917bool MenuItem::ExtensionKey::empty() const {
918  return extension_id.empty() && !webview_instance_id;
919}
920
921MenuItem::Id::Id() : incognito(false), uid(0) {}
922
923MenuItem::Id::Id(bool incognito, const MenuItem::ExtensionKey& extension_key)
924    : incognito(incognito), extension_key(extension_key), uid(0) {}
925
926MenuItem::Id::~Id() {
927}
928
929bool MenuItem::Id::operator==(const Id& other) const {
930  return (incognito == other.incognito &&
931          extension_key == other.extension_key && uid == other.uid &&
932          string_uid == other.string_uid);
933}
934
935bool MenuItem::Id::operator!=(const Id& other) const {
936  return !(*this == other);
937}
938
939bool MenuItem::Id::operator<(const Id& other) const {
940  if (incognito < other.incognito)
941    return true;
942  if (incognito == other.incognito) {
943    if (extension_key < other.extension_key)
944      return true;
945    if (extension_key == other.extension_key) {
946      if (uid < other.uid)
947        return true;
948      if (uid == other.uid)
949        return string_uid < other.string_uid;
950    }
951  }
952  return false;
953}
954
955}  // namespace extensions
956