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