1 // Copyright (c) 2011 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_context_menu_api.h"
6
7 #include <string>
8
9 #include "base/values.h"
10 #include "base/string_number_conversions.h"
11 #include "base/string_util.h"
12 #include "chrome/browser/extensions/extension_service.h"
13 #include "chrome/browser/profiles/profile.h"
14 #include "chrome/common/extensions/extension_error_utils.h"
15
16 const char kCheckedKey[] = "checked";
17 const char kContextsKey[] = "contexts";
18 const char kDocumentUrlPatternsKey[] = "documentUrlPatterns";
19 const char kGeneratedIdKey[] = "generatedId";
20 const char kParentIdKey[] = "parentId";
21 const char kTargetUrlPatternsKey[] = "targetUrlPatterns";
22 const char kTitleKey[] = "title";
23 const char kTypeKey[] = "type";
24
25 const char kCannotFindItemError[] = "Cannot find menu item with id *";
26 const char kCheckedError[] =
27 "Only items with type \"radio\" or \"checkbox\" can be checked";
28 const char kInvalidURLPatternError[] = "Invalid url pattern '*'";
29 const char kInvalidValueError[] = "Invalid value for *";
30 const char kInvalidTypeStringError[] = "Invalid type string '*'";
31 const char kParentsMustBeNormalError[] =
32 "Parent items must have type \"normal\"";
33 const char kTitleNeededError[] =
34 "All menu items except for separators must have a title";
35
36
ParseContexts(const DictionaryValue & properties,const char * key,ExtensionMenuItem::ContextList * result)37 bool ExtensionContextMenuFunction::ParseContexts(
38 const DictionaryValue& properties,
39 const char* key,
40 ExtensionMenuItem::ContextList* result) {
41 ListValue* list = NULL;
42 if (!properties.GetList(key, &list)) {
43 return true;
44 }
45 ExtensionMenuItem::ContextList tmp_result;
46
47 std::string value;
48 for (size_t i = 0; i < list->GetSize(); i++) {
49 if (!list->GetString(i, &value))
50 return false;
51
52 if (value == "all") {
53 tmp_result.Add(ExtensionMenuItem::ALL);
54 } else if (value == "page") {
55 tmp_result.Add(ExtensionMenuItem::PAGE);
56 } else if (value == "selection") {
57 tmp_result.Add(ExtensionMenuItem::SELECTION);
58 } else if (value == "link") {
59 tmp_result.Add(ExtensionMenuItem::LINK);
60 } else if (value == "editable") {
61 tmp_result.Add(ExtensionMenuItem::EDITABLE);
62 } else if (value == "image") {
63 tmp_result.Add(ExtensionMenuItem::IMAGE);
64 } else if (value == "video") {
65 tmp_result.Add(ExtensionMenuItem::VIDEO);
66 } else if (value == "audio") {
67 tmp_result.Add(ExtensionMenuItem::AUDIO);
68 } else if (value == "frame") {
69 tmp_result.Add(ExtensionMenuItem::FRAME);
70 } else {
71 error_ = ExtensionErrorUtils::FormatErrorMessage(kInvalidValueError, key);
72 return false;
73 }
74 }
75 *result = tmp_result;
76 return true;
77 }
78
ParseType(const DictionaryValue & properties,const ExtensionMenuItem::Type & default_value,ExtensionMenuItem::Type * result)79 bool ExtensionContextMenuFunction::ParseType(
80 const DictionaryValue& properties,
81 const ExtensionMenuItem::Type& default_value,
82 ExtensionMenuItem::Type* result) {
83 DCHECK(result);
84 if (!properties.HasKey(kTypeKey)) {
85 *result = default_value;
86 return true;
87 }
88
89 std::string type_string;
90 if (!properties.GetString(kTypeKey, &type_string))
91 return false;
92
93 if (type_string == "normal") {
94 *result = ExtensionMenuItem::NORMAL;
95 } else if (type_string == "checkbox") {
96 *result = ExtensionMenuItem::CHECKBOX;
97 } else if (type_string == "radio") {
98 *result = ExtensionMenuItem::RADIO;
99 } else if (type_string == "separator") {
100 *result = ExtensionMenuItem::SEPARATOR;
101 } else {
102 error_ = ExtensionErrorUtils::FormatErrorMessage(kInvalidTypeStringError,
103 type_string);
104 return false;
105 }
106 return true;
107 }
108
ParseChecked(ExtensionMenuItem::Type type,const DictionaryValue & properties,bool default_value,bool * checked)109 bool ExtensionContextMenuFunction::ParseChecked(
110 ExtensionMenuItem::Type type,
111 const DictionaryValue& properties,
112 bool default_value,
113 bool* checked) {
114 if (!properties.HasKey(kCheckedKey)) {
115 *checked = default_value;
116 return true;
117 }
118 if (!properties.GetBoolean(kCheckedKey, checked))
119 return false;
120 if (checked && type != ExtensionMenuItem::CHECKBOX &&
121 type != ExtensionMenuItem::RADIO) {
122 error_ = kCheckedError;
123 return false;
124 }
125 return true;
126 }
127
ParseURLPatterns(const DictionaryValue & properties,const char * key,ExtensionExtent * result)128 bool ExtensionContextMenuFunction::ParseURLPatterns(
129 const DictionaryValue& properties,
130 const char* key,
131 ExtensionExtent* result) {
132 if (!properties.HasKey(key))
133 return true;
134 ListValue* list = NULL;
135 if (!properties.GetList(key, &list))
136 return false;
137 for (ListValue::iterator i = list->begin(); i != list->end(); ++i) {
138 std::string tmp;
139 if (!(*i)->GetAsString(&tmp))
140 return false;
141
142 URLPattern pattern(ExtensionMenuManager::kAllowedSchemes);
143 // TODO(skerner): Consider enabling strict pattern parsing
144 // if this extension's location indicates that it is under development.
145 if (URLPattern::PARSE_SUCCESS != pattern.Parse(tmp,
146 URLPattern::PARSE_LENIENT)) {
147 error_ = ExtensionErrorUtils::FormatErrorMessage(kInvalidURLPatternError,
148 tmp);
149 return false;
150 }
151 result->AddPattern(pattern);
152 }
153 return true;
154 }
155
SetURLPatterns(const DictionaryValue & properties,ExtensionMenuItem * item)156 bool ExtensionContextMenuFunction::SetURLPatterns(
157 const DictionaryValue& properties,
158 ExtensionMenuItem* item) {
159 // Process the documentUrlPattern value.
160 ExtensionExtent document_url_patterns;
161 if (!ParseURLPatterns(properties, kDocumentUrlPatternsKey,
162 &document_url_patterns))
163 return false;
164
165 if (!document_url_patterns.is_empty()) {
166 item->set_document_url_patterns(document_url_patterns);
167 }
168
169 // Process the targetUrlPattern value.
170 ExtensionExtent target_url_patterns;
171 if (!ParseURLPatterns(properties, kTargetUrlPatternsKey,
172 &target_url_patterns))
173 return false;
174
175 if (!target_url_patterns.is_empty()) {
176 item->set_target_url_patterns(target_url_patterns);
177 }
178
179 return true;
180 }
181
GetParent(const DictionaryValue & properties,const ExtensionMenuManager & manager,ExtensionMenuItem ** result)182 bool ExtensionContextMenuFunction::GetParent(
183 const DictionaryValue& properties,
184 const ExtensionMenuManager& manager,
185 ExtensionMenuItem** result) {
186 if (!properties.HasKey(kParentIdKey))
187 return true;
188 ExtensionMenuItem::Id parent_id(profile(), extension_id(), 0);
189 if (properties.HasKey(kParentIdKey) &&
190 !properties.GetInteger(kParentIdKey, &parent_id.uid))
191 return false;
192
193 ExtensionMenuItem* parent = manager.GetItemById(parent_id);
194 if (!parent) {
195 error_ = "Cannot find menu item with id " +
196 base::IntToString(parent_id.uid);
197 return false;
198 }
199 if (parent->type() != ExtensionMenuItem::NORMAL) {
200 error_ = kParentsMustBeNormalError;
201 return false;
202 }
203 *result = parent;
204 return true;
205 }
206
RunImpl()207 bool CreateContextMenuFunction::RunImpl() {
208 DictionaryValue* properties;
209 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &properties));
210 EXTENSION_FUNCTION_VALIDATE(properties != NULL);
211
212 ExtensionMenuItem::Id id(profile(), extension_id(), 0);
213 EXTENSION_FUNCTION_VALIDATE(properties->GetInteger(kGeneratedIdKey,
214 &id.uid));
215 std::string title;
216 if (properties->HasKey(kTitleKey) &&
217 !properties->GetString(kTitleKey, &title))
218 return false;
219
220 ExtensionMenuManager* menu_manager =
221 profile()->GetExtensionService()->menu_manager();
222
223 ExtensionMenuItem::ContextList contexts(ExtensionMenuItem::PAGE);
224 if (!ParseContexts(*properties, kContextsKey, &contexts))
225 return false;
226
227 ExtensionMenuItem::Type type;
228 if (!ParseType(*properties, ExtensionMenuItem::NORMAL, &type))
229 return false;
230
231 if (title.empty() && type != ExtensionMenuItem::SEPARATOR) {
232 error_ = kTitleNeededError;
233 return false;
234 }
235
236 bool checked;
237 if (!ParseChecked(type, *properties, false, &checked))
238 return false;
239
240 scoped_ptr<ExtensionMenuItem> item(
241 new ExtensionMenuItem(id, title, checked, type, contexts));
242
243 if (!SetURLPatterns(*properties, item.get()))
244 return false;
245
246 bool success = true;
247 if (properties->HasKey(kParentIdKey)) {
248 ExtensionMenuItem::Id parent_id(profile(), extension_id(), 0);
249 EXTENSION_FUNCTION_VALIDATE(properties->GetInteger(kParentIdKey,
250 &parent_id.uid));
251 ExtensionMenuItem* parent = menu_manager->GetItemById(parent_id);
252 if (!parent) {
253 error_ = ExtensionErrorUtils::FormatErrorMessage(
254 kCannotFindItemError, base::IntToString(parent_id.uid));
255 return false;
256 }
257 if (parent->type() != ExtensionMenuItem::NORMAL) {
258 error_ = kParentsMustBeNormalError;
259 return false;
260 }
261 success = menu_manager->AddChildItem(parent_id, item.release());
262 } else {
263 success = menu_manager->AddContextItem(GetExtension(), item.release());
264 }
265
266 if (!success)
267 return false;
268
269 return true;
270 }
271
RunImpl()272 bool UpdateContextMenuFunction::RunImpl() {
273 ExtensionMenuItem::Id item_id(profile(), extension_id(), 0);
274 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &item_id.uid));
275
276 ExtensionService* service = profile()->GetExtensionService();
277 ExtensionMenuManager* manager = service->menu_manager();
278 ExtensionMenuItem* item = manager->GetItemById(item_id);
279 if (!item || item->extension_id() != extension_id()) {
280 error_ = ExtensionErrorUtils::FormatErrorMessage(
281 kCannotFindItemError, base::IntToString(item_id.uid));
282 return false;
283 }
284
285 DictionaryValue *properties = NULL;
286 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &properties));
287 EXTENSION_FUNCTION_VALIDATE(properties != NULL);
288
289 ExtensionMenuManager* menu_manager =
290 profile()->GetExtensionService()->menu_manager();
291
292 // Type.
293 ExtensionMenuItem::Type type;
294 if (!ParseType(*properties, item->type(), &type))
295 return false;
296 if (type != item->type())
297 item->set_type(type);
298
299 // Title.
300 if (properties->HasKey(kTitleKey)) {
301 std::string title;
302 EXTENSION_FUNCTION_VALIDATE(properties->GetString(kTitleKey, &title));
303 if (title.empty() && type != ExtensionMenuItem::SEPARATOR) {
304 error_ = kTitleNeededError;
305 return false;
306 }
307 item->set_title(title);
308 }
309
310 // Checked state.
311 bool checked;
312 if (!ParseChecked(item->type(), *properties, item->checked(), &checked))
313 return false;
314 if (checked != item->checked()) {
315 if (!item->SetChecked(checked))
316 return false;
317 }
318
319 // Contexts.
320 ExtensionMenuItem::ContextList contexts(item->contexts());
321 if (!ParseContexts(*properties, kContextsKey, &contexts))
322 return false;
323 if (contexts != item->contexts())
324 item->set_contexts(contexts);
325
326 // Parent id.
327 ExtensionMenuItem* parent = NULL;
328 if (!GetParent(*properties, *menu_manager, &parent))
329 return false;
330 if (parent && !menu_manager->ChangeParent(item->id(), &parent->id()))
331 return false;
332
333 if (!SetURLPatterns(*properties, item))
334 return false;
335
336 return true;
337 }
338
RunImpl()339 bool RemoveContextMenuFunction::RunImpl() {
340 ExtensionMenuItem::Id id(profile(), extension_id(), 0);
341 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &id.uid));
342 ExtensionService* service = profile()->GetExtensionService();
343 ExtensionMenuManager* manager = service->menu_manager();
344
345 ExtensionMenuItem* item = manager->GetItemById(id);
346 // Ensure one extension can't remove another's menu items.
347 if (!item || item->extension_id() != extension_id()) {
348 error_ = ExtensionErrorUtils::FormatErrorMessage(
349 kCannotFindItemError, base::IntToString(id.uid));
350 return false;
351 }
352
353 return manager->RemoveContextMenuItem(id);
354 }
355
RunImpl()356 bool RemoveAllContextMenusFunction::RunImpl() {
357 ExtensionService* service = profile()->GetExtensionService();
358 ExtensionMenuManager* manager = service->menu_manager();
359 manager->RemoveAllContextItems(extension_id());
360 return true;
361 }
362