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