OLD | NEW |
| (Empty) |
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/api/context_menus/context_menus_api.h" | |
6 | |
7 #include <string> | |
8 | |
9 #include "base/string_number_conversions.h" | |
10 #include "base/string_util.h" | |
11 #include "base/values.h" | |
12 #include "chrome/browser/extensions/extension_service.h" | |
13 #include "chrome/browser/extensions/menu_manager.h" | |
14 #include "chrome/browser/profiles/profile.h" | |
15 #include "chrome/common/extensions/api/context_menus.h" | |
16 #include "extensions/common/error_utils.h" | |
17 #include "extensions/common/url_pattern_set.h" | |
18 | |
19 using extensions::ErrorUtils; | |
20 | |
21 namespace { | |
22 | |
23 const char kGeneratedIdKey[] = "generatedId"; | |
24 | |
25 const char kCannotFindItemError[] = "Cannot find menu item with id *"; | |
26 const char kOnclickDisallowedError[] = "Extensions using event pages cannot " | |
27 "pass an onclick parameter to chrome.contextMenus.create. Instead, use " | |
28 "the chrome.contextMenus.onClicked event."; | |
29 const char kCheckedError[] = | |
30 "Only items with type \"radio\" or \"checkbox\" can be checked"; | |
31 const char kDuplicateIDError[] = | |
32 "Cannot create item with duplicate id *"; | |
33 const char kIdRequiredError[] = "Extensions using event pages must pass an " | |
34 "id parameter to chrome.contextMenus.create"; | |
35 const char kParentsMustBeNormalError[] = | |
36 "Parent items must have type \"normal\""; | |
37 const char kTitleNeededError[] = | |
38 "All menu items except for separators must have a title"; | |
39 const char kLauncherNotAllowedError[] = | |
40 "Only packaged apps are allowed to use 'launcher' context"; | |
41 | |
42 std::string GetIDString(const extensions::MenuItem::Id& id) { | |
43 if (id.uid == 0) | |
44 return id.string_uid; | |
45 else | |
46 return base::IntToString(id.uid); | |
47 } | |
48 | |
49 template<typename PropertyWithEnumT> | |
50 extensions::MenuItem::ContextList GetContexts( | |
51 const PropertyWithEnumT& property) { | |
52 extensions::MenuItem::ContextList contexts; | |
53 for (size_t i = 0; i < property.contexts->size(); ++i) { | |
54 switch (property.contexts->at(i)) { | |
55 case PropertyWithEnumT::CONTEXTS_ELEMENT_ALL: | |
56 contexts.Add(extensions::MenuItem::ALL); | |
57 break; | |
58 case PropertyWithEnumT::CONTEXTS_ELEMENT_PAGE: | |
59 contexts.Add(extensions::MenuItem::PAGE); | |
60 break; | |
61 case PropertyWithEnumT::CONTEXTS_ELEMENT_SELECTION: | |
62 contexts.Add(extensions::MenuItem::SELECTION); | |
63 break; | |
64 case PropertyWithEnumT::CONTEXTS_ELEMENT_LINK: | |
65 contexts.Add(extensions::MenuItem::LINK); | |
66 break; | |
67 case PropertyWithEnumT::CONTEXTS_ELEMENT_EDITABLE: | |
68 contexts.Add(extensions::MenuItem::EDITABLE); | |
69 break; | |
70 case PropertyWithEnumT::CONTEXTS_ELEMENT_IMAGE: | |
71 contexts.Add(extensions::MenuItem::IMAGE); | |
72 break; | |
73 case PropertyWithEnumT::CONTEXTS_ELEMENT_VIDEO: | |
74 contexts.Add(extensions::MenuItem::VIDEO); | |
75 break; | |
76 case PropertyWithEnumT::CONTEXTS_ELEMENT_AUDIO: | |
77 contexts.Add(extensions::MenuItem::AUDIO); | |
78 break; | |
79 case PropertyWithEnumT::CONTEXTS_ELEMENT_FRAME: | |
80 contexts.Add(extensions::MenuItem::FRAME); | |
81 break; | |
82 case PropertyWithEnumT::CONTEXTS_ELEMENT_LAUNCHER: | |
83 contexts.Add(extensions::MenuItem::LAUNCHER); | |
84 break; | |
85 case PropertyWithEnumT::CONTEXTS_ELEMENT_NONE: | |
86 NOTREACHED(); | |
87 } | |
88 } | |
89 return contexts; | |
90 } | |
91 | |
92 template<typename PropertyWithEnumT> | |
93 extensions::MenuItem::Type GetType(const PropertyWithEnumT& property) { | |
94 switch (property.type) { | |
95 case PropertyWithEnumT::TYPE_NONE: | |
96 case PropertyWithEnumT::TYPE_NORMAL: | |
97 return extensions::MenuItem::NORMAL; | |
98 case PropertyWithEnumT::TYPE_CHECKBOX: | |
99 return extensions::MenuItem::CHECKBOX; | |
100 case PropertyWithEnumT::TYPE_RADIO: | |
101 return extensions::MenuItem::RADIO; | |
102 case PropertyWithEnumT::TYPE_SEPARATOR: | |
103 return extensions::MenuItem::SEPARATOR; | |
104 } | |
105 return extensions::MenuItem::NORMAL; | |
106 } | |
107 | |
108 template<typename PropertyWithEnumT> | |
109 scoped_ptr<extensions::MenuItem::Id> GetParentId( | |
110 const PropertyWithEnumT& property, | |
111 bool is_off_the_record, | |
112 std::string extension_id) { | |
113 scoped_ptr<extensions::MenuItem::Id> parent_id( | |
114 new extensions::MenuItem::Id(is_off_the_record, extension_id)); | |
115 switch (property.parent_id_type) { | |
116 case PropertyWithEnumT::PARENT_ID_NONE: | |
117 return scoped_ptr<extensions::MenuItem::Id>().Pass(); | |
118 case PropertyWithEnumT::PARENT_ID_INTEGER: | |
119 parent_id->uid = *property.parent_id_integer; | |
120 break; | |
121 case PropertyWithEnumT::PARENT_ID_STRING: | |
122 parent_id->string_uid = *property.parent_id_string; | |
123 break; | |
124 } | |
125 return parent_id.Pass(); | |
126 } | |
127 | |
128 extensions::MenuItem* GetParent(extensions::MenuItem::Id parent_id, | |
129 const extensions::MenuManager* menu_manager, | |
130 std::string* error) { | |
131 extensions::MenuItem* parent = menu_manager->GetItemById(parent_id); | |
132 if (!parent) { | |
133 *error = ErrorUtils::FormatErrorMessage( | |
134 kCannotFindItemError, GetIDString(parent_id)); | |
135 return NULL; | |
136 } | |
137 if (parent->type() != extensions::MenuItem::NORMAL) { | |
138 *error = kParentsMustBeNormalError; | |
139 return NULL; | |
140 } | |
141 | |
142 return parent; | |
143 } | |
144 | |
145 } // namespace | |
146 | |
147 namespace extensions { | |
148 | |
149 namespace Create = api::context_menus::Create; | |
150 namespace Remove = api::context_menus::Remove; | |
151 namespace Update = api::context_menus::Update; | |
152 | |
153 bool ContextMenusCreateFunction::RunImpl() { | |
154 MenuItem::Id id(profile()->IsOffTheRecord(), extension_id()); | |
155 scoped_ptr<Create::Params> params(Create::Params::Create(*args_)); | |
156 EXTENSION_FUNCTION_VALIDATE(params.get()); | |
157 | |
158 if (params->create_properties.id.get()) { | |
159 id.string_uid = *params->create_properties.id; | |
160 } else { | |
161 if (GetExtension()->has_lazy_background_page()) { | |
162 error_ = kIdRequiredError; | |
163 return false; | |
164 } | |
165 | |
166 // The Generated Id is added by context_menus_custom_bindings.js. | |
167 DictionaryValue* properties = NULL; | |
168 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &properties)); | |
169 EXTENSION_FUNCTION_VALIDATE(properties->GetInteger(kGeneratedIdKey, | |
170 &id.uid)); | |
171 } | |
172 | |
173 std::string title; | |
174 if (params->create_properties.title.get()) | |
175 title = *params->create_properties.title; | |
176 | |
177 MenuManager* menu_manager = profile()->GetExtensionService()->menu_manager(); | |
178 | |
179 if (menu_manager->GetItemById(id)) { | |
180 error_ = ErrorUtils::FormatErrorMessage(kDuplicateIDError, | |
181 GetIDString(id)); | |
182 return false; | |
183 } | |
184 | |
185 if (GetExtension()->has_lazy_background_page() && | |
186 params->create_properties.onclick.get()) { | |
187 error_ = kOnclickDisallowedError; | |
188 return false; | |
189 } | |
190 | |
191 MenuItem::ContextList contexts; | |
192 if (params->create_properties.contexts.get()) | |
193 contexts = GetContexts(params->create_properties); | |
194 else | |
195 contexts.Add(MenuItem::PAGE); | |
196 | |
197 if (contexts.Contains(MenuItem::LAUNCHER) && | |
198 !GetExtension()->is_platform_app()) { | |
199 error_ = kLauncherNotAllowedError; | |
200 return false; | |
201 } | |
202 | |
203 MenuItem::Type type = GetType(params->create_properties); | |
204 | |
205 if (title.empty() && type != MenuItem::SEPARATOR) { | |
206 error_ = kTitleNeededError; | |
207 return false; | |
208 } | |
209 | |
210 bool checked = false; | |
211 if (params->create_properties.checked.get()) | |
212 checked = *params->create_properties.checked; | |
213 | |
214 bool enabled = true; | |
215 if (params->create_properties.enabled.get()) | |
216 enabled = *params->create_properties.enabled; | |
217 | |
218 scoped_ptr<MenuItem> item( | |
219 new MenuItem(id, title, checked, enabled, type, contexts)); | |
220 | |
221 if (!item->PopulateURLPatterns( | |
222 params->create_properties.document_url_patterns.get(), | |
223 params->create_properties.target_url_patterns.get(), | |
224 &error_)) { | |
225 return false; | |
226 } | |
227 | |
228 bool success = true; | |
229 scoped_ptr<MenuItem::Id> parent_id(GetParentId(params->create_properties, | |
230 profile()->IsOffTheRecord(), | |
231 extension_id())); | |
232 if (parent_id.get()) { | |
233 MenuItem* parent = GetParent(*parent_id, menu_manager, &error_); | |
234 if (!parent) | |
235 return false; | |
236 success = menu_manager->AddChildItem(parent->id(), item.release()); | |
237 } else { | |
238 success = menu_manager->AddContextItem(GetExtension(), item.release()); | |
239 } | |
240 | |
241 if (!success) | |
242 return false; | |
243 | |
244 menu_manager->WriteToStorage(GetExtension()); | |
245 return true; | |
246 } | |
247 | |
248 bool ContextMenusUpdateFunction::RunImpl() { | |
249 bool radio_item_updated = false; | |
250 MenuItem::Id item_id(profile()->IsOffTheRecord(), extension_id()); | |
251 scoped_ptr<Update::Params> params(Update::Params::Create(*args_)); | |
252 | |
253 EXTENSION_FUNCTION_VALIDATE(params.get()); | |
254 switch (params->id_type) { | |
255 case Update::Params::ID_STRING: | |
256 item_id.string_uid = *params->id_string; | |
257 break; | |
258 case Update::Params::ID_INTEGER: | |
259 item_id.uid = *params->id_integer; | |
260 break; | |
261 case Update::Params::ID_NONE: | |
262 NOTREACHED(); | |
263 } | |
264 | |
265 ExtensionService* service = profile()->GetExtensionService(); | |
266 MenuManager* manager = service->menu_manager(); | |
267 MenuItem* item = manager->GetItemById(item_id); | |
268 if (!item || item->extension_id() != extension_id()) { | |
269 error_ = ErrorUtils::FormatErrorMessage( | |
270 kCannotFindItemError, GetIDString(item_id)); | |
271 return false; | |
272 } | |
273 | |
274 // Type. | |
275 MenuItem::Type type = GetType(params->update_properties); | |
276 | |
277 if (type != item->type()) { | |
278 if (type == MenuItem::RADIO || item->type() == MenuItem::RADIO) | |
279 radio_item_updated = true; | |
280 item->set_type(type); | |
281 } | |
282 | |
283 // Title. | |
284 if (params->update_properties.title.get()) { | |
285 std::string title(*params->update_properties.title); | |
286 if (title.empty() && item->type() != MenuItem::SEPARATOR) { | |
287 error_ = kTitleNeededError; | |
288 return false; | |
289 } | |
290 item->set_title(title); | |
291 } | |
292 | |
293 // Checked state. | |
294 if (params->update_properties.checked.get()) { | |
295 bool checked = *params->update_properties.checked; | |
296 if (checked && | |
297 item->type() != MenuItem::CHECKBOX && | |
298 item->type() != MenuItem::RADIO) { | |
299 error_ = kCheckedError; | |
300 return false; | |
301 } | |
302 if (checked != item->checked()) { | |
303 if (!item->SetChecked(checked)) { | |
304 error_ = kCheckedError; | |
305 return false; | |
306 } | |
307 radio_item_updated = true; | |
308 } | |
309 } | |
310 | |
311 // Enabled. | |
312 if (params->update_properties.enabled.get()) | |
313 item->set_enabled(*params->update_properties.enabled); | |
314 | |
315 // Contexts. | |
316 MenuItem::ContextList contexts; | |
317 if (params->update_properties.contexts.get()) { | |
318 contexts = GetContexts(params->update_properties); | |
319 | |
320 if (contexts.Contains(MenuItem::LAUNCHER) && | |
321 !GetExtension()->is_platform_app()) { | |
322 error_ = kLauncherNotAllowedError; | |
323 return false; | |
324 } | |
325 | |
326 if (contexts != item->contexts()) | |
327 item->set_contexts(contexts); | |
328 } | |
329 | |
330 // Parent id. | |
331 MenuItem* parent = NULL; | |
332 scoped_ptr<MenuItem::Id> parent_id(GetParentId(params->update_properties, | |
333 profile()->IsOffTheRecord(), | |
334 extension_id())); | |
335 if (parent_id.get()) { | |
336 MenuItem* parent = GetParent(*parent_id, manager, &error_); | |
337 if (!parent || !manager->ChangeParent(item->id(), &parent->id())) | |
338 return false; | |
339 } | |
340 | |
341 // URL Patterns. | |
342 if (!item->PopulateURLPatterns( | |
343 params->update_properties.document_url_patterns.get(), | |
344 params->update_properties.target_url_patterns.get(), &error_)) { | |
345 return false; | |
346 } | |
347 | |
348 // There is no need to call ItemUpdated if ChangeParent is called because | |
349 // all sanitation is taken care of in ChangeParent. | |
350 if (!parent && radio_item_updated && !manager->ItemUpdated(item->id())) | |
351 return false; | |
352 | |
353 manager->WriteToStorage(GetExtension()); | |
354 return true; | |
355 } | |
356 | |
357 bool ContextMenusRemoveFunction::RunImpl() { | |
358 scoped_ptr<Remove::Params> params(Remove::Params::Create(*args_)); | |
359 EXTENSION_FUNCTION_VALIDATE(params.get()); | |
360 | |
361 ExtensionService* service = profile()->GetExtensionService(); | |
362 MenuManager* manager = service->menu_manager(); | |
363 | |
364 MenuItem::Id id(profile()->IsOffTheRecord(), extension_id()); | |
365 switch (params->menu_item_id_type) { | |
366 case Remove::Params::MENU_ITEM_ID_STRING: | |
367 id.string_uid = *params->menu_item_id_string; | |
368 break; | |
369 case Remove::Params::MENU_ITEM_ID_INTEGER: | |
370 id.uid = *params->menu_item_id_integer; | |
371 break; | |
372 case Remove::Params::MENU_ITEM_ID_NONE: | |
373 NOTREACHED(); | |
374 } | |
375 | |
376 MenuItem* item = manager->GetItemById(id); | |
377 // Ensure one extension can't remove another's menu items. | |
378 if (!item || item->extension_id() != extension_id()) { | |
379 error_ = ErrorUtils::FormatErrorMessage( | |
380 kCannotFindItemError, GetIDString(id)); | |
381 return false; | |
382 } | |
383 | |
384 if (!manager->RemoveContextMenuItem(id)) | |
385 return false; | |
386 manager->WriteToStorage(GetExtension()); | |
387 return true; | |
388 } | |
389 | |
390 bool ContextMenusRemoveAllFunction::RunImpl() { | |
391 ExtensionService* service = profile()->GetExtensionService(); | |
392 MenuManager* manager = service->menu_manager(); | |
393 manager->RemoveAllContextItems(GetExtension()->id()); | |
394 manager->WriteToStorage(GetExtension()); | |
395 return true; | |
396 } | |
397 | |
398 } // namespace extensions | |
OLD | NEW |