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