menu_manager.cc revision 0529e5d033099cbfc42635f6f6183833b09dff6e
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/state_store.h" 20#include "chrome/browser/extensions/tab_helper.h" 21#include "chrome/browser/guestview/webview/webview_guest.h" 22#include "chrome/browser/profiles/profile.h" 23#include "chrome/common/extensions/api/context_menus.h" 24#include "chrome/common/extensions/api/webview.h" 25#include "content/public/browser/notification_details.h" 26#include "content/public/browser/notification_service.h" 27#include "content/public/browser/notification_source.h" 28#include "content/public/browser/web_contents.h" 29#include "content/public/common/context_menu_params.h" 30#include "extensions/browser/event_router.h" 31#include "extensions/browser/extension_system.h" 32#include "extensions/common/extension.h" 33#include "extensions/common/manifest_handlers/background_info.h" 34#include "ui/gfx/favicon_size.h" 35#include "ui/gfx/text_elider.h" 36 37using content::WebContents; 38using extensions::ExtensionSystem; 39 40namespace extensions { 41 42namespace context_menus = api::context_menus; 43namespace webview = api::webview; 44 45namespace { 46 47// Keys for serialization to and from Value to store in the preferences. 48const char kContextMenusKey[] = "context_menus"; 49 50const char kCheckedKey[] = "checked"; 51const char kContextsKey[] = "contexts"; 52const char kDocumentURLPatternsKey[] = "document_url_patterns"; 53const char kEnabledKey[] = "enabled"; 54const char kIncognitoKey[] = "incognito"; 55const char kParentUIDKey[] = "parent_uid"; 56const char kStringUIDKey[] = "string_uid"; 57const char kTargetURLPatternsKey[] = "target_url_patterns"; 58const char kTitleKey[] = "title"; 59const char kTypeKey[] = "type"; 60 61void SetIdKeyValue(base::DictionaryValue* properties, 62 const char* key, 63 const MenuItem::Id& id) { 64 if (id.uid == 0) 65 properties->SetString(key, id.string_uid); 66 else 67 properties->SetInteger(key, id.uid); 68} 69 70MenuItem::List MenuItemsFromValue(const std::string& extension_id, 71 base::Value* value) { 72 MenuItem::List items; 73 74 base::ListValue* list = NULL; 75 if (!value || !value->GetAsList(&list)) 76 return items; 77 78 for (size_t i = 0; i < list->GetSize(); ++i) { 79 base::DictionaryValue* dict = NULL; 80 if (!list->GetDictionary(i, &dict)) 81 continue; 82 MenuItem* item = MenuItem::Populate( 83 extension_id, *dict, NULL); 84 if (!item) 85 continue; 86 items.push_back(item); 87 } 88 return items; 89} 90 91scoped_ptr<base::Value> MenuItemsToValue(const MenuItem::List& items) { 92 scoped_ptr<base::ListValue> list(new base::ListValue()); 93 for (size_t i = 0; i < items.size(); ++i) 94 list->Append(items[i]->ToValue().release()); 95 return scoped_ptr<base::Value>(list.release()); 96} 97 98bool GetStringList(const base::DictionaryValue& dict, 99 const std::string& key, 100 std::vector<std::string>* out) { 101 if (!dict.HasKey(key)) 102 return true; 103 104 const base::ListValue* list = NULL; 105 if (!dict.GetListWithoutPathExpansion(key, &list)) 106 return false; 107 108 for (size_t i = 0; i < list->GetSize(); ++i) { 109 std::string pattern; 110 if (!list->GetString(i, &pattern)) 111 return false; 112 out->push_back(pattern); 113 } 114 115 return true; 116} 117 118} // namespace 119 120MenuItem::MenuItem(const Id& id, 121 const std::string& title, 122 bool checked, 123 bool enabled, 124 Type type, 125 const ContextList& contexts) 126 : id_(id), 127 title_(title), 128 type_(type), 129 checked_(checked), 130 enabled_(enabled), 131 contexts_(contexts) {} 132 133MenuItem::~MenuItem() { 134 STLDeleteElements(&children_); 135} 136 137MenuItem* MenuItem::ReleaseChild(const Id& child_id, 138 bool recursive) { 139 for (List::iterator i = children_.begin(); i != children_.end(); ++i) { 140 MenuItem* child = NULL; 141 if ((*i)->id() == child_id) { 142 child = *i; 143 children_.erase(i); 144 return child; 145 } else if (recursive) { 146 child = (*i)->ReleaseChild(child_id, recursive); 147 if (child) 148 return child; 149 } 150 } 151 return NULL; 152} 153 154void MenuItem::GetFlattenedSubtree(MenuItem::List* list) { 155 list->push_back(this); 156 for (List::iterator i = children_.begin(); i != children_.end(); ++i) 157 (*i)->GetFlattenedSubtree(list); 158} 159 160std::set<MenuItem::Id> MenuItem::RemoveAllDescendants() { 161 std::set<Id> result; 162 for (List::iterator i = children_.begin(); i != children_.end(); ++i) { 163 MenuItem* child = *i; 164 result.insert(child->id()); 165 std::set<Id> removed = child->RemoveAllDescendants(); 166 result.insert(removed.begin(), removed.end()); 167 } 168 STLDeleteElements(&children_); 169 return result; 170} 171 172base::string16 MenuItem::TitleWithReplacement(const base::string16& selection, 173 size_t max_length) const { 174 base::string16 result = base::UTF8ToUTF16(title_); 175 // TODO(asargent) - Change this to properly handle %% escaping so you can 176 // put "%s" in titles that won't get substituted. 177 ReplaceSubstringsAfterOffset(&result, 0, base::ASCIIToUTF16("%s"), selection); 178 179 if (result.length() > max_length) 180 result = gfx::TruncateString(result, max_length); 181 return result; 182} 183 184bool MenuItem::SetChecked(bool checked) { 185 if (type_ != CHECKBOX && type_ != RADIO) 186 return false; 187 checked_ = checked; 188 return true; 189} 190 191void MenuItem::AddChild(MenuItem* item) { 192 item->parent_id_.reset(new Id(id_)); 193 children_.push_back(item); 194} 195 196scoped_ptr<base::DictionaryValue> MenuItem::ToValue() const { 197 scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue); 198 // Should only be called for extensions with event pages, which only have 199 // string IDs for items. 200 DCHECK_EQ(0, id_.uid); 201 value->SetString(kStringUIDKey, id_.string_uid); 202 value->SetBoolean(kIncognitoKey, id_.incognito); 203 value->SetInteger(kTypeKey, type_); 204 if (type_ != SEPARATOR) 205 value->SetString(kTitleKey, title_); 206 if (type_ == CHECKBOX || type_ == RADIO) 207 value->SetBoolean(kCheckedKey, checked_); 208 value->SetBoolean(kEnabledKey, enabled_); 209 value->Set(kContextsKey, contexts_.ToValue().release()); 210 if (parent_id_) { 211 DCHECK_EQ(0, parent_id_->uid); 212 value->SetString(kParentUIDKey, parent_id_->string_uid); 213 } 214 value->Set(kDocumentURLPatternsKey, 215 document_url_patterns_.ToValue().release()); 216 value->Set(kTargetURLPatternsKey, target_url_patterns_.ToValue().release()); 217 return value.Pass(); 218} 219 220// static 221MenuItem* MenuItem::Populate(const std::string& extension_id, 222 const base::DictionaryValue& value, 223 std::string* error) { 224 bool incognito = false; 225 if (!value.GetBoolean(kIncognitoKey, &incognito)) 226 return NULL; 227 Id id(incognito, MenuItem::ExtensionKey(extension_id)); 228 if (!value.GetString(kStringUIDKey, &id.string_uid)) 229 return NULL; 230 int type_int; 231 Type type = NORMAL; 232 if (!value.GetInteger(kTypeKey, &type_int)) 233 return NULL; 234 type = static_cast<Type>(type_int); 235 std::string title; 236 if (type != SEPARATOR && !value.GetString(kTitleKey, &title)) 237 return NULL; 238 bool checked = false; 239 if ((type == CHECKBOX || type == RADIO) && 240 !value.GetBoolean(kCheckedKey, &checked)) { 241 return NULL; 242 } 243 bool enabled = true; 244 if (!value.GetBoolean(kEnabledKey, &enabled)) 245 return NULL; 246 ContextList contexts; 247 const base::Value* contexts_value = NULL; 248 if (!value.Get(kContextsKey, &contexts_value)) 249 return NULL; 250 if (!contexts.Populate(*contexts_value)) 251 return NULL; 252 253 scoped_ptr<MenuItem> result(new MenuItem( 254 id, title, checked, enabled, type, contexts)); 255 256 std::vector<std::string> document_url_patterns; 257 if (!GetStringList(value, kDocumentURLPatternsKey, &document_url_patterns)) 258 return NULL; 259 std::vector<std::string> target_url_patterns; 260 if (!GetStringList(value, kTargetURLPatternsKey, &target_url_patterns)) 261 return NULL; 262 263 if (!result->PopulateURLPatterns(&document_url_patterns, 264 &target_url_patterns, 265 error)) { 266 return NULL; 267 } 268 269 // parent_id is filled in from the value, but it might not be valid. It's left 270 // to be validated upon being added (via AddChildItem) to the menu manager. 271 scoped_ptr<Id> parent_id( 272 new Id(incognito, MenuItem::ExtensionKey(extension_id))); 273 if (value.HasKey(kParentUIDKey)) { 274 if (!value.GetString(kParentUIDKey, &parent_id->string_uid)) 275 return NULL; 276 result->parent_id_.swap(parent_id); 277 } 278 return result.release(); 279} 280 281bool MenuItem::PopulateURLPatterns( 282 std::vector<std::string>* document_url_patterns, 283 std::vector<std::string>* target_url_patterns, 284 std::string* error) { 285 if (document_url_patterns) { 286 if (!document_url_patterns_.Populate( 287 *document_url_patterns, URLPattern::SCHEME_ALL, true, error)) { 288 return false; 289 } 290 } 291 if (target_url_patterns) { 292 if (!target_url_patterns_.Populate( 293 *target_url_patterns, URLPattern::SCHEME_ALL, true, error)) { 294 return false; 295 } 296 } 297 return true; 298} 299 300// static 301const char MenuManager::kOnContextMenus[] = "contextMenus"; 302const char MenuManager::kOnWebviewContextMenus[] = "webview.contextMenus"; 303 304MenuManager::MenuManager(Profile* profile, StateStore* store) 305 : profile_(profile), store_(store) { 306 registrar_.Add(this, 307 chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED, 308 content::Source<Profile>(profile)); 309 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED, 310 content::Source<Profile>(profile)); 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(Profile* profile) { 326 return MenuManagerFactory::GetForProfile(profile); 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(profile_, 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(Profile* profile, 608 WebContents* web_contents, 609 const content::ContextMenuParams& params, 610 const MenuItem::Id& menu_item_id) { 611 EventRouter* event_router = EventRouter::Get(profile); 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(profile_)->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 webview_custom_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 = profile; 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 ? webview::OnClicked::kEventName 716 : context_menus::OnClicked::kEventName, 717 args.Pass())); 718 event->restrict_to_browser_context = profile; 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 = 808 ExtensionSystem::Get(profile_)->extension_service()->extensions()-> 809 GetByID(extension_id); 810 if (!extension) 811 return; 812 813 MenuItem::List items = MenuItemsFromValue(extension_id, value.get()); 814 for (size_t i = 0; i < items.size(); ++i) { 815 bool added = false; 816 817 if (items[i]->parent_id()) { 818 // Parent IDs are stored in the parent_id field for convenience, but 819 // they have not yet been validated. Separate them out here. 820 // Because of the order in which we store items in the prefs, parents will 821 // precede children, so we should already know about any parent items. 822 scoped_ptr<MenuItem::Id> parent_id; 823 parent_id.swap(items[i]->parent_id_); 824 added = AddChildItem(*parent_id, items[i]); 825 } else { 826 added = AddContextItem(extension, items[i]); 827 } 828 829 if (!added) 830 delete items[i]; 831 } 832} 833 834void MenuManager::Observe(int type, 835 const content::NotificationSource& source, 836 const content::NotificationDetails& details) { 837 switch (type) { 838 case chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED: { 839 // Remove menu items for disabled/uninstalled extensions. 840 const Extension* extension = 841 content::Details<UnloadedExtensionInfo>(details)->extension; 842 MenuItem::ExtensionKey extension_key(extension->id()); 843 if (ContainsKey(context_items_, extension_key)) { 844 RemoveAllContextItems(extension_key); 845 } 846 break; 847 } 848 case chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED: { 849 const Extension* extension = 850 content::Details<const Extension>(details).ptr(); 851 if (store_ && BackgroundInfo::HasLazyBackgroundPage(extension)) { 852 store_->GetExtensionValue(extension->id(), kContextMenusKey, 853 base::Bind(&MenuManager::ReadFromStorage, 854 AsWeakPtr(), extension->id())); 855 } 856 break; 857 } 858 case chrome::NOTIFICATION_PROFILE_DESTROYED: { 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() == profile_ && 864 profile->GetOriginalProfile() != profile) { 865 RemoveAllIncognitoContextItems(); 866 } 867 break; 868 } 869 default: 870 NOTREACHED(); 871 break; 872 } 873} 874 875const SkBitmap& MenuManager::GetIconForExtension( 876 const std::string& extension_id) { 877 return icon_manager_.GetIcon(extension_id); 878} 879 880void MenuManager::RemoveAllIncognitoContextItems() { 881 // Get all context menu items with "incognito" set to "split". 882 std::set<MenuItem::Id> items_to_remove; 883 std::map<MenuItem::Id, MenuItem*>::const_iterator iter; 884 for (iter = items_by_id_.begin(); 885 iter != items_by_id_.end(); 886 ++iter) { 887 if (iter->first.incognito) 888 items_to_remove.insert(iter->first); 889 } 890 891 std::set<MenuItem::Id>::iterator remove_iter; 892 for (remove_iter = items_to_remove.begin(); 893 remove_iter != items_to_remove.end(); 894 ++remove_iter) 895 RemoveContextMenuItem(*remove_iter); 896} 897 898MenuItem::ExtensionKey::ExtensionKey() : webview_instance_id(0) {} 899 900MenuItem::ExtensionKey::ExtensionKey(const std::string& extension_id, 901 int webview_instance_id) 902 : extension_id(extension_id), webview_instance_id(webview_instance_id) {} 903 904MenuItem::ExtensionKey::ExtensionKey(const std::string& extension_id) 905 : extension_id(extension_id), webview_instance_id(0) {} 906 907bool MenuItem::ExtensionKey::operator==(const ExtensionKey& other) const { 908 return extension_id == other.extension_id && 909 webview_instance_id == other.webview_instance_id; 910} 911 912bool MenuItem::ExtensionKey::operator<(const ExtensionKey& other) const { 913 if (extension_id != other.extension_id) 914 return extension_id < other.extension_id; 915 916 return webview_instance_id < other.webview_instance_id; 917} 918 919bool MenuItem::ExtensionKey::operator!=(const ExtensionKey& other) const { 920 return !(*this == other); 921} 922 923bool MenuItem::ExtensionKey::empty() const { 924 return extension_id.empty() && !webview_instance_id; 925} 926 927MenuItem::Id::Id() : incognito(false), uid(0) {} 928 929MenuItem::Id::Id(bool incognito, const MenuItem::ExtensionKey& extension_key) 930 : incognito(incognito), extension_key(extension_key), uid(0) {} 931 932MenuItem::Id::~Id() { 933} 934 935bool MenuItem::Id::operator==(const Id& other) const { 936 return (incognito == other.incognito && 937 extension_key == other.extension_key && uid == other.uid && 938 string_uid == other.string_uid); 939} 940 941bool MenuItem::Id::operator!=(const Id& other) const { 942 return !(*this == other); 943} 944 945bool MenuItem::Id::operator<(const Id& other) const { 946 if (incognito < other.incognito) 947 return true; 948 if (incognito == other.incognito) { 949 if (extension_key < other.extension_key) 950 return true; 951 if (extension_key == other.extension_key) { 952 if (uid < other.uid) 953 return true; 954 if (uid == other.uid) 955 return string_uid < other.string_uid; 956 } 957 } 958 return false; 959} 960 961} // namespace extensions 962