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/bookmarks/bookmarks_api.h"
6
7#include "base/bind.h"
8#include "base/files/file_path.h"
9#include "base/i18n/file_util_icu.h"
10#include "base/i18n/time_formatting.h"
11#include "base/lazy_instance.h"
12#include "base/memory/scoped_ptr.h"
13#include "base/path_service.h"
14#include "base/prefs/pref_service.h"
15#include "base/sha1.h"
16#include "base/stl_util.h"
17#include "base/strings/string16.h"
18#include "base/strings/string_number_conversions.h"
19#include "base/strings/string_util.h"
20#include "base/strings/utf_string_conversions.h"
21#include "base/time/time.h"
22#include "chrome/browser/bookmarks/bookmark_html_writer.h"
23#include "chrome/browser/bookmarks/bookmark_model_factory.h"
24#include "chrome/browser/bookmarks/chrome_bookmark_client.h"
25#include "chrome/browser/bookmarks/chrome_bookmark_client_factory.h"
26#include "chrome/browser/extensions/api/bookmarks/bookmark_api_constants.h"
27#include "chrome/browser/extensions/api/bookmarks/bookmark_api_helpers.h"
28#include "chrome/browser/importer/external_process_importer_host.h"
29#include "chrome/browser/importer/importer_uma.h"
30#include "chrome/browser/platform_util.h"
31#include "chrome/browser/profiles/profile.h"
32#include "chrome/browser/ui/chrome_select_file_policy.h"
33#include "chrome/browser/ui/host_desktop.h"
34#include "chrome/common/chrome_paths.h"
35#include "chrome/common/extensions/api/bookmarks.h"
36#include "chrome/common/importer/importer_data_types.h"
37#include "chrome/common/pref_names.h"
38#include "chrome/grit/generated_resources.h"
39#include "components/bookmarks/browser/bookmark_model.h"
40#include "components/bookmarks/browser/bookmark_utils.h"
41#include "components/user_prefs/user_prefs.h"
42#include "content/public/browser/browser_context.h"
43#include "content/public/browser/notification_service.h"
44#include "content/public/browser/web_contents.h"
45#include "extensions/browser/event_router.h"
46#include "extensions/browser/extension_function_dispatcher.h"
47#include "extensions/browser/notification_types.h"
48#include "ui/base/l10n/l10n_util.h"
49
50#if defined(OS_WIN)
51#include "ui/aura/remote_window_tree_host_win.h"
52#endif
53
54namespace extensions {
55
56namespace keys = bookmark_api_constants;
57namespace bookmarks = api::bookmarks;
58
59using bookmarks::BookmarkTreeNode;
60using bookmarks::CreateDetails;
61using content::BrowserContext;
62using content::BrowserThread;
63using content::WebContents;
64
65namespace {
66
67// Generates a default path (including a default filename) that will be
68// used for pre-populating the "Export Bookmarks" file chooser dialog box.
69base::FilePath GetDefaultFilepathForBookmarkExport() {
70  base::Time time = base::Time::Now();
71
72  // Concatenate a date stamp to the filename.
73#if defined(OS_POSIX)
74  base::FilePath::StringType filename =
75      l10n_util::GetStringFUTF8(IDS_EXPORT_BOOKMARKS_DEFAULT_FILENAME,
76                                base::TimeFormatShortDateNumeric(time));
77#elif defined(OS_WIN)
78  base::FilePath::StringType filename =
79      l10n_util::GetStringFUTF16(IDS_EXPORT_BOOKMARKS_DEFAULT_FILENAME,
80                                 base::TimeFormatShortDateNumeric(time));
81#endif
82
83  base::i18n::ReplaceIllegalCharactersInPath(&filename, '_');
84
85  base::FilePath default_path;
86  PathService::Get(chrome::DIR_USER_DOCUMENTS, &default_path);
87  return default_path.Append(filename);
88}
89
90}  // namespace
91
92bool BookmarksFunction::RunAsync() {
93  BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile());
94  if (!model->loaded()) {
95    // Bookmarks are not ready yet.  We'll wait.
96    model->AddObserver(this);
97    AddRef();  // Balanced in Loaded().
98    return true;
99  }
100
101  bool success = RunOnReady();
102  if (success) {
103    content::NotificationService::current()->Notify(
104        extensions::NOTIFICATION_EXTENSION_BOOKMARKS_API_INVOKED,
105        content::Source<const Extension>(extension()),
106        content::Details<const BookmarksFunction>(this));
107  }
108  SendResponse(success);
109  return true;
110}
111
112BookmarkModel* BookmarksFunction::GetBookmarkModel() {
113  return BookmarkModelFactory::GetForProfile(GetProfile());
114}
115
116ChromeBookmarkClient* BookmarksFunction::GetChromeBookmarkClient() {
117  return ChromeBookmarkClientFactory::GetForProfile(GetProfile());
118}
119
120bool BookmarksFunction::GetBookmarkIdAsInt64(const std::string& id_string,
121                                             int64* id) {
122  if (base::StringToInt64(id_string, id))
123    return true;
124
125  error_ = keys::kInvalidIdError;
126  return false;
127}
128
129const BookmarkNode* BookmarksFunction::GetBookmarkNodeFromId(
130    const std::string& id_string) {
131  int64 id;
132  if (!GetBookmarkIdAsInt64(id_string, &id))
133    return NULL;
134
135  const BookmarkNode* node = ::bookmarks::GetBookmarkNodeByID(
136      BookmarkModelFactory::GetForProfile(GetProfile()), id);
137  if (!node)
138    error_ = keys::kNoNodeError;
139
140  return node;
141}
142
143const BookmarkNode* BookmarksFunction::CreateBookmarkNode(
144    BookmarkModel* model,
145    const CreateDetails& details,
146    const BookmarkNode::MetaInfoMap* meta_info) {
147  int64 parentId;
148
149  if (!details.parent_id.get()) {
150    // Optional, default to "other bookmarks".
151    parentId = model->other_node()->id();
152  } else {
153    if (!GetBookmarkIdAsInt64(*details.parent_id, &parentId))
154      return NULL;
155  }
156  const BookmarkNode* parent =
157      ::bookmarks::GetBookmarkNodeByID(model, parentId);
158  if (!CanBeModified(parent))
159    return NULL;
160
161  int index;
162  if (!details.index.get()) {  // Optional (defaults to end).
163    index = parent->child_count();
164  } else {
165    index = *details.index;
166    if (index > parent->child_count() || index < 0) {
167      error_ = keys::kInvalidIndexError;
168      return NULL;
169    }
170  }
171
172  base::string16 title;  // Optional.
173  if (details.title.get())
174    title = base::UTF8ToUTF16(*details.title.get());
175
176  std::string url_string;  // Optional.
177  if (details.url.get())
178    url_string = *details.url.get();
179
180  GURL url(url_string);
181  if (!url_string.empty() && !url.is_valid()) {
182    error_ = keys::kInvalidUrlError;
183    return NULL;
184  }
185
186  const BookmarkNode* node;
187  if (url_string.length())
188    node = model->AddURLWithCreationTimeAndMetaInfo(
189        parent, index, title, url, base::Time::Now(), meta_info);
190  else
191    node = model->AddFolderWithMetaInfo(parent, index, title, meta_info);
192  DCHECK(node);
193  if (!node) {
194    error_ = keys::kNoNodeError;
195    return NULL;
196  }
197
198  return node;
199}
200
201bool BookmarksFunction::EditBookmarksEnabled() {
202  PrefService* prefs = user_prefs::UserPrefs::Get(GetProfile());
203  if (prefs->GetBoolean(::bookmarks::prefs::kEditBookmarksEnabled))
204    return true;
205  error_ = keys::kEditBookmarksDisabled;
206  return false;
207}
208
209bool BookmarksFunction::CanBeModified(const BookmarkNode* node) {
210  if (!node) {
211    error_ = keys::kNoParentError;
212    return false;
213  }
214  if (node->is_root()) {
215    error_ = keys::kModifySpecialError;
216    return false;
217  }
218  ChromeBookmarkClient* client = GetChromeBookmarkClient();
219  if (client->IsDescendantOfManagedNode(node)) {
220    error_ = keys::kModifyManagedError;
221    return false;
222  }
223  return true;
224}
225
226void BookmarksFunction::BookmarkModelChanged() {
227}
228
229void BookmarksFunction::BookmarkModelLoaded(BookmarkModel* model,
230                                            bool ids_reassigned) {
231  model->RemoveObserver(this);
232  RunOnReady();
233  Release();  // Balanced in RunOnReady().
234}
235
236BookmarkEventRouter::BookmarkEventRouter(Profile* profile)
237    : browser_context_(profile),
238      model_(BookmarkModelFactory::GetForProfile(profile)),
239      client_(ChromeBookmarkClientFactory::GetForProfile(profile)) {
240  model_->AddObserver(this);
241}
242
243BookmarkEventRouter::~BookmarkEventRouter() {
244  if (model_) {
245    model_->RemoveObserver(this);
246  }
247}
248
249void BookmarkEventRouter::DispatchEvent(
250    const std::string& event_name,
251    scoped_ptr<base::ListValue> event_args) {
252  EventRouter* event_router = EventRouter::Get(browser_context_);
253  if (event_router) {
254    event_router->BroadcastEvent(
255        make_scoped_ptr(new extensions::Event(event_name, event_args.Pass())));
256  }
257}
258
259void BookmarkEventRouter::BookmarkModelLoaded(BookmarkModel* model,
260                                              bool ids_reassigned) {
261  // TODO(erikkay): Perhaps we should send this event down to the extension
262  // so they know when it's safe to use the API?
263}
264
265void BookmarkEventRouter::BookmarkModelBeingDeleted(BookmarkModel* model) {
266  model_ = NULL;
267}
268
269void BookmarkEventRouter::BookmarkNodeMoved(BookmarkModel* model,
270                                            const BookmarkNode* old_parent,
271                                            int old_index,
272                                            const BookmarkNode* new_parent,
273                                            int new_index) {
274  const BookmarkNode* node = new_parent->GetChild(new_index);
275  bookmarks::OnMoved::MoveInfo move_info;
276  move_info.parent_id = base::Int64ToString(new_parent->id());
277  move_info.index = new_index;
278  move_info.old_parent_id = base::Int64ToString(old_parent->id());
279  move_info.old_index = old_index;
280
281  DispatchEvent(
282      bookmarks::OnMoved::kEventName,
283      bookmarks::OnMoved::Create(base::Int64ToString(node->id()), move_info));
284}
285
286void BookmarkEventRouter::BookmarkNodeAdded(BookmarkModel* model,
287                                            const BookmarkNode* parent,
288                                            int index) {
289  const BookmarkNode* node = parent->GetChild(index);
290  scoped_ptr<BookmarkTreeNode> tree_node(
291      bookmark_api_helpers::GetBookmarkTreeNode(client_, node, false, false));
292  DispatchEvent(bookmarks::OnCreated::kEventName,
293                bookmarks::OnCreated::Create(base::Int64ToString(node->id()),
294                                             *tree_node));
295}
296
297void BookmarkEventRouter::BookmarkNodeRemoved(
298    BookmarkModel* model,
299    const BookmarkNode* parent,
300    int index,
301    const BookmarkNode* node,
302    const std::set<GURL>& removed_urls) {
303  bookmarks::OnRemoved::RemoveInfo remove_info;
304  remove_info.parent_id = base::Int64ToString(parent->id());
305  remove_info.index = index;
306
307  DispatchEvent(bookmarks::OnRemoved::kEventName,
308                bookmarks::OnRemoved::Create(base::Int64ToString(node->id()),
309                                             remove_info));
310}
311
312void BookmarkEventRouter::BookmarkAllUserNodesRemoved(
313    BookmarkModel* model,
314    const std::set<GURL>& removed_urls) {
315  NOTREACHED();
316  // TODO(shashishekhar) Currently this notification is only used on Android,
317  // which does not support extensions. If Desktop needs to support this, add
318  // a new event to the extensions api.
319}
320
321void BookmarkEventRouter::BookmarkNodeChanged(BookmarkModel* model,
322                                              const BookmarkNode* node) {
323  // TODO(erikkay) The only three things that BookmarkModel sends this
324  // notification for are title, url and favicon.  Since we're currently
325  // ignoring favicon and since the notification doesn't say which one anyway,
326  // for now we only include title and url.  The ideal thing would be to change
327  // BookmarkModel to indicate what changed.
328  bookmarks::OnChanged::ChangeInfo change_info;
329  change_info.title = base::UTF16ToUTF8(node->GetTitle());
330  if (node->is_url())
331    change_info.url.reset(new std::string(node->url().spec()));
332
333  DispatchEvent(bookmarks::OnChanged::kEventName,
334                bookmarks::OnChanged::Create(base::Int64ToString(node->id()),
335                                             change_info));
336}
337
338void BookmarkEventRouter::BookmarkNodeFaviconChanged(BookmarkModel* model,
339                                                     const BookmarkNode* node) {
340  // TODO(erikkay) anything we should do here?
341}
342
343void BookmarkEventRouter::BookmarkNodeChildrenReordered(
344    BookmarkModel* model,
345    const BookmarkNode* node) {
346  bookmarks::OnChildrenReordered::ReorderInfo reorder_info;
347  int childCount = node->child_count();
348  for (int i = 0; i < childCount; ++i) {
349    const BookmarkNode* child = node->GetChild(i);
350    reorder_info.child_ids.push_back(base::Int64ToString(child->id()));
351  }
352
353  DispatchEvent(bookmarks::OnChildrenReordered::kEventName,
354                bookmarks::OnChildrenReordered::Create(
355                    base::Int64ToString(node->id()), reorder_info));
356}
357
358void BookmarkEventRouter::ExtensiveBookmarkChangesBeginning(
359    BookmarkModel* model) {
360  DispatchEvent(bookmarks::OnImportBegan::kEventName,
361                bookmarks::OnImportBegan::Create());
362}
363
364void BookmarkEventRouter::ExtensiveBookmarkChangesEnded(BookmarkModel* model) {
365  DispatchEvent(bookmarks::OnImportEnded::kEventName,
366                bookmarks::OnImportEnded::Create());
367}
368
369BookmarksAPI::BookmarksAPI(BrowserContext* context)
370    : browser_context_(context) {
371  EventRouter* event_router = EventRouter::Get(browser_context_);
372  event_router->RegisterObserver(this, bookmarks::OnCreated::kEventName);
373  event_router->RegisterObserver(this, bookmarks::OnRemoved::kEventName);
374  event_router->RegisterObserver(this, bookmarks::OnChanged::kEventName);
375  event_router->RegisterObserver(this, bookmarks::OnMoved::kEventName);
376  event_router->RegisterObserver(this,
377                                 bookmarks::OnChildrenReordered::kEventName);
378  event_router->RegisterObserver(this, bookmarks::OnImportBegan::kEventName);
379  event_router->RegisterObserver(this, bookmarks::OnImportEnded::kEventName);
380}
381
382BookmarksAPI::~BookmarksAPI() {
383}
384
385void BookmarksAPI::Shutdown() {
386  EventRouter::Get(browser_context_)->UnregisterObserver(this);
387}
388
389static base::LazyInstance<BrowserContextKeyedAPIFactory<BookmarksAPI> >
390    g_factory = LAZY_INSTANCE_INITIALIZER;
391
392// static
393BrowserContextKeyedAPIFactory<BookmarksAPI>*
394BookmarksAPI::GetFactoryInstance() {
395  return g_factory.Pointer();
396}
397
398void BookmarksAPI::OnListenerAdded(const EventListenerInfo& details) {
399  bookmark_event_router_.reset(
400      new BookmarkEventRouter(Profile::FromBrowserContext(browser_context_)));
401  EventRouter::Get(browser_context_)->UnregisterObserver(this);
402}
403
404bool BookmarksGetFunction::RunOnReady() {
405  scoped_ptr<bookmarks::Get::Params> params(
406      bookmarks::Get::Params::Create(*args_));
407  EXTENSION_FUNCTION_VALIDATE(params.get());
408
409  std::vector<linked_ptr<BookmarkTreeNode> > nodes;
410  ChromeBookmarkClient* client = GetChromeBookmarkClient();
411  if (params->id_or_id_list.as_strings) {
412    std::vector<std::string>& ids = *params->id_or_id_list.as_strings;
413    size_t count = ids.size();
414    EXTENSION_FUNCTION_VALIDATE(count > 0);
415    for (size_t i = 0; i < count; ++i) {
416      const BookmarkNode* node = GetBookmarkNodeFromId(ids[i]);
417      if (!node)
418        return false;
419      bookmark_api_helpers::AddNode(client, node, &nodes, false);
420    }
421  } else {
422    const BookmarkNode* node =
423        GetBookmarkNodeFromId(*params->id_or_id_list.as_string);
424    if (!node)
425      return false;
426    bookmark_api_helpers::AddNode(client, node, &nodes, false);
427  }
428
429  results_ = bookmarks::Get::Results::Create(nodes);
430  return true;
431}
432
433bool BookmarksGetChildrenFunction::RunOnReady() {
434  scoped_ptr<bookmarks::GetChildren::Params> params(
435      bookmarks::GetChildren::Params::Create(*args_));
436  EXTENSION_FUNCTION_VALIDATE(params.get());
437
438  const BookmarkNode* node = GetBookmarkNodeFromId(params->id);
439  if (!node)
440    return false;
441
442  std::vector<linked_ptr<BookmarkTreeNode> > nodes;
443  int child_count = node->child_count();
444  for (int i = 0; i < child_count; ++i) {
445    const BookmarkNode* child = node->GetChild(i);
446    bookmark_api_helpers::AddNode(
447        GetChromeBookmarkClient(), child, &nodes, false);
448  }
449
450  results_ = bookmarks::GetChildren::Results::Create(nodes);
451  return true;
452}
453
454bool BookmarksGetRecentFunction::RunOnReady() {
455  scoped_ptr<bookmarks::GetRecent::Params> params(
456      bookmarks::GetRecent::Params::Create(*args_));
457  EXTENSION_FUNCTION_VALIDATE(params.get());
458  if (params->number_of_items < 1)
459    return false;
460
461  std::vector<const BookmarkNode*> nodes;
462  ::bookmarks::GetMostRecentlyAddedEntries(
463      BookmarkModelFactory::GetForProfile(GetProfile()),
464      params->number_of_items,
465      &nodes);
466
467  std::vector<linked_ptr<BookmarkTreeNode> > tree_nodes;
468  std::vector<const BookmarkNode*>::iterator i = nodes.begin();
469  for (; i != nodes.end(); ++i) {
470    const BookmarkNode* node = *i;
471    bookmark_api_helpers::AddNode(
472        GetChromeBookmarkClient(), node, &tree_nodes, false);
473  }
474
475  results_ = bookmarks::GetRecent::Results::Create(tree_nodes);
476  return true;
477}
478
479bool BookmarksGetTreeFunction::RunOnReady() {
480  std::vector<linked_ptr<BookmarkTreeNode> > nodes;
481  const BookmarkNode* node =
482      BookmarkModelFactory::GetForProfile(GetProfile())->root_node();
483  bookmark_api_helpers::AddNode(GetChromeBookmarkClient(), node, &nodes, true);
484  results_ = bookmarks::GetTree::Results::Create(nodes);
485  return true;
486}
487
488bool BookmarksGetSubTreeFunction::RunOnReady() {
489  scoped_ptr<bookmarks::GetSubTree::Params> params(
490      bookmarks::GetSubTree::Params::Create(*args_));
491  EXTENSION_FUNCTION_VALIDATE(params.get());
492
493  const BookmarkNode* node = GetBookmarkNodeFromId(params->id);
494  if (!node)
495    return false;
496
497  std::vector<linked_ptr<BookmarkTreeNode> > nodes;
498  bookmark_api_helpers::AddNode(GetChromeBookmarkClient(), node, &nodes, true);
499  results_ = bookmarks::GetSubTree::Results::Create(nodes);
500  return true;
501}
502
503bool BookmarksSearchFunction::RunOnReady() {
504  scoped_ptr<bookmarks::Search::Params> params(
505      bookmarks::Search::Params::Create(*args_));
506  EXTENSION_FUNCTION_VALIDATE(params.get());
507
508  PrefService* prefs = user_prefs::UserPrefs::Get(GetProfile());
509  std::string lang = prefs->GetString(prefs::kAcceptLanguages);
510  std::vector<const BookmarkNode*> nodes;
511  if (params->query.as_string) {
512    ::bookmarks::QueryFields query;
513    query.word_phrase_query.reset(
514        new base::string16(base::UTF8ToUTF16(*params->query.as_string)));
515    ::bookmarks::GetBookmarksMatchingProperties(
516        BookmarkModelFactory::GetForProfile(GetProfile()),
517        query,
518        std::numeric_limits<int>::max(),
519        lang,
520        &nodes);
521  } else {
522    DCHECK(params->query.as_object);
523    const bookmarks::Search::Params::Query::Object& object =
524        *params->query.as_object;
525    ::bookmarks::QueryFields query;
526    if (object.query) {
527      query.word_phrase_query.reset(
528          new base::string16(base::UTF8ToUTF16(*object.query)));
529    }
530    if (object.url)
531      query.url.reset(new base::string16(base::UTF8ToUTF16(*object.url)));
532    if (object.title)
533      query.title.reset(new base::string16(base::UTF8ToUTF16(*object.title)));
534    ::bookmarks::GetBookmarksMatchingProperties(
535        BookmarkModelFactory::GetForProfile(GetProfile()),
536        query,
537        std::numeric_limits<int>::max(),
538        lang,
539        &nodes);
540  }
541
542  std::vector<linked_ptr<BookmarkTreeNode> > tree_nodes;
543  ChromeBookmarkClient* client = GetChromeBookmarkClient();
544  for (std::vector<const BookmarkNode*>::iterator node_iter = nodes.begin();
545       node_iter != nodes.end(); ++node_iter) {
546    bookmark_api_helpers::AddNode(client, *node_iter, &tree_nodes, false);
547  }
548
549  results_ = bookmarks::Search::Results::Create(tree_nodes);
550  return true;
551}
552
553// static
554bool BookmarksRemoveFunction::ExtractIds(const base::ListValue* args,
555                                         std::list<int64>* ids,
556                                         bool* invalid_id) {
557  std::string id_string;
558  if (!args->GetString(0, &id_string))
559    return false;
560  int64 id;
561  if (base::StringToInt64(id_string, &id))
562    ids->push_back(id);
563  else
564    *invalid_id = true;
565  return true;
566}
567
568bool BookmarksRemoveFunction::RunOnReady() {
569  if (!EditBookmarksEnabled())
570    return false;
571
572  scoped_ptr<bookmarks::Remove::Params> params(
573      bookmarks::Remove::Params::Create(*args_));
574  EXTENSION_FUNCTION_VALIDATE(params.get());
575
576  int64 id;
577  if (!GetBookmarkIdAsInt64(params->id, &id))
578    return false;
579
580  bool recursive = false;
581  if (name() == BookmarksRemoveTreeFunction::function_name())
582    recursive = true;
583
584  BookmarkModel* model = GetBookmarkModel();
585  ChromeBookmarkClient* client = GetChromeBookmarkClient();
586  if (!bookmark_api_helpers::RemoveNode(model, client, id, recursive, &error_))
587    return false;
588
589  return true;
590}
591
592bool BookmarksCreateFunction::RunOnReady() {
593  if (!EditBookmarksEnabled())
594    return false;
595
596  scoped_ptr<bookmarks::Create::Params> params(
597      bookmarks::Create::Params::Create(*args_));
598  EXTENSION_FUNCTION_VALIDATE(params.get());
599
600  BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile());
601  const BookmarkNode* node = CreateBookmarkNode(model, params->bookmark, NULL);
602  if (!node)
603    return false;
604
605  scoped_ptr<BookmarkTreeNode> ret(bookmark_api_helpers::GetBookmarkTreeNode(
606      GetChromeBookmarkClient(), node, false, false));
607  results_ = bookmarks::Create::Results::Create(*ret);
608
609  return true;
610}
611
612// static
613bool BookmarksMoveFunction::ExtractIds(const base::ListValue* args,
614                                       std::list<int64>* ids,
615                                       bool* invalid_id) {
616  // For now, Move accepts ID parameters in the same way as an Update.
617  return BookmarksUpdateFunction::ExtractIds(args, ids, invalid_id);
618}
619
620bool BookmarksMoveFunction::RunOnReady() {
621  if (!EditBookmarksEnabled())
622    return false;
623
624  scoped_ptr<bookmarks::Move::Params> params(
625      bookmarks::Move::Params::Create(*args_));
626  EXTENSION_FUNCTION_VALIDATE(params.get());
627
628  const BookmarkNode* node = GetBookmarkNodeFromId(params->id);
629  if (!node)
630    return false;
631
632  BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile());
633  if (model->is_permanent_node(node)) {
634    error_ = keys::kModifySpecialError;
635    return false;
636  }
637
638  const BookmarkNode* parent = NULL;
639  if (!params->destination.parent_id.get()) {
640    // Optional, defaults to current parent.
641    parent = node->parent();
642  } else {
643    int64 parentId;
644    if (!GetBookmarkIdAsInt64(*params->destination.parent_id, &parentId))
645      return false;
646
647    parent = ::bookmarks::GetBookmarkNodeByID(model, parentId);
648  }
649  if (!CanBeModified(parent) || !CanBeModified(node))
650    return false;
651
652  int index;
653  if (params->destination.index.get()) {  // Optional (defaults to end).
654    index = *params->destination.index;
655    if (index > parent->child_count() || index < 0) {
656      error_ = keys::kInvalidIndexError;
657      return false;
658    }
659  } else {
660    index = parent->child_count();
661  }
662
663  model->Move(node, parent, index);
664
665  scoped_ptr<BookmarkTreeNode> tree_node(
666      bookmark_api_helpers::GetBookmarkTreeNode(
667          GetChromeBookmarkClient(), node, false, false));
668  results_ = bookmarks::Move::Results::Create(*tree_node);
669
670  return true;
671}
672
673// static
674bool BookmarksUpdateFunction::ExtractIds(const base::ListValue* args,
675                                         std::list<int64>* ids,
676                                         bool* invalid_id) {
677  // For now, Update accepts ID parameters in the same way as an Remove.
678  return BookmarksRemoveFunction::ExtractIds(args, ids, invalid_id);
679}
680
681bool BookmarksUpdateFunction::RunOnReady() {
682  if (!EditBookmarksEnabled())
683    return false;
684
685  scoped_ptr<bookmarks::Update::Params> params(
686      bookmarks::Update::Params::Create(*args_));
687  EXTENSION_FUNCTION_VALIDATE(params.get());
688
689  // Optional but we need to distinguish non present from an empty title.
690  base::string16 title;
691  bool has_title = false;
692  if (params->changes.title.get()) {
693    title = base::UTF8ToUTF16(*params->changes.title);
694    has_title = true;
695  }
696
697  // Optional.
698  std::string url_string;
699  if (params->changes.url.get())
700    url_string = *params->changes.url;
701  GURL url(url_string);
702  if (!url_string.empty() && !url.is_valid()) {
703    error_ = keys::kInvalidUrlError;
704    return false;
705  }
706
707  const BookmarkNode* node = GetBookmarkNodeFromId(params->id);
708  if (!CanBeModified(node))
709    return false;
710
711  BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile());
712  if (model->is_permanent_node(node)) {
713    error_ = keys::kModifySpecialError;
714    return false;
715  }
716  if (has_title)
717    model->SetTitle(node, title);
718  if (!url.is_empty())
719    model->SetURL(node, url);
720
721  scoped_ptr<BookmarkTreeNode> tree_node(
722      bookmark_api_helpers::GetBookmarkTreeNode(
723          GetChromeBookmarkClient(), node, false, false));
724  results_ = bookmarks::Update::Results::Create(*tree_node);
725  return true;
726}
727
728BookmarksIOFunction::BookmarksIOFunction() {}
729
730BookmarksIOFunction::~BookmarksIOFunction() {
731  // There may be pending file dialogs, we need to tell them that we've gone
732  // away so they don't try and call back to us.
733  if (select_file_dialog_.get())
734    select_file_dialog_->ListenerDestroyed();
735}
736
737void BookmarksIOFunction::SelectFile(ui::SelectFileDialog::Type type) {
738  // GetDefaultFilepathForBookmarkExport() might have to touch the filesystem
739  // (stat or access, for example), so this requires a thread with IO allowed.
740  if (!BrowserThread::CurrentlyOn(BrowserThread::FILE)) {
741    BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
742        base::Bind(&BookmarksIOFunction::SelectFile, this, type));
743    return;
744  }
745
746  // Pre-populating the filename field in case this is a SELECT_SAVEAS_FILE
747  // dialog. If not, there is no filename field in the dialog box.
748  base::FilePath default_path;
749  if (type == ui::SelectFileDialog::SELECT_SAVEAS_FILE)
750    default_path = GetDefaultFilepathForBookmarkExport();
751  else
752    DCHECK(type == ui::SelectFileDialog::SELECT_OPEN_FILE);
753
754  // After getting the |default_path|, ask the UI to display the file dialog.
755  BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
756      base::Bind(&BookmarksIOFunction::ShowSelectFileDialog, this,
757                 type, default_path));
758}
759
760void BookmarksIOFunction::ShowSelectFileDialog(
761    ui::SelectFileDialog::Type type,
762    const base::FilePath& default_path) {
763  if (!dispatcher())
764    return;  // Extension was unloaded.
765
766  // Balanced in one of the three callbacks of SelectFileDialog:
767  // either FileSelectionCanceled, MultiFilesSelected, or FileSelected
768  AddRef();
769
770  WebContents* web_contents = dispatcher()->delegate()->
771      GetAssociatedWebContents();
772
773  select_file_dialog_ = ui::SelectFileDialog::Create(
774      this, new ChromeSelectFilePolicy(web_contents));
775  ui::SelectFileDialog::FileTypeInfo file_type_info;
776  file_type_info.extensions.resize(1);
777  file_type_info.extensions[0].push_back(FILE_PATH_LITERAL("html"));
778  gfx::NativeWindow owning_window = web_contents ?
779      platform_util::GetTopLevel(web_contents->GetNativeView())
780          : NULL;
781#if defined(OS_WIN)
782  if (!owning_window &&
783      chrome::GetActiveDesktop() == chrome::HOST_DESKTOP_TYPE_ASH)
784    owning_window = aura::RemoteWindowTreeHostWin::Instance()->GetAshWindow();
785#endif
786  // |web_contents| can be NULL (for background pages), which is fine. In such
787  // a case if file-selection dialogs are forbidden by policy, we will not
788  // show an InfoBar, which is better than letting one appear out of the blue.
789  select_file_dialog_->SelectFile(type,
790                                  base::string16(),
791                                  default_path,
792                                  &file_type_info,
793                                  0,
794                                  base::FilePath::StringType(),
795                                  owning_window,
796                                  NULL);
797}
798
799void BookmarksIOFunction::FileSelectionCanceled(void* params) {
800  Release();  // Balanced in BookmarksIOFunction::SelectFile()
801}
802
803void BookmarksIOFunction::MultiFilesSelected(
804    const std::vector<base::FilePath>& files, void* params) {
805  Release();  // Balanced in BookmarsIOFunction::SelectFile()
806  NOTREACHED() << "Should not be able to select multiple files";
807}
808
809bool BookmarksImportFunction::RunOnReady() {
810  if (!EditBookmarksEnabled())
811    return false;
812  SelectFile(ui::SelectFileDialog::SELECT_OPEN_FILE);
813  return true;
814}
815
816void BookmarksImportFunction::FileSelected(const base::FilePath& path,
817                                           int index,
818                                           void* params) {
819  // Deletes itself.
820  ExternalProcessImporterHost* importer_host = new ExternalProcessImporterHost;
821  importer::SourceProfile source_profile;
822  source_profile.importer_type = importer::TYPE_BOOKMARKS_FILE;
823  source_profile.source_path = path;
824  importer_host->StartImportSettings(source_profile,
825                                     GetProfile(),
826                                     importer::FAVORITES,
827                                     new ProfileWriter(GetProfile()));
828
829  importer::LogImporterUseToMetrics("BookmarksAPI",
830                                    importer::TYPE_BOOKMARKS_FILE);
831  Release();  // Balanced in BookmarksIOFunction::SelectFile()
832}
833
834bool BookmarksExportFunction::RunOnReady() {
835  SelectFile(ui::SelectFileDialog::SELECT_SAVEAS_FILE);
836  return true;
837}
838
839void BookmarksExportFunction::FileSelected(const base::FilePath& path,
840                                           int index,
841                                           void* params) {
842  bookmark_html_writer::WriteBookmarks(GetProfile(), path, NULL);
843  Release();  // Balanced in BookmarksIOFunction::SelectFile()
844}
845
846}  // namespace extensions
847