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