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