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