• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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