1 // Copyright 2014 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 // Definition of helper functions for the ContextMenus API.
6
7 #ifndef CHROME_BROWSER_EXTENSIONS_API_CONTEXT_MENUS_CONTEXT_MENUS_API_HELPERS_H_
8 #define CHROME_BROWSER_EXTENSIONS_API_CONTEXT_MENUS_CONTEXT_MENUS_API_HELPERS_H_
9
10 #include "chrome/browser/extensions/menu_manager.h"
11 #include "chrome/browser/profiles/profile.h"
12 #include "extensions/common/error_utils.h"
13 #include "extensions/common/manifest_handlers/background_info.h"
14
15 namespace extensions {
16 namespace context_menus_api_helpers {
17
18 namespace {
19
20 template <typename PropertyWithEnumT>
GetParentId(const PropertyWithEnumT & property,bool is_off_the_record,const MenuItem::ExtensionKey & key)21 scoped_ptr<extensions::MenuItem::Id> GetParentId(
22 const PropertyWithEnumT& property,
23 bool is_off_the_record,
24 const MenuItem::ExtensionKey& key) {
25 if (!property.parent_id)
26 return scoped_ptr<extensions::MenuItem::Id>();
27
28 scoped_ptr<extensions::MenuItem::Id> parent_id(
29 new extensions::MenuItem::Id(is_off_the_record, key));
30 if (property.parent_id->as_integer)
31 parent_id->uid = *property.parent_id->as_integer;
32 else if (property.parent_id->as_string)
33 parent_id->string_uid = *property.parent_id->as_string;
34 else
35 NOTREACHED();
36 return parent_id.Pass();
37 }
38
39 } // namespace
40
41 extern const char kCannotFindItemError[];
42 extern const char kCheckedError[];
43 extern const char kDuplicateIDError[];
44 extern const char kGeneratedIdKey[];
45 extern const char kLauncherNotAllowedError[];
46 extern const char kOnclickDisallowedError[];
47 extern const char kParentsMustBeNormalError[];
48 extern const char kTitleNeededError[];
49
50 std::string GetIDString(const MenuItem::Id& id);
51
52 MenuItem* GetParent(MenuItem::Id parent_id,
53 const MenuManager* menu_manager,
54 std::string* error);
55
56 template<typename PropertyWithEnumT>
GetContexts(const PropertyWithEnumT & property)57 MenuItem::ContextList GetContexts(const PropertyWithEnumT& property) {
58 MenuItem::ContextList contexts;
59 for (size_t i = 0; i < property.contexts->size(); ++i) {
60 switch (property.contexts->at(i)) {
61 case PropertyWithEnumT::CONTEXTS_TYPE_ALL:
62 contexts.Add(extensions::MenuItem::ALL);
63 break;
64 case PropertyWithEnumT::CONTEXTS_TYPE_PAGE:
65 contexts.Add(extensions::MenuItem::PAGE);
66 break;
67 case PropertyWithEnumT::CONTEXTS_TYPE_SELECTION:
68 contexts.Add(extensions::MenuItem::SELECTION);
69 break;
70 case PropertyWithEnumT::CONTEXTS_TYPE_LINK:
71 contexts.Add(extensions::MenuItem::LINK);
72 break;
73 case PropertyWithEnumT::CONTEXTS_TYPE_EDITABLE:
74 contexts.Add(extensions::MenuItem::EDITABLE);
75 break;
76 case PropertyWithEnumT::CONTEXTS_TYPE_IMAGE:
77 contexts.Add(extensions::MenuItem::IMAGE);
78 break;
79 case PropertyWithEnumT::CONTEXTS_TYPE_VIDEO:
80 contexts.Add(extensions::MenuItem::VIDEO);
81 break;
82 case PropertyWithEnumT::CONTEXTS_TYPE_AUDIO:
83 contexts.Add(extensions::MenuItem::AUDIO);
84 break;
85 case PropertyWithEnumT::CONTEXTS_TYPE_FRAME:
86 contexts.Add(extensions::MenuItem::FRAME);
87 break;
88 case PropertyWithEnumT::CONTEXTS_TYPE_LAUNCHER:
89 // Not available for <webview>.
90 contexts.Add(extensions::MenuItem::LAUNCHER);
91 break;
92 case PropertyWithEnumT::CONTEXTS_TYPE_NONE:
93 NOTREACHED();
94 }
95 }
96 return contexts;
97 }
98
99 template<typename PropertyWithEnumT>
GetType(const PropertyWithEnumT & property,MenuItem::Type default_type)100 MenuItem::Type GetType(const PropertyWithEnumT& property,
101 MenuItem::Type default_type) {
102 switch (property.type) {
103 case PropertyWithEnumT::TYPE_NONE:
104 return default_type;
105 case PropertyWithEnumT::TYPE_NORMAL:
106 return extensions::MenuItem::NORMAL;
107 case PropertyWithEnumT::TYPE_CHECKBOX:
108 return extensions::MenuItem::CHECKBOX;
109 case PropertyWithEnumT::TYPE_RADIO:
110 return extensions::MenuItem::RADIO;
111 case PropertyWithEnumT::TYPE_SEPARATOR:
112 return extensions::MenuItem::SEPARATOR;
113 }
114 return extensions::MenuItem::NORMAL;
115 }
116
117 // Creates and adds a menu item from |create_properties|.
118 template<typename PropertyWithEnumT>
CreateMenuItem(const PropertyWithEnumT & create_properties,Profile * profile,const Extension * extension,const MenuItem::Id & item_id,std::string * error)119 bool CreateMenuItem(const PropertyWithEnumT& create_properties,
120 Profile* profile,
121 const Extension* extension,
122 const MenuItem::Id& item_id,
123 std::string* error) {
124 bool is_webview = item_id.extension_key.webview_instance_id != 0;
125 MenuManager* menu_manager = MenuManager::Get(profile);
126
127 if (menu_manager->GetItemById(item_id)) {
128 *error = ErrorUtils::FormatErrorMessage(kDuplicateIDError,
129 GetIDString(item_id));
130 return false;
131 }
132
133 if (!is_webview && BackgroundInfo::HasLazyBackgroundPage(extension) &&
134 create_properties.onclick.get()) {
135 *error = kOnclickDisallowedError;
136 return false;
137 }
138
139 // Contexts.
140 MenuItem::ContextList contexts;
141 if (create_properties.contexts.get())
142 contexts = GetContexts(create_properties);
143 else
144 contexts.Add(MenuItem::PAGE);
145
146 if (contexts.Contains(MenuItem::LAUNCHER)) {
147 // Launcher item is not allowed for <webview>.
148 if (!extension->is_platform_app() || is_webview) {
149 *error = kLauncherNotAllowedError;
150 return false;
151 }
152 }
153
154 // Title.
155 std::string title;
156 if (create_properties.title.get())
157 title = *create_properties.title;
158
159 MenuItem::Type type = GetType(create_properties, MenuItem::NORMAL);
160 if (title.empty() && type != MenuItem::SEPARATOR) {
161 *error = kTitleNeededError;
162 return false;
163 }
164
165 // Checked state.
166 bool checked = false;
167 if (create_properties.checked.get())
168 checked = *create_properties.checked;
169
170 // Enabled.
171 bool enabled = true;
172 if (create_properties.enabled.get())
173 enabled = *create_properties.enabled;
174
175 scoped_ptr<MenuItem> item(
176 new MenuItem(item_id, title, checked, enabled, type, contexts));
177
178 // URL Patterns.
179 if (!item->PopulateURLPatterns(
180 create_properties.document_url_patterns.get(),
181 create_properties.target_url_patterns.get(),
182 error)) {
183 return false;
184 }
185
186 // Parent id.
187 bool success = true;
188 scoped_ptr<MenuItem::Id> parent_id(GetParentId(
189 create_properties, profile->IsOffTheRecord(), item_id.extension_key));
190 if (parent_id.get()) {
191 MenuItem* parent = GetParent(*parent_id, menu_manager, error);
192 if (!parent)
193 return false;
194 success = menu_manager->AddChildItem(parent->id(), item.release());
195 } else {
196 success = menu_manager->AddContextItem(extension, item.release());
197 }
198
199 if (!success)
200 return false;
201
202 menu_manager->WriteToStorage(extension, item_id.extension_key);
203 return true;
204 }
205
206 // Updates a menu item from |update_properties|.
207 template<typename PropertyWithEnumT>
UpdateMenuItem(const PropertyWithEnumT & update_properties,Profile * profile,const Extension * extension,const MenuItem::Id & item_id,std::string * error)208 bool UpdateMenuItem(const PropertyWithEnumT& update_properties,
209 Profile* profile,
210 const Extension* extension,
211 const MenuItem::Id& item_id,
212 std::string* error) {
213 bool radio_item_updated = false;
214 bool is_webview = item_id.extension_key.webview_instance_id != 0;
215 MenuManager* menu_manager = MenuManager::Get(profile);
216
217 MenuItem* item = menu_manager->GetItemById(item_id);
218 if (!item || item->extension_id() != extension->id()){
219 *error = ErrorUtils::FormatErrorMessage(
220 kCannotFindItemError, GetIDString(item_id));
221 return false;
222 }
223
224 // Type.
225 MenuItem::Type type = GetType(update_properties, item->type());
226
227 if (type != item->type()) {
228 if (type == MenuItem::RADIO || item->type() == MenuItem::RADIO)
229 radio_item_updated = true;
230 item->set_type(type);
231 }
232
233 // Title.
234 if (update_properties.title.get()) {
235 std::string title(*update_properties.title);
236 if (title.empty() && item->type() != MenuItem::SEPARATOR) {
237 *error = kTitleNeededError;
238 return false;
239 }
240 item->set_title(title);
241 }
242
243 // Checked state.
244 if (update_properties.checked.get()) {
245 bool checked = *update_properties.checked;
246 if (checked &&
247 item->type() != MenuItem::CHECKBOX &&
248 item->type() != MenuItem::RADIO) {
249 *error = kCheckedError;
250 return false;
251 }
252 if (checked != item->checked()) {
253 if (!item->SetChecked(checked)) {
254 *error = kCheckedError;
255 return false;
256 }
257 radio_item_updated = true;
258 }
259 }
260
261 // Enabled.
262 if (update_properties.enabled.get())
263 item->set_enabled(*update_properties.enabled);
264
265 // Contexts.
266 MenuItem::ContextList contexts;
267 if (update_properties.contexts.get()) {
268 contexts = GetContexts(update_properties);
269
270 if (contexts.Contains(MenuItem::LAUNCHER)) {
271 // Launcher item is not allowed for <webview>.
272 if (!extension->is_platform_app() || is_webview) {
273 *error = kLauncherNotAllowedError;
274 return false;
275 }
276 }
277
278 if (contexts != item->contexts())
279 item->set_contexts(contexts);
280 }
281
282 // Parent id.
283 MenuItem* parent = NULL;
284 scoped_ptr<MenuItem::Id> parent_id(GetParentId(
285 update_properties, profile->IsOffTheRecord(), item_id.extension_key));
286 if (parent_id.get()) {
287 MenuItem* parent = GetParent(*parent_id, menu_manager, error);
288 if (!parent || !menu_manager->ChangeParent(item->id(), &parent->id()))
289 return false;
290 }
291
292 // URL Patterns.
293 if (!item->PopulateURLPatterns(
294 update_properties.document_url_patterns.get(),
295 update_properties.target_url_patterns.get(), error)) {
296 return false;
297 }
298
299 // There is no need to call ItemUpdated if ChangeParent is called because
300 // all sanitation is taken care of in ChangeParent.
301 if (!parent && radio_item_updated && !menu_manager->ItemUpdated(item->id()))
302 return false;
303
304 menu_manager->WriteToStorage(extension, item_id.extension_key);
305 return true;
306 }
307
308 } // namespace context_menus_api_helpers
309 } // namespace extensions
310
311 #endif // CHROME_BROWSER_EXTENSIONS_API_CONTEXT_MENUS_CONTEXT_MENUS_API_HELPERS_H_
312