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
26namespace keys = extension_bookmarks_module_constants;
27
28namespace {
29
30// Returns a single bookmark node from the argument ID.
31// This returns NULL in case of failure.
32const 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.
45bool 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.
75void 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.
102void 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.
120void 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
144ExtensionBookmarkManagerEventRouter::ExtensionBookmarkManagerEventRouter(
145    Profile* profile, TabContents* tab_contents)
146    : profile_(profile),
147    tab_contents_(tab_contents) {
148  tab_contents_->SetBookmarkDragDelegate(this);
149}
150
151ExtensionBookmarkManagerEventRouter::~ExtensionBookmarkManagerEventRouter() {
152  if (tab_contents_->GetBookmarkDragDelegate() == this)
153    tab_contents_->SetBookmarkDragDelegate(NULL);
154}
155
156void 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
167void 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
177void ExtensionBookmarkManagerEventRouter::OnDragEnter(
178    const BookmarkNodeData& data) {
179  DispatchDragEvent(data, keys::kOnBookmarkDragEnter);
180}
181
182void 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
188void ExtensionBookmarkManagerEventRouter::OnDragLeave(
189    const BookmarkNodeData& data) {
190  DispatchDragEvent(data, keys::kOnBookmarkDragLeave);
191}
192
193void 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
202const BookmarkNodeData*
203ExtensionBookmarkManagerEventRouter::GetBookmarkNodeData() {
204  if (bookmark_drag_data_.is_valid())
205    return &bookmark_drag_data_;
206  return NULL;
207}
208
209void ExtensionBookmarkManagerEventRouter::ClearBookmarkNodeData() {
210  bookmark_drag_data_.Clear();
211}
212
213bool 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
222bool CopyBookmarkManagerFunction::RunImpl() {
223  return CopyOrCut(false);
224}
225
226bool CutBookmarkManagerFunction::RunImpl() {
227  if (!EditBookmarksEnabled())
228    return false;
229  return CopyOrCut(true);
230}
231
232bool 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
261bool 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
276bool 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
289bool 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
360bool 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
382bool 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
435bool 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