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_bookmark_manager_api.h"
6
7 #include <vector>
8
9 #include "base/json/json_writer.h"
10 #include "base/string_number_conversions.h"
11 #include "base/values.h"
12 #include "chrome/browser/bookmarks/bookmark_model.h"
13 #include "chrome/browser/bookmarks/bookmark_node_data.h"
14 #include "chrome/browser/bookmarks/bookmark_utils.h"
15 #include "chrome/browser/extensions/extension_bookmark_helpers.h"
16 #include "chrome/browser/extensions/extension_bookmarks_module_constants.h"
17 #include "chrome/browser/extensions/extension_event_router.h"
18 #include "chrome/browser/extensions/extension_web_ui.h"
19 #include "chrome/browser/profiles/profile.h"
20 #include "chrome/browser/ui/webui/chrome_url_data_manager.h"
21 #include "content/browser/renderer_host/render_view_host.h"
22 #include "content/browser/tab_contents/tab_contents.h"
23 #include "grit/generated_resources.h"
24 #include "ui/base/l10n/l10n_util.h"
25
26 namespace keys = extension_bookmarks_module_constants;
27
28 namespace {
29
30 // Returns a single bookmark node from the argument ID.
31 // This returns NULL in case of failure.
GetNodeFromArguments(BookmarkModel * model,const ListValue * args)32 const BookmarkNode* GetNodeFromArguments(BookmarkModel* model,
33 const ListValue* args) {
34 std::string id_string;
35 if (!args->GetString(0, &id_string))
36 return NULL;
37 int64 id;
38 if (!base::StringToInt64(id_string, &id))
39 return NULL;
40 return model->GetNodeByID(id);
41 }
42
43 // Gets a vector of bookmark nodes from the argument list of IDs.
44 // This returns false in the case of failure.
GetNodesFromArguments(BookmarkModel * model,const ListValue * args,size_t args_index,std::vector<const BookmarkNode * > * nodes)45 bool GetNodesFromArguments(BookmarkModel* model, const ListValue* args,
46 size_t args_index, std::vector<const BookmarkNode*>* nodes) {
47
48 ListValue* ids;
49 if (!args->GetList(args_index, &ids))
50 return false;
51
52 size_t count = ids->GetSize();
53 if (count == 0)
54 return false;
55
56 for (size_t i = 0; i < count; ++i) {
57 std::string id_string;
58 if (!ids->GetString(i, &id_string))
59 return false;
60 int64 id;
61 if (!base::StringToInt64(id_string, &id))
62 return false;
63 const BookmarkNode* node = model->GetNodeByID(id);
64 if (!node)
65 return false;
66 nodes->push_back(node);
67 }
68
69 return true;
70 }
71
72 // Recursively adds a node to a list. This is by used |BookmarkNodeDataToJSON|
73 // when the data comes from the current profile. In this case we have a
74 // BookmarkNode since we got the data from the current profile.
AddNodeToList(ListValue * list,const BookmarkNode & node)75 void AddNodeToList(ListValue* list, const BookmarkNode& node) {
76 DictionaryValue* dict = new DictionaryValue();
77
78 // Add id and parentId so we can associate the data with existing nodes on the
79 // client side.
80 std::string id_string = base::Int64ToString(node.id());
81 dict->SetString(keys::kIdKey, id_string);
82
83 std::string parent_id_string = base::Int64ToString(node.parent()->id());
84 dict->SetString(keys::kParentIdKey, parent_id_string);
85
86 if (node.is_url())
87 dict->SetString(keys::kUrlKey, node.GetURL().spec());
88
89 dict->SetString(keys::kTitleKey, node.GetTitle());
90
91 ListValue* children = new ListValue();
92 for (int i = 0; i < node.child_count(); ++i)
93 AddNodeToList(children, *node.GetChild(i));
94 dict->Set(keys::kChildrenKey, children);
95
96 list->Append(dict);
97 }
98
99 // Recursively adds an element to a list. This is used by
100 // |BookmarkNodeDataToJSON| when the data comes from a different profile. When
101 // the data comes from a different profile we do not have any IDs or parent IDs.
AddElementToList(ListValue * list,const BookmarkNodeData::Element & element)102 void AddElementToList(ListValue* list,
103 const BookmarkNodeData::Element& element) {
104 DictionaryValue* dict = new DictionaryValue();
105
106 if (element.is_url)
107 dict->SetString(keys::kUrlKey, element.url.spec());
108
109 dict->SetString(keys::kTitleKey, element.title);
110
111 ListValue* children = new ListValue();
112 for (size_t i = 0; i < element.children.size(); ++i)
113 AddElementToList(children, element.children[i]);
114 dict->Set(keys::kChildrenKey, children);
115
116 list->Append(dict);
117 }
118
119 // Builds the JSON structure based on the BookmarksDragData.
BookmarkNodeDataToJSON(Profile * profile,const BookmarkNodeData & data,ListValue * args)120 void BookmarkNodeDataToJSON(Profile* profile, const BookmarkNodeData& data,
121 ListValue* args) {
122 bool same_profile = data.IsFromProfile(profile);
123 DictionaryValue* value = new DictionaryValue();
124 value->SetBoolean(keys::kSameProfileKey, same_profile);
125
126 ListValue* list = new ListValue();
127 if (same_profile) {
128 std::vector<const BookmarkNode*> nodes = data.GetNodes(profile);
129 for (size_t i = 0; i < nodes.size(); ++i)
130 AddNodeToList(list, *nodes[i]);
131 } else {
132 // We do not have an node IDs when the data comes from a different profile.
133 std::vector<BookmarkNodeData::Element> elements = data.elements;
134 for (size_t i = 0; i < elements.size(); ++i)
135 AddElementToList(list, elements[i]);
136 }
137 value->Set(keys::kElementsKey, list);
138
139 args->Append(value);
140 }
141
142 } // namespace
143
ExtensionBookmarkManagerEventRouter(Profile * profile,TabContents * tab_contents)144 ExtensionBookmarkManagerEventRouter::ExtensionBookmarkManagerEventRouter(
145 Profile* profile, TabContents* tab_contents)
146 : profile_(profile),
147 tab_contents_(tab_contents) {
148 tab_contents_->SetBookmarkDragDelegate(this);
149 }
150
~ExtensionBookmarkManagerEventRouter()151 ExtensionBookmarkManagerEventRouter::~ExtensionBookmarkManagerEventRouter() {
152 if (tab_contents_->GetBookmarkDragDelegate() == this)
153 tab_contents_->SetBookmarkDragDelegate(NULL);
154 }
155
DispatchEvent(const char * event_name,const ListValue * args)156 void ExtensionBookmarkManagerEventRouter::DispatchEvent(const char* event_name,
157 const ListValue* args) {
158 if (!profile_->GetExtensionEventRouter())
159 return;
160
161 std::string json_args;
162 base::JSONWriter::Write(args, false, &json_args);
163 profile_->GetExtensionEventRouter()->DispatchEventToRenderers(
164 event_name, json_args, NULL, GURL());
165 }
166
DispatchDragEvent(const BookmarkNodeData & data,const char * event_name)167 void ExtensionBookmarkManagerEventRouter::DispatchDragEvent(
168 const BookmarkNodeData& data, const char* event_name) {
169 if (data.size() == 0)
170 return;
171
172 ListValue args;
173 BookmarkNodeDataToJSON(profile_, data, &args);
174 DispatchEvent(event_name, &args);
175 }
176
OnDragEnter(const BookmarkNodeData & data)177 void ExtensionBookmarkManagerEventRouter::OnDragEnter(
178 const BookmarkNodeData& data) {
179 DispatchDragEvent(data, keys::kOnBookmarkDragEnter);
180 }
181
OnDragOver(const BookmarkNodeData & data)182 void ExtensionBookmarkManagerEventRouter::OnDragOver(
183 const BookmarkNodeData& data) {
184 // Intentionally empty since these events happens too often and floods the
185 // message queue. We do not need this event for the bookmark manager anyway.
186 }
187
OnDragLeave(const BookmarkNodeData & data)188 void ExtensionBookmarkManagerEventRouter::OnDragLeave(
189 const BookmarkNodeData& data) {
190 DispatchDragEvent(data, keys::kOnBookmarkDragLeave);
191 }
192
OnDrop(const BookmarkNodeData & data)193 void ExtensionBookmarkManagerEventRouter::OnDrop(
194 const BookmarkNodeData& data) {
195 DispatchDragEvent(data, keys::kOnBookmarkDrop);
196
197 // Make a copy that is owned by this instance.
198 ClearBookmarkNodeData();
199 bookmark_drag_data_ = data;
200 }
201
202 const BookmarkNodeData*
GetBookmarkNodeData()203 ExtensionBookmarkManagerEventRouter::GetBookmarkNodeData() {
204 if (bookmark_drag_data_.is_valid())
205 return &bookmark_drag_data_;
206 return NULL;
207 }
208
ClearBookmarkNodeData()209 void ExtensionBookmarkManagerEventRouter::ClearBookmarkNodeData() {
210 bookmark_drag_data_.Clear();
211 }
212
CopyOrCut(bool cut)213 bool ClipboardBookmarkManagerFunction::CopyOrCut(bool cut) {
214 BookmarkModel* model = profile()->GetBookmarkModel();
215 std::vector<const BookmarkNode*> nodes;
216 EXTENSION_FUNCTION_VALIDATE(GetNodesFromArguments(model, args_.get(),
217 0, &nodes));
218 bookmark_utils::CopyToClipboard(model, nodes, cut);
219 return true;
220 }
221
RunImpl()222 bool CopyBookmarkManagerFunction::RunImpl() {
223 return CopyOrCut(false);
224 }
225
RunImpl()226 bool CutBookmarkManagerFunction::RunImpl() {
227 if (!EditBookmarksEnabled())
228 return false;
229 return CopyOrCut(true);
230 }
231
RunImpl()232 bool PasteBookmarkManagerFunction::RunImpl() {
233 if (!EditBookmarksEnabled())
234 return false;
235 BookmarkModel* model = profile()->GetBookmarkModel();
236 const BookmarkNode* parent_node = GetNodeFromArguments(model, args_.get());
237 if (!parent_node) {
238 error_ = keys::kNoParentError;
239 return false;
240 }
241 bool can_paste = bookmark_utils::CanPasteFromClipboard(parent_node);
242 if (!can_paste)
243 return false;
244
245 // We want to use the highest index of the selected nodes as a destination.
246 std::vector<const BookmarkNode*> nodes;
247 // No need to test return value, if we got an empty list, we insert at end.
248 GetNodesFromArguments(model, args_.get(), 1, &nodes);
249 int highest_index = -1; // -1 means insert at end of list.
250 for (size_t node = 0; node < nodes.size(); ++node) {
251 // + 1 so that we insert after the selection.
252 int this_node_index = parent_node->GetIndexOf(nodes[node]) + 1;
253 if (this_node_index > highest_index)
254 highest_index = this_node_index;
255 }
256
257 bookmark_utils::PasteFromClipboard(model, parent_node, highest_index);
258 return true;
259 }
260
RunImpl()261 bool CanPasteBookmarkManagerFunction::RunImpl() {
262 if (!EditBookmarksEnabled())
263 return false;
264 BookmarkModel* model = profile()->GetBookmarkModel();
265 const BookmarkNode* parent_node = GetNodeFromArguments(model, args_.get());
266 if (!parent_node) {
267 error_ = keys::kNoParentError;
268 return false;
269 }
270 bool can_paste = bookmark_utils::CanPasteFromClipboard(parent_node);
271 result_.reset(Value::CreateBooleanValue(can_paste));
272 SendResponse(true);
273 return true;
274 }
275
RunImpl()276 bool SortChildrenBookmarkManagerFunction::RunImpl() {
277 if (!EditBookmarksEnabled())
278 return false;
279 BookmarkModel* model = profile()->GetBookmarkModel();
280 const BookmarkNode* parent_node = GetNodeFromArguments(model, args_.get());
281 if (!parent_node) {
282 error_ = keys::kNoParentError;
283 return false;
284 }
285 model->SortChildren(parent_node);
286 return true;
287 }
288
RunImpl()289 bool BookmarkManagerGetStringsFunction::RunImpl() {
290 DictionaryValue* localized_strings = new DictionaryValue();
291
292 localized_strings->SetString("title",
293 l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_TITLE));
294 localized_strings->SetString("search_button",
295 l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_SEARCH_BUTTON));
296 localized_strings->SetString("show_in_folder",
297 l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_SHOW_IN_FOLDER));
298 localized_strings->SetString("sort",
299 l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_SORT));
300 localized_strings->SetString("organize_menu",
301 l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_ORGANIZE_MENU));
302 localized_strings->SetString("tools_menu",
303 l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_TOOLS_MENU));
304 localized_strings->SetString("import_menu",
305 l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_IMPORT_MENU));
306 localized_strings->SetString("export_menu",
307 l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_EXPORT_MENU));
308 localized_strings->SetString("rename_folder",
309 l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_RENAME_FOLDER));
310 localized_strings->SetString("edit",
311 l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_EDIT));
312 localized_strings->SetString("should_open_all",
313 l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_SHOULD_OPEN_ALL));
314 localized_strings->SetString("open_incognito",
315 l10n_util::GetStringUTF16(IDS_BOOMARK_BAR_OPEN_INCOGNITO));
316 localized_strings->SetString("open_in_new_tab",
317 l10n_util::GetStringUTF16(IDS_BOOMARK_BAR_OPEN_IN_NEW_TAB));
318 localized_strings->SetString("open_in_new_window",
319 l10n_util::GetStringUTF16(IDS_BOOMARK_BAR_OPEN_IN_NEW_WINDOW));
320 localized_strings->SetString("add_new_bookmark",
321 l10n_util::GetStringUTF16(IDS_BOOMARK_BAR_ADD_NEW_BOOKMARK));
322 localized_strings->SetString("new_folder",
323 l10n_util::GetStringUTF16(IDS_BOOMARK_BAR_NEW_FOLDER));
324 localized_strings->SetString("open_all",
325 l10n_util::GetStringUTF16(IDS_BOOMARK_BAR_OPEN_ALL));
326 localized_strings->SetString("open_all_new_window",
327 l10n_util::GetStringUTF16(IDS_BOOMARK_BAR_OPEN_ALL_NEW_WINDOW));
328 localized_strings->SetString("open_all_incognito",
329 l10n_util::GetStringUTF16(IDS_BOOMARK_BAR_OPEN_ALL_INCOGNITO));
330 localized_strings->SetString("remove",
331 l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_REMOVE));
332 localized_strings->SetString("copy",
333 l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_COPY));
334 localized_strings->SetString("cut",
335 l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_CUT));
336 localized_strings->SetString("paste",
337 l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_PASTE));
338 localized_strings->SetString("delete",
339 l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_DELETE));
340 localized_strings->SetString("new_folder_name",
341 l10n_util::GetStringUTF16(IDS_BOOMARK_EDITOR_NEW_FOLDER_NAME));
342 localized_strings->SetString("name_input_placeholder",
343 l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_NAME_INPUT_PLACE_HOLDER));
344 localized_strings->SetString("url_input_placeholder",
345 l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_URL_INPUT_PLACE_HOLDER));
346 localized_strings->SetString("invalid_url",
347 l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_INVALID_URL));
348 localized_strings->SetString("recent",
349 l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_RECENT));
350 localized_strings->SetString("search",
351 l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_SEARCH));
352
353 ChromeURLDataManager::DataSource::SetFontAndTextDirection(localized_strings);
354
355 result_.reset(localized_strings);
356 SendResponse(true);
357 return true;
358 }
359
RunImpl()360 bool StartDragBookmarkManagerFunction::RunImpl() {
361 if (!EditBookmarksEnabled())
362 return false;
363 BookmarkModel* model = profile()->GetBookmarkModel();
364 std::vector<const BookmarkNode*> nodes;
365 EXTENSION_FUNCTION_VALIDATE(
366 GetNodesFromArguments(model, args_.get(), 0, &nodes));
367
368 if (dispatcher()->render_view_host()->delegate()->GetRenderViewType() ==
369 ViewType::TAB_CONTENTS) {
370 ExtensionWebUI* web_ui =
371 static_cast<ExtensionWebUI*>(dispatcher()->delegate());
372 bookmark_utils::DragBookmarks(
373 profile(), nodes, web_ui->tab_contents()->GetNativeView());
374
375 return true;
376 } else {
377 NOTREACHED();
378 return false;
379 }
380 }
381
RunImpl()382 bool DropBookmarkManagerFunction::RunImpl() {
383 if (!EditBookmarksEnabled())
384 return false;
385
386 BookmarkModel* model = profile()->GetBookmarkModel();
387
388 int64 id;
389 std::string id_string;
390 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &id_string));
391
392 if (!base::StringToInt64(id_string, &id)) {
393 error_ = keys::kInvalidIdError;
394 return false;
395 }
396
397 const BookmarkNode* drop_parent = model->GetNodeByID(id);
398 if (!drop_parent) {
399 error_ = keys::kNoParentError;
400 return false;
401 }
402
403 int drop_index;
404 if (args_->GetSize() == 2)
405 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(1, &drop_index));
406 else
407 drop_index = drop_parent->child_count();
408
409 if (dispatcher()->render_view_host()->delegate()->GetRenderViewType() ==
410 ViewType::TAB_CONTENTS) {
411 ExtensionWebUI* web_ui =
412 static_cast<ExtensionWebUI*>(dispatcher()->delegate());
413 ExtensionBookmarkManagerEventRouter* router =
414 web_ui->extension_bookmark_manager_event_router();
415
416 DCHECK(router);
417 const BookmarkNodeData* drag_data = router->GetBookmarkNodeData();
418 if (drag_data == NULL) {
419 NOTREACHED() <<"Somehow we're dropping null bookmark data";
420 return false;
421 }
422 bookmark_utils::PerformBookmarkDrop(profile(),
423 *drag_data,
424 drop_parent, drop_index);
425
426 router->ClearBookmarkNodeData();
427 SendResponse(true);
428 return true;
429 } else {
430 NOTREACHED();
431 return false;
432 }
433 }
434
RunImpl()435 bool GetSubtreeBookmarkManagerFunction::RunImpl() {
436 BookmarkModel* model = profile()->GetBookmarkModel();
437 const BookmarkNode* node;
438 int64 id;
439 std::string id_string;
440 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &id_string));
441 bool folders_only;
442 EXTENSION_FUNCTION_VALIDATE(args_->GetBoolean(1, &folders_only));
443 if (id_string == "") {
444 node = model->root_node();
445 } else {
446 if (!base::StringToInt64(id_string, &id)) {
447 error_ = keys::kInvalidIdError;
448 return false;
449 }
450 node = model->GetNodeByID(id);
451 }
452 if (!node) {
453 error_ = keys::kNoNodeError;
454 return false;
455 }
456 scoped_ptr<ListValue> json(new ListValue());
457 if (folders_only) {
458 extension_bookmark_helpers::AddNodeFoldersOnly(node,
459 json.get(),
460 true);
461 } else {
462 extension_bookmark_helpers::AddNode(node, json.get(), true);
463 }
464 result_.reset(json.release());
465 return true;
466 }
467