bookmark_manager_private_api.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
1// Copyright (c) 2012 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/api/bookmark_manager_private/bookmark_manager_private_api.h"
6
7#include <vector>
8
9#include "base/json/json_writer.h"
10#include "base/prefs/pref_service.h"
11#include "base/strings/string_number_conversions.h"
12#include "base/values.h"
13#include "chrome/browser/bookmarks/bookmark_model.h"
14#include "chrome/browser/bookmarks/bookmark_model_factory.h"
15#include "chrome/browser/bookmarks/bookmark_node_data.h"
16#include "chrome/browser/bookmarks/bookmark_utils.h"
17#include "chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api_constants.h"
18#include "chrome/browser/extensions/api/bookmarks/bookmark_api_constants.h"
19#include "chrome/browser/extensions/api/bookmarks/bookmark_api_helpers.h"
20#include "chrome/browser/extensions/event_router.h"
21#include "chrome/browser/extensions/extension_function_dispatcher.h"
22#include "chrome/browser/extensions/extension_system.h"
23#include "chrome/browser/extensions/extension_web_ui.h"
24#include "chrome/browser/profiles/profile.h"
25#include "chrome/browser/ui/bookmarks/bookmark_drag_drop.h"
26#include "chrome/browser/view_type_utils.h"
27#include "chrome/common/pref_names.h"
28#include "components/user_prefs/user_prefs.h"
29#include "content/public/browser/render_view_host.h"
30#include "content/public/browser/web_contents.h"
31#include "content/public/browser/web_contents_view.h"
32#include "content/public/browser/web_ui.h"
33#include "grit/generated_resources.h"
34#include "ui/base/l10n/l10n_util.h"
35#include "ui/webui/web_ui_util.h"
36
37#if defined(OS_WIN)
38#include "win8/util/win8_util.h"
39#endif  // OS_WIN
40
41namespace extensions {
42
43namespace bookmark_keys = bookmark_api_constants;
44namespace manager_keys = bookmark_manager_api_constants;
45
46using content::WebContents;
47
48namespace {
49
50// Returns a single bookmark node from the argument ID.
51// This returns NULL in case of failure.
52const BookmarkNode* GetNodeFromArguments(BookmarkModel* model,
53    const ListValue* args) {
54  std::string id_string;
55  if (!args->GetString(0, &id_string))
56    return NULL;
57  int64 id;
58  if (!base::StringToInt64(id_string, &id))
59    return NULL;
60  return model->GetNodeByID(id);
61}
62
63// Gets a vector of bookmark nodes from the argument list of IDs.
64// This returns false in the case of failure.
65bool GetNodesFromArguments(BookmarkModel* model, const ListValue* args,
66    size_t args_index, std::vector<const BookmarkNode*>* nodes) {
67
68  const ListValue* ids;
69  if (!args->GetList(args_index, &ids))
70    return false;
71
72  size_t count = ids->GetSize();
73  if (count == 0)
74    return false;
75
76  for (size_t i = 0; i < count; ++i) {
77    std::string id_string;
78    if (!ids->GetString(i, &id_string))
79      return false;
80    int64 id;
81    if (!base::StringToInt64(id_string, &id))
82      return false;
83    const BookmarkNode* node = model->GetNodeByID(id);
84    if (!node)
85      return false;
86    nodes->push_back(node);
87  }
88
89  return true;
90}
91
92// Recursively adds a node to a list. This is by used |BookmarkNodeDataToJSON|
93// when the data comes from the current profile. In this case we have a
94// BookmarkNode since we got the data from the current profile.
95void AddNodeToList(ListValue* list, const BookmarkNode& node) {
96  DictionaryValue* dict = new DictionaryValue();
97
98  // Add id and parentId so we can associate the data with existing nodes on the
99  // client side.
100  std::string id_string = base::Int64ToString(node.id());
101  dict->SetString(bookmark_keys::kIdKey, id_string);
102
103  std::string parent_id_string = base::Int64ToString(node.parent()->id());
104  dict->SetString(bookmark_keys::kParentIdKey, parent_id_string);
105
106  if (node.is_url())
107    dict->SetString(bookmark_keys::kUrlKey, node.url().spec());
108
109  dict->SetString(bookmark_keys::kTitleKey, node.GetTitle());
110
111  ListValue* children = new ListValue();
112  for (int i = 0; i < node.child_count(); ++i)
113    AddNodeToList(children, *node.GetChild(i));
114  dict->Set(bookmark_keys::kChildrenKey, children);
115
116  list->Append(dict);
117}
118
119// Recursively adds an element to a list. This is used by
120// |BookmarkNodeDataToJSON| when the data comes from a different profile. When
121// the data comes from a different profile we do not have any IDs or parent IDs.
122void AddElementToList(ListValue* list,
123                      const BookmarkNodeData::Element& element) {
124  DictionaryValue* dict = new DictionaryValue();
125
126  if (element.is_url)
127    dict->SetString(bookmark_keys::kUrlKey, element.url.spec());
128
129  dict->SetString(bookmark_keys::kTitleKey, element.title);
130
131  ListValue* children = new ListValue();
132  for (size_t i = 0; i < element.children.size(); ++i)
133    AddElementToList(children, element.children[i]);
134  dict->Set(bookmark_keys::kChildrenKey, children);
135
136  list->Append(dict);
137}
138
139// Builds the JSON structure based on the BookmarksDragData.
140void BookmarkNodeDataToJSON(Profile* profile, const BookmarkNodeData& data,
141                            ListValue* args) {
142  bool same_profile = data.IsFromProfile(profile);
143  DictionaryValue* value = new DictionaryValue();
144  value->SetBoolean(manager_keys::kSameProfileKey, same_profile);
145
146  ListValue* list = new ListValue();
147  if (same_profile) {
148    std::vector<const BookmarkNode*> nodes = data.GetNodes(profile);
149    for (size_t i = 0; i < nodes.size(); ++i)
150      AddNodeToList(list, *nodes[i]);
151  } else {
152    // We do not have an node IDs when the data comes from a different profile.
153    std::vector<BookmarkNodeData::Element> elements = data.elements;
154    for (size_t i = 0; i < elements.size(); ++i)
155      AddElementToList(list, elements[i]);
156  }
157  value->Set(manager_keys::kElementsKey, list);
158
159  args->Append(value);
160}
161
162}  // namespace
163
164BookmarkManagerPrivateEventRouter::BookmarkManagerPrivateEventRouter(
165    Profile* profile,
166    content::WebContents* web_contents)
167    : profile_(profile),
168      web_contents_(web_contents) {
169  BookmarkTabHelper* bookmark_tab_helper =
170      BookmarkTabHelper::FromWebContents(web_contents_);
171  bookmark_tab_helper->set_bookmark_drag_delegate(this);
172}
173
174BookmarkManagerPrivateEventRouter::~BookmarkManagerPrivateEventRouter() {
175  BookmarkTabHelper* bookmark_tab_helper =
176      BookmarkTabHelper::FromWebContents(web_contents_);
177  if (bookmark_tab_helper->bookmark_drag_delegate() == this)
178    bookmark_tab_helper->set_bookmark_drag_delegate(NULL);
179}
180
181void BookmarkManagerPrivateEventRouter::DispatchEvent(
182    const char* event_name,
183    scoped_ptr<ListValue> args) {
184  if (!ExtensionSystem::Get(profile_)->event_router())
185    return;
186
187  scoped_ptr<Event> event(new Event(event_name, args.Pass()));
188  ExtensionSystem::Get(profile_)->event_router()->BroadcastEvent(event.Pass());
189}
190
191void BookmarkManagerPrivateEventRouter::DispatchDragEvent(
192    const BookmarkNodeData& data,
193    const char* event_name) {
194  if (data.size() == 0)
195    return;
196
197  scoped_ptr<ListValue> args(new ListValue());
198  BookmarkNodeDataToJSON(profile_, data, args.get());
199  DispatchEvent(event_name, args.Pass());
200}
201
202void BookmarkManagerPrivateEventRouter::OnDragEnter(
203    const BookmarkNodeData& data) {
204  DispatchDragEvent(data, manager_keys::kOnBookmarkDragEnter);
205}
206
207void BookmarkManagerPrivateEventRouter::OnDragOver(
208    const BookmarkNodeData& data) {
209  // Intentionally empty since these events happens too often and floods the
210  // message queue. We do not need this event for the bookmark manager anyway.
211}
212
213void BookmarkManagerPrivateEventRouter::OnDragLeave(
214    const BookmarkNodeData& data) {
215  DispatchDragEvent(data, manager_keys::kOnBookmarkDragLeave);
216}
217
218void BookmarkManagerPrivateEventRouter::OnDrop(const BookmarkNodeData& data) {
219  DispatchDragEvent(data, manager_keys::kOnBookmarkDrop);
220
221  // Make a copy that is owned by this instance.
222  ClearBookmarkNodeData();
223  bookmark_drag_data_ = data;
224}
225
226const BookmarkNodeData*
227BookmarkManagerPrivateEventRouter::GetBookmarkNodeData() {
228  if (bookmark_drag_data_.is_valid())
229    return &bookmark_drag_data_;
230  return NULL;
231}
232
233void BookmarkManagerPrivateEventRouter::ClearBookmarkNodeData() {
234  bookmark_drag_data_.Clear();
235}
236
237bool ClipboardBookmarkManagerFunction::CopyOrCut(bool cut) {
238  BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
239  std::vector<const BookmarkNode*> nodes;
240  EXTENSION_FUNCTION_VALIDATE(GetNodesFromArguments(model, args_.get(),
241                                                    0, &nodes));
242  bookmark_utils::CopyToClipboard(model, nodes, cut);
243  return true;
244}
245
246bool BookmarkManagerPrivateCopyFunction::RunImpl() {
247  return CopyOrCut(false);
248}
249
250bool BookmarkManagerPrivateCutFunction::RunImpl() {
251  if (!EditBookmarksEnabled())
252    return false;
253  return CopyOrCut(true);
254}
255
256bool BookmarkManagerPrivatePasteFunction::RunImpl() {
257  if (!EditBookmarksEnabled())
258    return false;
259  BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
260  const BookmarkNode* parent_node = GetNodeFromArguments(model, args_.get());
261  if (!parent_node) {
262    error_ = bookmark_keys::kNoParentError;
263    return false;
264  }
265  bool can_paste = bookmark_utils::CanPasteFromClipboard(parent_node);
266  if (!can_paste)
267    return false;
268
269  // We want to use the highest index of the selected nodes as a destination.
270  std::vector<const BookmarkNode*> nodes;
271  // No need to test return value, if we got an empty list, we insert at end.
272  GetNodesFromArguments(model, args_.get(), 1, &nodes);
273  int highest_index = -1;  // -1 means insert at end of list.
274  for (size_t i = 0; i < nodes.size(); ++i) {
275    // + 1 so that we insert after the selection.
276    int index = parent_node->GetIndexOf(nodes[i]) + 1;
277    if (index > highest_index)
278      highest_index = index;
279  }
280
281  bookmark_utils::PasteFromClipboard(model, parent_node, highest_index);
282  return true;
283}
284
285bool BookmarkManagerPrivateCanPasteFunction::RunImpl() {
286  if (!EditBookmarksEnabled())
287    return false;
288  BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
289  const BookmarkNode* parent_node = GetNodeFromArguments(model, args_.get());
290  if (!parent_node) {
291    error_ = bookmark_keys::kNoParentError;
292    return false;
293  }
294  bool can_paste = bookmark_utils::CanPasteFromClipboard(parent_node);
295  SetResult(new base::FundamentalValue(can_paste));
296  return true;
297}
298
299bool BookmarkManagerPrivateSortChildrenFunction::RunImpl() {
300  if (!EditBookmarksEnabled())
301    return false;
302  BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
303  const BookmarkNode* parent_node = GetNodeFromArguments(model, args_.get());
304  if (!parent_node) {
305    error_ = bookmark_keys::kNoParentError;
306    return false;
307  }
308  model->SortChildren(parent_node);
309  return true;
310}
311
312bool BookmarkManagerPrivateGetStringsFunction::RunImpl() {
313  DictionaryValue* localized_strings = new DictionaryValue();
314
315  localized_strings->SetString("title",
316      l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_TITLE));
317  localized_strings->SetString("search_button",
318      l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_SEARCH_BUTTON));
319  localized_strings->SetString("show_in_folder",
320      l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_SHOW_IN_FOLDER));
321  localized_strings->SetString("sort",
322      l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_SORT));
323  localized_strings->SetString("organize_menu",
324      l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_ORGANIZE_MENU));
325  localized_strings->SetString("tools_menu",
326      l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_TOOLS_MENU));
327  localized_strings->SetString("import_menu",
328      l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_IMPORT_MENU));
329  localized_strings->SetString("export_menu",
330      l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_EXPORT_MENU));
331  localized_strings->SetString("rename_folder",
332      l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_RENAME_FOLDER));
333  localized_strings->SetString("edit",
334      l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_EDIT));
335  localized_strings->SetString("should_open_all",
336      l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_SHOULD_OPEN_ALL));
337  localized_strings->SetString("open_incognito",
338      l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_OPEN_INCOGNITO));
339  localized_strings->SetString("open_in_new_tab",
340      l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_OPEN_IN_NEW_TAB));
341  localized_strings->SetString("open_in_new_window",
342      l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_OPEN_IN_NEW_WINDOW));
343  localized_strings->SetString("add_new_bookmark",
344      l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_ADD_NEW_BOOKMARK));
345  localized_strings->SetString("new_folder",
346      l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_NEW_FOLDER));
347  localized_strings->SetString("open_all",
348      l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_OPEN_ALL));
349  localized_strings->SetString("open_all_new_window",
350      l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW));
351  localized_strings->SetString("open_all_incognito",
352      l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_OPEN_ALL_INCOGNITO));
353  localized_strings->SetString("remove",
354      l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_REMOVE));
355  localized_strings->SetString("copy",
356      l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_COPY));
357  localized_strings->SetString("cut",
358      l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_CUT));
359  localized_strings->SetString("paste",
360      l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_PASTE));
361  localized_strings->SetString("delete",
362      l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_DELETE));
363  localized_strings->SetString("undo_delete",
364      l10n_util::GetStringUTF16(IDS_UNDO_DELETE));
365  localized_strings->SetString("new_folder_name",
366      l10n_util::GetStringUTF16(IDS_BOOKMARK_EDITOR_NEW_FOLDER_NAME));
367  localized_strings->SetString("name_input_placeholder",
368      l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_NAME_INPUT_PLACE_HOLDER));
369  localized_strings->SetString("url_input_placeholder",
370      l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_URL_INPUT_PLACE_HOLDER));
371  localized_strings->SetString("invalid_url",
372      l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_INVALID_URL));
373  localized_strings->SetString("recent",
374      l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_RECENT));
375  localized_strings->SetString("search",
376      l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_SEARCH));
377  localized_strings->SetString("bookmark_all_tabs",
378      l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_BOOKMARK_ALL_TABS));
379  localized_strings->SetString("save",
380      l10n_util::GetStringUTF16(IDS_SAVE));
381  localized_strings->SetString("cancel",
382      l10n_util::GetStringUTF16(IDS_CANCEL));
383
384  webui::SetFontAndTextDirection(localized_strings);
385
386  SetResult(localized_strings);
387
388  // This is needed because unlike the rest of these functions, this class
389  // inherits from AsyncFunction directly, rather than BookmarkFunction.
390  SendResponse(true);
391
392  return true;
393}
394
395bool BookmarkManagerPrivateStartDragFunction::RunImpl() {
396  if (!EditBookmarksEnabled())
397    return false;
398  BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
399  std::vector<const BookmarkNode*> nodes;
400  EXTENSION_FUNCTION_VALIDATE(
401      GetNodesFromArguments(model, args_.get(), 0, &nodes));
402
403  WebContents* web_contents =
404      WebContents::FromRenderViewHost(render_view_host_);
405  if (chrome::GetViewType(web_contents) == chrome::VIEW_TYPE_TAB_CONTENTS) {
406    WebContents* web_contents =
407        dispatcher()->delegate()->GetAssociatedWebContents();
408    CHECK(web_contents);
409    chrome::DragBookmarks(profile(), nodes,
410                          web_contents->GetView()->GetNativeView());
411
412    return true;
413  } else {
414    NOTREACHED();
415    return false;
416  }
417}
418
419bool BookmarkManagerPrivateDropFunction::RunImpl() {
420  if (!EditBookmarksEnabled())
421    return false;
422
423  BookmarkModel* model =BookmarkModelFactory::GetForProfile(profile());
424
425  int64 id;
426  std::string id_string;
427  EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &id_string));
428
429  if (!base::StringToInt64(id_string, &id)) {
430    error_ = bookmark_keys::kInvalidIdError;
431    return false;
432  }
433
434  const BookmarkNode* drop_parent = model->GetNodeByID(id);
435  if (!drop_parent) {
436    error_ = bookmark_keys::kNoParentError;
437    return false;
438  }
439
440  int drop_index;
441  if (HasOptionalArgument(1))
442    EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(1, &drop_index));
443  else
444    drop_index = drop_parent->child_count();
445
446  WebContents* web_contents =
447      WebContents::FromRenderViewHost(render_view_host_);
448  if (chrome::GetViewType(web_contents) == chrome::VIEW_TYPE_TAB_CONTENTS) {
449    WebContents* web_contents =
450        dispatcher()->delegate()->GetAssociatedWebContents();
451    CHECK(web_contents);
452    ExtensionWebUI* web_ui =
453        static_cast<ExtensionWebUI*>(web_contents->GetWebUI()->GetController());
454    CHECK(web_ui);
455    BookmarkManagerPrivateEventRouter* router =
456        web_ui->bookmark_manager_private_event_router();
457
458    DCHECK(router);
459    const BookmarkNodeData* drag_data = router->GetBookmarkNodeData();
460    if (drag_data == NULL) {
461      NOTREACHED() <<"Somehow we're dropping null bookmark data";
462      return false;
463    }
464    chrome::DropBookmarks(profile(), *drag_data, drop_parent, drop_index);
465
466    router->ClearBookmarkNodeData();
467    return true;
468  } else {
469    NOTREACHED();
470    return false;
471  }
472}
473
474bool BookmarkManagerPrivateGetSubtreeFunction::RunImpl() {
475  BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
476  const BookmarkNode* node;
477  int64 id;
478  std::string id_string;
479  EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &id_string));
480  bool folders_only;
481  EXTENSION_FUNCTION_VALIDATE(args_->GetBoolean(1, &folders_only));
482  if (id_string == "") {
483    node = model->root_node();
484  } else {
485     if (!base::StringToInt64(id_string, &id)) {
486      error_ = bookmark_keys::kInvalidIdError;
487      return false;
488    }
489    node = model->GetNodeByID(id);
490  }
491  if (!node) {
492    error_ = bookmark_keys::kNoNodeError;
493    return false;
494  }
495  scoped_ptr<ListValue> json(new ListValue());
496  if (folders_only)
497    bookmark_api_helpers::AddNodeFoldersOnly(node, json.get(), true);
498  else
499    bookmark_api_helpers::AddNode(node, json.get(), true);
500  SetResult(json.release());
501  return true;
502}
503
504bool BookmarkManagerPrivateCanEditFunction::RunImpl() {
505  PrefService* prefs = components::UserPrefs::Get(profile_);
506  SetResult(new base::FundamentalValue(
507      prefs->GetBoolean(prefs::kEditBookmarksEnabled)));
508  return true;
509}
510
511bool BookmarkManagerPrivateRecordLaunchFunction::RunImpl() {
512  bookmark_utils::RecordBookmarkLaunch(bookmark_utils::LAUNCH_MANAGER);
513  return true;
514}
515
516bool BookmarkManagerPrivateCanOpenNewWindowsFunction::RunImpl() {
517  bool can_open_new_windows = true;
518
519#if defined(OS_WIN)
520  if (win8::IsSingleWindowMetroMode())
521    can_open_new_windows = false;
522#endif  // OS_WIN
523
524  SetResult(new base::FundamentalValue(can_open_new_windows));
525  return true;
526}
527
528}  // namespace extensions
529