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