| OLD | NEW |
| (Empty) | |
| 1 // Copyright (c) 2010 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/extension_menu_manager.h" |
| 6 |
| 7 #include "base/logging.h" |
| 8 #include "base/string_util.h" |
| 9 #include "base/utf_string_conversions.h" |
| 10 #include "base/values.h" |
| 11 #include "base/json/json_writer.h" |
| 12 #include "chrome/app/chrome_dll_resource.h" |
| 13 #include "chrome/browser/extensions/extension_message_service.h" |
| 14 #include "chrome/browser/extensions/extension_tabs_module.h" |
| 15 #include "chrome/browser/profile.h" |
| 16 #include "chrome/common/extensions/extension.h" |
| 17 #include "webkit/glue/context_menu.h" |
| 18 |
| 19 ExtensionMenuItem::ExtensionMenuItem(const std::string& extension_id, |
| 20 std::string title, |
| 21 bool checked, Type type, |
| 22 const ContextList& contexts, |
| 23 const ContextList& enabled_contexts) |
| 24 : extension_id_(extension_id), |
| 25 title_(title), |
| 26 id_(0), |
| 27 type_(type), |
| 28 checked_(checked), |
| 29 contexts_(contexts), |
| 30 enabled_contexts_(enabled_contexts), |
| 31 parent_id_(0) {} |
| 32 |
| 33 ExtensionMenuItem::~ExtensionMenuItem() {} |
| 34 |
| 35 ExtensionMenuItem* ExtensionMenuItem::ChildAt(int index) const { |
| 36 if (index < 0 || static_cast<size_t>(index) >= children_.size()) |
| 37 return NULL; |
| 38 return children_[index].get(); |
| 39 } |
| 40 |
| 41 bool ExtensionMenuItem::RemoveChild(int child_id) { |
| 42 for (List::iterator i = children_.begin(); i != children_.end(); ++i) { |
| 43 if ((*i)->id() == child_id) { |
| 44 children_.erase(i); |
| 45 return true; |
| 46 } else if ((*i)->RemoveChild(child_id)) { |
| 47 return true; |
| 48 } |
| 49 } |
| 50 return false; |
| 51 } |
| 52 |
| 53 string16 ExtensionMenuItem::TitleWithReplacement( |
| 54 const string16& selection) const { |
| 55 string16 result = UTF8ToUTF16(title_); |
| 56 // TODO(asargent) - Change this to properly handle %% escaping so you can |
| 57 // put "%s" in titles that won't get substituted. |
| 58 ReplaceSubstringsAfterOffset(&result, 0, ASCIIToUTF16("%s"), selection); |
| 59 return result; |
| 60 } |
| 61 |
| 62 bool ExtensionMenuItem::SetChecked(bool checked) { |
| 63 if (type_ != CHECKBOX && type_ != RADIO) |
| 64 return false; |
| 65 checked_ = checked; |
| 66 return true; |
| 67 } |
| 68 |
| 69 void ExtensionMenuItem::AddChild(ExtensionMenuItem* item) { |
| 70 item->parent_id_ = id_; |
| 71 children_.push_back(linked_ptr<ExtensionMenuItem>(item)); |
| 72 } |
| 73 |
| 74 ExtensionMenuManager::ExtensionMenuManager() : next_item_id_(1) { |
| 75 registrar_.Add(this, NotificationType::EXTENSION_UNLOADED, |
| 76 NotificationService::AllSources()); |
| 77 } |
| 78 |
| 79 ExtensionMenuManager::~ExtensionMenuManager() {} |
| 80 |
| 81 std::set<std::string> ExtensionMenuManager::ExtensionIds() { |
| 82 std::set<std::string> id_set; |
| 83 for (MenuItemMap::const_iterator i = context_items_.begin(); |
| 84 i != context_items_.end(); ++i) { |
| 85 id_set.insert(i->first); |
| 86 } |
| 87 return id_set; |
| 88 } |
| 89 |
| 90 std::vector<const ExtensionMenuItem*> ExtensionMenuManager::MenuItems( |
| 91 const std::string& extension_id) { |
| 92 std::vector<const ExtensionMenuItem*> result; |
| 93 |
| 94 MenuItemMap::iterator i = context_items_.find(extension_id); |
| 95 if (i != context_items_.end()) { |
| 96 ExtensionMenuItem::List& list = i->second; |
| 97 ExtensionMenuItem::List::iterator j; |
| 98 for (j = list.begin(); j != list.end(); ++j) { |
| 99 result.push_back(j->get()); |
| 100 } |
| 101 } |
| 102 |
| 103 return result; |
| 104 } |
| 105 |
| 106 int ExtensionMenuManager::AddContextItem(ExtensionMenuItem* item) { |
| 107 const std::string& extension_id = item->extension_id(); |
| 108 // The item must have a non-empty extension id. |
| 109 if (extension_id.empty()) |
| 110 return 0; |
| 111 |
| 112 DCHECK_EQ(0, item->id()); |
| 113 item->set_id(next_item_id_++); |
| 114 |
| 115 context_items_[extension_id].push_back(linked_ptr<ExtensionMenuItem>(item)); |
| 116 items_by_id_[item->id()] = item; |
| 117 |
| 118 if (item->type() == ExtensionMenuItem::RADIO && item->checked()) |
| 119 RadioItemSelected(item); |
| 120 |
| 121 return item->id(); |
| 122 } |
| 123 |
| 124 int ExtensionMenuManager::AddChildItem(int parent_id, |
| 125 ExtensionMenuItem* child) { |
| 126 ExtensionMenuItem* parent = GetItemById(parent_id); |
| 127 if (!parent || parent->type() != ExtensionMenuItem::NORMAL || |
| 128 parent->extension_id() != child->extension_id()) |
| 129 return 0; |
| 130 child->set_id(next_item_id_++); |
| 131 parent->AddChild(child); |
| 132 items_by_id_[child->id()] = child; |
| 133 return child->id(); |
| 134 } |
| 135 |
| 136 bool ExtensionMenuManager::RemoveContextMenuItem(int id) { |
| 137 if (items_by_id_.find(id) == items_by_id_.end()) |
| 138 return false; |
| 139 |
| 140 MenuItemMap::iterator i; |
| 141 for (i = context_items_.begin(); i != context_items_.end(); ++i) { |
| 142 ExtensionMenuItem::List& list = i->second; |
| 143 ExtensionMenuItem::List::iterator j; |
| 144 for (j = list.begin(); j < list.end(); ++j) { |
| 145 // See if the current item is a match, or if one of its children was. |
| 146 if ((*j)->id() == id) { |
| 147 list.erase(j); |
| 148 items_by_id_.erase(id); |
| 149 return true; |
| 150 } else if ((*j)->RemoveChild(id)) { |
| 151 items_by_id_.erase(id); |
| 152 return true; |
| 153 } |
| 154 } |
| 155 } |
| 156 NOTREACHED(); // The check at the very top should prevent getting here. |
| 157 return false; |
| 158 } |
| 159 |
| 160 ExtensionMenuItem* ExtensionMenuManager::GetItemById(int id) { |
| 161 std::map<int, ExtensionMenuItem*>::const_iterator i = items_by_id_.find(id); |
| 162 if (i != items_by_id_.end()) |
| 163 return i->second; |
| 164 else |
| 165 return NULL; |
| 166 } |
| 167 |
| 168 void ExtensionMenuManager::RadioItemSelected(ExtensionMenuItem* item) { |
| 169 // If this is a child item, we need to get a handle to the list from its |
| 170 // parent. Otherwise get a handle to the top-level list. |
| 171 ExtensionMenuItem::List* list = NULL; |
| 172 if (item->parent_id()) { |
| 173 ExtensionMenuItem* parent = GetItemById(item->parent_id()); |
| 174 if (!parent) { |
| 175 NOTREACHED(); |
| 176 return; |
| 177 } |
| 178 list = parent->children(); |
| 179 } else { |
| 180 if (context_items_.find(item->extension_id()) == context_items_.end()) { |
| 181 NOTREACHED(); |
| 182 return; |
| 183 } |
| 184 list = &context_items_[item->extension_id()]; |
| 185 } |
| 186 |
| 187 // Find where |item| is in the list. |
| 188 ExtensionMenuItem::List::iterator item_location; |
| 189 for (item_location = list->begin(); item_location != list->end(); |
| 190 ++item_location) { |
| 191 if (item_location->get() == item) |
| 192 break; |
| 193 } |
| 194 if (item_location == list->end()) { |
| 195 NOTREACHED(); // We should have found the item. |
| 196 return; |
| 197 } |
| 198 |
| 199 // Iterate backwards from |item| and uncheck any adjacent radio items. |
| 200 ExtensionMenuItem::List::iterator i; |
| 201 if (item_location != list->begin()) { |
| 202 i = item_location; |
| 203 do { |
| 204 --i; |
| 205 if ((*i)->type() != ExtensionMenuItem::RADIO) |
| 206 break; |
| 207 (*i)->SetChecked(false); |
| 208 } while (i != list->begin()); |
| 209 } |
| 210 |
| 211 // Now iterate forwards from |item| and uncheck any adjacent radio items. |
| 212 for (i = item_location + 1; i != list->end(); ++i) { |
| 213 if ((*i)->type() != ExtensionMenuItem::RADIO) |
| 214 break; |
| 215 (*i)->SetChecked(false); |
| 216 } |
| 217 } |
| 218 |
| 219 static void AddURLProperty(DictionaryValue* dictionary, |
| 220 const std::wstring& key, const GURL& url) { |
| 221 if (!url.is_empty()) |
| 222 dictionary->SetString(key, url.possibly_invalid_spec()); |
| 223 } |
| 224 |
| 225 void ExtensionMenuManager::GetItemAndIndex(int id, ExtensionMenuItem** item, |
| 226 size_t* index) { |
| 227 for (MenuItemMap::const_iterator i = context_items_.begin(); |
| 228 i != context_items_.end(); ++i) { |
| 229 const ExtensionMenuItem::List& list = i->second; |
| 230 for (size_t tmp_index = 0; tmp_index < list.size(); tmp_index++) { |
| 231 if (list[tmp_index]->id() == id) { |
| 232 if (item) |
| 233 *item = list[tmp_index].get(); |
| 234 if (index) |
| 235 *index = tmp_index; |
| 236 return; |
| 237 } |
| 238 } |
| 239 } |
| 240 if (item) |
| 241 *item = NULL; |
| 242 if (index) |
| 243 *index = 0; |
| 244 } |
| 245 |
| 246 |
| 247 void ExtensionMenuManager::ExecuteCommand(Profile* profile, |
| 248 TabContents* tab_contents, |
| 249 const ContextMenuParams& params, |
| 250 int menuItemId) { |
| 251 ExtensionMessageService* service = profile->GetExtensionMessageService(); |
| 252 if (!service) |
| 253 return; |
| 254 |
| 255 ExtensionMenuItem* item = GetItemById(menuItemId); |
| 256 if (!item) |
| 257 return; |
| 258 |
| 259 if (item->type() == ExtensionMenuItem::RADIO) |
| 260 RadioItemSelected(item); |
| 261 |
| 262 ListValue args; |
| 263 |
| 264 DictionaryValue* properties = new DictionaryValue(); |
| 265 properties->SetInteger(L"menuItemId", item->id()); |
| 266 if (item->parent_id()) |
| 267 properties->SetInteger(L"parentMenuItemId", item->parent_id()); |
| 268 |
| 269 switch (params.media_type) { |
| 270 case WebKit::WebContextMenuData::MediaTypeImage: |
| 271 properties->SetString(L"mediaType", "IMAGE"); |
| 272 break; |
| 273 case WebKit::WebContextMenuData::MediaTypeVideo: |
| 274 properties->SetString(L"mediaType", "VIDEO"); |
| 275 break; |
| 276 case WebKit::WebContextMenuData::MediaTypeAudio: |
| 277 properties->SetString(L"mediaType", "AUDIO"); |
| 278 break; |
| 279 default: {} // Do nothing. |
| 280 } |
| 281 |
| 282 AddURLProperty(properties, L"linkUrl", params.unfiltered_link_url); |
| 283 AddURLProperty(properties, L"srcUrl", params.src_url); |
| 284 AddURLProperty(properties, L"mainFrameUrl", params.page_url); |
| 285 AddURLProperty(properties, L"frameUrl", params.frame_url); |
| 286 |
| 287 if (params.selection_text.length() > 0) |
| 288 properties->SetString(L"selectionText", params.selection_text); |
| 289 |
| 290 properties->SetBoolean(L"editable", params.is_editable); |
| 291 |
| 292 args.Append(properties); |
| 293 |
| 294 // Add the tab info to the argument list. |
| 295 args.Append(ExtensionTabUtil::CreateTabValue(tab_contents)); |
| 296 |
| 297 if (item->type() == ExtensionMenuItem::CHECKBOX || |
| 298 item->type() == ExtensionMenuItem::RADIO) { |
| 299 bool was_checked = item->checked(); |
| 300 properties->SetBoolean(L"wasChecked", was_checked); |
| 301 |
| 302 // RADIO items always get set to true when you click on them, but CHECKBOX |
| 303 // items get their state toggled. |
| 304 bool checked = |
| 305 (item->type() == ExtensionMenuItem::RADIO) ? true : !was_checked; |
| 306 |
| 307 item->SetChecked(checked); |
| 308 properties->SetBoolean(L"checked", item->checked()); |
| 309 } |
| 310 |
| 311 std::string json_args; |
| 312 base::JSONWriter::Write(&args, false, &json_args); |
| 313 std::string event_name = "contextMenu/" + item->extension_id(); |
| 314 service->DispatchEventToRenderers(event_name, json_args, |
| 315 profile->IsOffTheRecord()); |
| 316 } |
| 317 |
| 318 void ExtensionMenuManager::Observe(NotificationType type, |
| 319 const NotificationSource& source, |
| 320 const NotificationDetails& details) { |
| 321 // Remove menu items for disabled/uninstalled extensions. |
| 322 if (type != NotificationType::EXTENSION_UNLOADED) { |
| 323 NOTREACHED(); |
| 324 return; |
| 325 } |
| 326 Extension* extension = Details<Extension>(details).ptr(); |
| 327 MenuItemMap::iterator i = context_items_.find(extension->id()); |
| 328 if (i != context_items_.end()) { |
| 329 const ExtensionMenuItem::List& list = i->second; |
| 330 ExtensionMenuItem::List::const_iterator j; |
| 331 for (j = list.begin(); j != list.end(); ++j) |
| 332 items_by_id_.erase((*j)->id()); |
| 333 context_items_.erase(i); |
| 334 } |
| 335 } |
| OLD | NEW |