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