Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / extensions / api / bookmarks / bookmarks_api.cc
blob3d64ad40cb66f4dceaad57f69505317743ed740a
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.
5 #include "chrome/browser/extensions/api/bookmarks/bookmarks_api.h"
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/managed_bookmark_service_factory.h"
25 #include "chrome/browser/extensions/api/bookmarks/bookmark_api_constants.h"
26 #include "chrome/browser/extensions/api/bookmarks/bookmark_api_helpers.h"
27 #include "chrome/browser/importer/external_process_importer_host.h"
28 #include "chrome/browser/importer/importer_uma.h"
29 #include "chrome/browser/platform_util.h"
30 #include "chrome/browser/profiles/profile.h"
31 #include "chrome/browser/ui/chrome_select_file_policy.h"
32 #include "chrome/browser/ui/host_desktop.h"
33 #include "chrome/common/chrome_paths.h"
34 #include "chrome/common/extensions/api/bookmarks.h"
35 #include "chrome/common/importer/importer_data_types.h"
36 #include "chrome/common/pref_names.h"
37 #include "chrome/grit/generated_resources.h"
38 #include "components/bookmarks/browser/bookmark_model.h"
39 #include "components/bookmarks/browser/bookmark_utils.h"
40 #include "components/bookmarks/managed/managed_bookmark_service.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"
50 #if defined(OS_WIN)
51 #include "ui/aura/remote_window_tree_host_win.h"
52 #endif
54 using bookmarks::BookmarkModel;
55 using bookmarks::BookmarkNode;
56 using bookmarks::ManagedBookmarkService;
58 namespace extensions {
60 namespace keys = bookmark_api_constants;
61 namespace bookmarks = api::bookmarks;
63 using bookmarks::BookmarkTreeNode;
64 using bookmarks::CreateDetails;
65 using content::BrowserContext;
66 using content::BrowserThread;
67 using content::WebContents;
69 namespace {
71 // Generates a default path (including a default filename) that will be
72 // used for pre-populating the "Export Bookmarks" file chooser dialog box.
73 base::FilePath GetDefaultFilepathForBookmarkExport() {
74 base::Time time = base::Time::Now();
76 // Concatenate a date stamp to the filename.
77 #if defined(OS_POSIX)
78 base::FilePath::StringType filename =
79 l10n_util::GetStringFUTF8(IDS_EXPORT_BOOKMARKS_DEFAULT_FILENAME,
80 base::TimeFormatShortDateNumeric(time));
81 #elif defined(OS_WIN)
82 base::FilePath::StringType filename =
83 l10n_util::GetStringFUTF16(IDS_EXPORT_BOOKMARKS_DEFAULT_FILENAME,
84 base::TimeFormatShortDateNumeric(time));
85 #endif
87 base::i18n::ReplaceIllegalCharactersInPath(&filename, '_');
89 base::FilePath default_path;
90 PathService::Get(chrome::DIR_USER_DOCUMENTS, &default_path);
91 return default_path.Append(filename);
94 } // namespace
96 bool BookmarksFunction::RunAsync() {
97 BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile());
98 if (!model->loaded()) {
99 // Bookmarks are not ready yet. We'll wait.
100 model->AddObserver(this);
101 AddRef(); // Balanced in Loaded().
102 return true;
105 RunAndSendResponse();
106 return true;
109 BookmarkModel* BookmarksFunction::GetBookmarkModel() {
110 return BookmarkModelFactory::GetForProfile(GetProfile());
113 ManagedBookmarkService* BookmarksFunction::GetManagedBookmarkService() {
114 return ManagedBookmarkServiceFactory::GetForProfile(GetProfile());
117 bool BookmarksFunction::GetBookmarkIdAsInt64(const std::string& id_string,
118 int64* id) {
119 if (base::StringToInt64(id_string, id))
120 return true;
122 error_ = keys::kInvalidIdError;
123 return false;
126 const BookmarkNode* BookmarksFunction::GetBookmarkNodeFromId(
127 const std::string& id_string) {
128 int64 id;
129 if (!GetBookmarkIdAsInt64(id_string, &id))
130 return NULL;
132 const BookmarkNode* node = ::bookmarks::GetBookmarkNodeByID(
133 BookmarkModelFactory::GetForProfile(GetProfile()), id);
134 if (!node)
135 error_ = keys::kNoNodeError;
137 return node;
140 const BookmarkNode* BookmarksFunction::CreateBookmarkNode(
141 BookmarkModel* model,
142 const CreateDetails& details,
143 const BookmarkNode::MetaInfoMap* meta_info) {
144 int64 parentId;
146 if (!details.parent_id.get()) {
147 // Optional, default to "other bookmarks".
148 parentId = model->other_node()->id();
149 } else {
150 if (!GetBookmarkIdAsInt64(*details.parent_id, &parentId))
151 return NULL;
153 const BookmarkNode* parent =
154 ::bookmarks::GetBookmarkNodeByID(model, parentId);
155 if (!CanBeModified(parent))
156 return NULL;
158 int index;
159 if (!details.index.get()) { // Optional (defaults to end).
160 index = parent->child_count();
161 } else {
162 index = *details.index;
163 if (index > parent->child_count() || index < 0) {
164 error_ = keys::kInvalidIndexError;
165 return NULL;
169 base::string16 title; // Optional.
170 if (details.title.get())
171 title = base::UTF8ToUTF16(*details.title.get());
173 std::string url_string; // Optional.
174 if (details.url.get())
175 url_string = *details.url.get();
177 GURL url(url_string);
178 if (!url_string.empty() && !url.is_valid()) {
179 error_ = keys::kInvalidUrlError;
180 return NULL;
183 const BookmarkNode* node;
184 if (url_string.length()) {
185 node = model->AddURLWithCreationTimeAndMetaInfo(
186 parent, index, title, url, base::Time::Now(), meta_info);
187 } else {
188 node = model->AddFolderWithMetaInfo(parent, index, title, meta_info);
189 model->SetDateFolderModified(parent, base::Time::Now());
192 DCHECK(node);
194 return node;
197 bool BookmarksFunction::EditBookmarksEnabled() {
198 PrefService* prefs = user_prefs::UserPrefs::Get(GetProfile());
199 if (prefs->GetBoolean(::bookmarks::prefs::kEditBookmarksEnabled))
200 return true;
201 error_ = keys::kEditBookmarksDisabled;
202 return false;
205 bool BookmarksFunction::CanBeModified(const BookmarkNode* node) {
206 if (!node) {
207 error_ = keys::kNoParentError;
208 return false;
210 if (node->is_root()) {
211 error_ = keys::kModifySpecialError;
212 return false;
214 ManagedBookmarkService* managed = GetManagedBookmarkService();
215 if (::bookmarks::IsDescendantOf(node, managed->managed_node()) ||
216 ::bookmarks::IsDescendantOf(node, managed->supervised_node())) {
217 error_ = keys::kModifyManagedError;
218 return false;
220 return true;
223 void BookmarksFunction::BookmarkModelChanged() {
226 void BookmarksFunction::BookmarkModelLoaded(BookmarkModel* model,
227 bool ids_reassigned) {
228 model->RemoveObserver(this);
229 RunAndSendResponse();
230 Release(); // Balanced in RunOnReady().
233 void BookmarksFunction::RunAndSendResponse() {
234 bool success = RunOnReady();
235 if (success) {
236 content::NotificationService::current()->Notify(
237 extensions::NOTIFICATION_EXTENSION_BOOKMARKS_API_INVOKED,
238 content::Source<const Extension>(extension()),
239 content::Details<const BookmarksFunction>(this));
241 SendResponse(success);
244 BookmarkEventRouter::BookmarkEventRouter(Profile* profile)
245 : browser_context_(profile),
246 model_(BookmarkModelFactory::GetForProfile(profile)),
247 managed_(ManagedBookmarkServiceFactory::GetForProfile(profile)) {
248 model_->AddObserver(this);
251 BookmarkEventRouter::~BookmarkEventRouter() {
252 if (model_) {
253 model_->RemoveObserver(this);
257 void BookmarkEventRouter::DispatchEvent(
258 events::HistogramValue histogram_value,
259 const std::string& event_name,
260 scoped_ptr<base::ListValue> event_args) {
261 EventRouter* event_router = EventRouter::Get(browser_context_);
262 if (event_router) {
263 event_router->BroadcastEvent(make_scoped_ptr(
264 new extensions::Event(histogram_value, event_name, event_args.Pass())));
268 void BookmarkEventRouter::BookmarkModelLoaded(BookmarkModel* model,
269 bool ids_reassigned) {
270 // TODO(erikkay): Perhaps we should send this event down to the extension
271 // so they know when it's safe to use the API?
274 void BookmarkEventRouter::BookmarkModelBeingDeleted(BookmarkModel* model) {
275 model_ = NULL;
278 void BookmarkEventRouter::BookmarkNodeMoved(BookmarkModel* model,
279 const BookmarkNode* old_parent,
280 int old_index,
281 const BookmarkNode* new_parent,
282 int new_index) {
283 const BookmarkNode* node = new_parent->GetChild(new_index);
284 bookmarks::OnMoved::MoveInfo move_info;
285 move_info.parent_id = base::Int64ToString(new_parent->id());
286 move_info.index = new_index;
287 move_info.old_parent_id = base::Int64ToString(old_parent->id());
288 move_info.old_index = old_index;
290 DispatchEvent(
291 events::BOOKMARKS_ON_MOVED, bookmarks::OnMoved::kEventName,
292 bookmarks::OnMoved::Create(base::Int64ToString(node->id()), move_info));
295 void BookmarkEventRouter::BookmarkNodeAdded(BookmarkModel* model,
296 const BookmarkNode* parent,
297 int index) {
298 const BookmarkNode* node = parent->GetChild(index);
299 scoped_ptr<BookmarkTreeNode> tree_node(
300 bookmark_api_helpers::GetBookmarkTreeNode(managed_, node, false, false));
301 DispatchEvent(events::BOOKMARKS_ON_CREATED, bookmarks::OnCreated::kEventName,
302 bookmarks::OnCreated::Create(base::Int64ToString(node->id()),
303 *tree_node));
306 void BookmarkEventRouter::BookmarkNodeRemoved(
307 BookmarkModel* model,
308 const BookmarkNode* parent,
309 int index,
310 const BookmarkNode* node,
311 const std::set<GURL>& removed_urls) {
312 bookmarks::OnRemoved::RemoveInfo remove_info;
313 remove_info.parent_id = base::Int64ToString(parent->id());
314 remove_info.index = index;
316 DispatchEvent(events::BOOKMARKS_ON_REMOVED, bookmarks::OnRemoved::kEventName,
317 bookmarks::OnRemoved::Create(base::Int64ToString(node->id()),
318 remove_info));
321 void BookmarkEventRouter::BookmarkAllUserNodesRemoved(
322 BookmarkModel* model,
323 const std::set<GURL>& removed_urls) {
324 NOTREACHED();
325 // TODO(shashishekhar) Currently this notification is only used on Android,
326 // which does not support extensions. If Desktop needs to support this, add
327 // a new event to the extensions api.
330 void BookmarkEventRouter::BookmarkNodeChanged(BookmarkModel* model,
331 const BookmarkNode* node) {
332 // TODO(erikkay) The only three things that BookmarkModel sends this
333 // notification for are title, url and favicon. Since we're currently
334 // ignoring favicon and since the notification doesn't say which one anyway,
335 // for now we only include title and url. The ideal thing would be to change
336 // BookmarkModel to indicate what changed.
337 bookmarks::OnChanged::ChangeInfo change_info;
338 change_info.title = base::UTF16ToUTF8(node->GetTitle());
339 if (node->is_url())
340 change_info.url.reset(new std::string(node->url().spec()));
342 DispatchEvent(events::BOOKMARKS_ON_CHANGED, bookmarks::OnChanged::kEventName,
343 bookmarks::OnChanged::Create(base::Int64ToString(node->id()),
344 change_info));
347 void BookmarkEventRouter::BookmarkNodeFaviconChanged(BookmarkModel* model,
348 const BookmarkNode* node) {
349 // TODO(erikkay) anything we should do here?
352 void BookmarkEventRouter::BookmarkNodeChildrenReordered(
353 BookmarkModel* model,
354 const BookmarkNode* node) {
355 bookmarks::OnChildrenReordered::ReorderInfo reorder_info;
356 int childCount = node->child_count();
357 for (int i = 0; i < childCount; ++i) {
358 const BookmarkNode* child = node->GetChild(i);
359 reorder_info.child_ids.push_back(base::Int64ToString(child->id()));
362 DispatchEvent(events::BOOKMARKS_ON_CHILDREN_REORDERED,
363 bookmarks::OnChildrenReordered::kEventName,
364 bookmarks::OnChildrenReordered::Create(
365 base::Int64ToString(node->id()), reorder_info));
368 void BookmarkEventRouter::ExtensiveBookmarkChangesBeginning(
369 BookmarkModel* model) {
370 DispatchEvent(events::BOOKMARKS_ON_IMPORT_BEGAN,
371 bookmarks::OnImportBegan::kEventName,
372 bookmarks::OnImportBegan::Create());
375 void BookmarkEventRouter::ExtensiveBookmarkChangesEnded(BookmarkModel* model) {
376 DispatchEvent(events::BOOKMARKS_ON_IMPORT_ENDED,
377 bookmarks::OnImportEnded::kEventName,
378 bookmarks::OnImportEnded::Create());
381 BookmarksAPI::BookmarksAPI(BrowserContext* context)
382 : browser_context_(context) {
383 EventRouter* event_router = EventRouter::Get(browser_context_);
384 event_router->RegisterObserver(this, bookmarks::OnCreated::kEventName);
385 event_router->RegisterObserver(this, bookmarks::OnRemoved::kEventName);
386 event_router->RegisterObserver(this, bookmarks::OnChanged::kEventName);
387 event_router->RegisterObserver(this, bookmarks::OnMoved::kEventName);
388 event_router->RegisterObserver(this,
389 bookmarks::OnChildrenReordered::kEventName);
390 event_router->RegisterObserver(this, bookmarks::OnImportBegan::kEventName);
391 event_router->RegisterObserver(this, bookmarks::OnImportEnded::kEventName);
394 BookmarksAPI::~BookmarksAPI() {
397 void BookmarksAPI::Shutdown() {
398 EventRouter::Get(browser_context_)->UnregisterObserver(this);
401 static base::LazyInstance<BrowserContextKeyedAPIFactory<BookmarksAPI> >
402 g_factory = LAZY_INSTANCE_INITIALIZER;
404 // static
405 BrowserContextKeyedAPIFactory<BookmarksAPI>*
406 BookmarksAPI::GetFactoryInstance() {
407 return g_factory.Pointer();
410 void BookmarksAPI::OnListenerAdded(const EventListenerInfo& details) {
411 bookmark_event_router_.reset(
412 new BookmarkEventRouter(Profile::FromBrowserContext(browser_context_)));
413 EventRouter::Get(browser_context_)->UnregisterObserver(this);
416 bool BookmarksGetFunction::RunOnReady() {
417 scoped_ptr<bookmarks::Get::Params> params(
418 bookmarks::Get::Params::Create(*args_));
419 EXTENSION_FUNCTION_VALIDATE(params.get());
421 std::vector<linked_ptr<BookmarkTreeNode> > nodes;
422 ManagedBookmarkService* managed = GetManagedBookmarkService();
423 if (params->id_or_id_list.as_strings) {
424 std::vector<std::string>& ids = *params->id_or_id_list.as_strings;
425 size_t count = ids.size();
426 EXTENSION_FUNCTION_VALIDATE(count > 0);
427 for (size_t i = 0; i < count; ++i) {
428 const BookmarkNode* node = GetBookmarkNodeFromId(ids[i]);
429 if (!node)
430 return false;
431 bookmark_api_helpers::AddNode(managed, node, &nodes, false);
433 } else {
434 const BookmarkNode* node =
435 GetBookmarkNodeFromId(*params->id_or_id_list.as_string);
436 if (!node)
437 return false;
438 bookmark_api_helpers::AddNode(managed, node, &nodes, false);
441 results_ = bookmarks::Get::Results::Create(nodes);
442 return true;
445 bool BookmarksGetChildrenFunction::RunOnReady() {
446 scoped_ptr<bookmarks::GetChildren::Params> params(
447 bookmarks::GetChildren::Params::Create(*args_));
448 EXTENSION_FUNCTION_VALIDATE(params.get());
450 const BookmarkNode* node = GetBookmarkNodeFromId(params->id);
451 if (!node)
452 return false;
454 std::vector<linked_ptr<BookmarkTreeNode> > nodes;
455 int child_count = node->child_count();
456 for (int i = 0; i < child_count; ++i) {
457 const BookmarkNode* child = node->GetChild(i);
458 bookmark_api_helpers::AddNode(GetManagedBookmarkService(), child, &nodes,
459 false);
462 results_ = bookmarks::GetChildren::Results::Create(nodes);
463 return true;
466 bool BookmarksGetRecentFunction::RunOnReady() {
467 scoped_ptr<bookmarks::GetRecent::Params> params(
468 bookmarks::GetRecent::Params::Create(*args_));
469 EXTENSION_FUNCTION_VALIDATE(params.get());
470 if (params->number_of_items < 1)
471 return false;
473 std::vector<const BookmarkNode*> nodes;
474 ::bookmarks::GetMostRecentlyAddedEntries(
475 BookmarkModelFactory::GetForProfile(GetProfile()),
476 params->number_of_items,
477 &nodes);
479 std::vector<linked_ptr<BookmarkTreeNode> > tree_nodes;
480 std::vector<const BookmarkNode*>::iterator i = nodes.begin();
481 for (; i != nodes.end(); ++i) {
482 const BookmarkNode* node = *i;
483 bookmark_api_helpers::AddNode(GetManagedBookmarkService(), node,
484 &tree_nodes, false);
487 results_ = bookmarks::GetRecent::Results::Create(tree_nodes);
488 return true;
491 bool BookmarksGetTreeFunction::RunOnReady() {
492 std::vector<linked_ptr<BookmarkTreeNode> > nodes;
493 const BookmarkNode* node =
494 BookmarkModelFactory::GetForProfile(GetProfile())->root_node();
495 bookmark_api_helpers::AddNode(GetManagedBookmarkService(), node, &nodes,
496 true);
497 results_ = bookmarks::GetTree::Results::Create(nodes);
498 return true;
501 bool BookmarksGetSubTreeFunction::RunOnReady() {
502 scoped_ptr<bookmarks::GetSubTree::Params> params(
503 bookmarks::GetSubTree::Params::Create(*args_));
504 EXTENSION_FUNCTION_VALIDATE(params.get());
506 const BookmarkNode* node = GetBookmarkNodeFromId(params->id);
507 if (!node)
508 return false;
510 std::vector<linked_ptr<BookmarkTreeNode> > nodes;
511 bookmark_api_helpers::AddNode(GetManagedBookmarkService(), node, &nodes,
512 true);
513 results_ = bookmarks::GetSubTree::Results::Create(nodes);
514 return true;
517 bool BookmarksSearchFunction::RunOnReady() {
518 scoped_ptr<bookmarks::Search::Params> params(
519 bookmarks::Search::Params::Create(*args_));
520 EXTENSION_FUNCTION_VALIDATE(params.get());
522 PrefService* prefs = user_prefs::UserPrefs::Get(GetProfile());
523 std::string lang = prefs->GetString(prefs::kAcceptLanguages);
524 std::vector<const BookmarkNode*> nodes;
525 if (params->query.as_string) {
526 ::bookmarks::QueryFields query;
527 query.word_phrase_query.reset(
528 new base::string16(base::UTF8ToUTF16(*params->query.as_string)));
529 ::bookmarks::GetBookmarksMatchingProperties(
530 BookmarkModelFactory::GetForProfile(GetProfile()),
531 query,
532 std::numeric_limits<int>::max(),
533 lang,
534 &nodes);
535 } else {
536 DCHECK(params->query.as_object);
537 const bookmarks::Search::Params::Query::Object& object =
538 *params->query.as_object;
539 ::bookmarks::QueryFields query;
540 if (object.query) {
541 query.word_phrase_query.reset(
542 new base::string16(base::UTF8ToUTF16(*object.query)));
544 if (object.url)
545 query.url.reset(new base::string16(base::UTF8ToUTF16(*object.url)));
546 if (object.title)
547 query.title.reset(new base::string16(base::UTF8ToUTF16(*object.title)));
548 ::bookmarks::GetBookmarksMatchingProperties(
549 BookmarkModelFactory::GetForProfile(GetProfile()),
550 query,
551 std::numeric_limits<int>::max(),
552 lang,
553 &nodes);
556 std::vector<linked_ptr<BookmarkTreeNode> > tree_nodes;
557 ManagedBookmarkService* managed = GetManagedBookmarkService();
558 for (std::vector<const BookmarkNode*>::iterator node_iter = nodes.begin();
559 node_iter != nodes.end(); ++node_iter) {
560 bookmark_api_helpers::AddNode(managed, *node_iter, &tree_nodes, false);
563 results_ = bookmarks::Search::Results::Create(tree_nodes);
564 return true;
567 // static
568 bool BookmarksRemoveFunction::ExtractIds(const base::ListValue* args,
569 std::list<int64>* ids,
570 bool* invalid_id) {
571 std::string id_string;
572 if (!args->GetString(0, &id_string))
573 return false;
574 int64 id;
575 if (base::StringToInt64(id_string, &id))
576 ids->push_back(id);
577 else
578 *invalid_id = true;
579 return true;
582 bool BookmarksRemoveFunction::RunOnReady() {
583 if (!EditBookmarksEnabled())
584 return false;
586 scoped_ptr<bookmarks::Remove::Params> params(
587 bookmarks::Remove::Params::Create(*args_));
588 EXTENSION_FUNCTION_VALIDATE(params.get());
590 int64 id;
591 if (!GetBookmarkIdAsInt64(params->id, &id))
592 return false;
594 bool recursive = false;
595 if (name() == BookmarksRemoveTreeFunction::function_name())
596 recursive = true;
598 BookmarkModel* model = GetBookmarkModel();
599 ManagedBookmarkService* managed = GetManagedBookmarkService();
600 if (!bookmark_api_helpers::RemoveNode(model, managed, id, recursive, &error_))
601 return false;
603 return true;
606 bool BookmarksCreateFunction::RunOnReady() {
607 if (!EditBookmarksEnabled())
608 return false;
610 scoped_ptr<bookmarks::Create::Params> params(
611 bookmarks::Create::Params::Create(*args_));
612 EXTENSION_FUNCTION_VALIDATE(params.get());
614 BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile());
615 const BookmarkNode* node = CreateBookmarkNode(model, params->bookmark, NULL);
616 if (!node)
617 return false;
619 scoped_ptr<BookmarkTreeNode> ret(bookmark_api_helpers::GetBookmarkTreeNode(
620 GetManagedBookmarkService(), node, false, false));
621 results_ = bookmarks::Create::Results::Create(*ret);
623 return true;
626 // static
627 bool BookmarksMoveFunction::ExtractIds(const base::ListValue* args,
628 std::list<int64>* ids,
629 bool* invalid_id) {
630 // For now, Move accepts ID parameters in the same way as an Update.
631 return BookmarksUpdateFunction::ExtractIds(args, ids, invalid_id);
634 bool BookmarksMoveFunction::RunOnReady() {
635 if (!EditBookmarksEnabled())
636 return false;
638 scoped_ptr<bookmarks::Move::Params> params(
639 bookmarks::Move::Params::Create(*args_));
640 EXTENSION_FUNCTION_VALIDATE(params.get());
642 const BookmarkNode* node = GetBookmarkNodeFromId(params->id);
643 if (!node)
644 return false;
646 BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile());
647 if (model->is_permanent_node(node)) {
648 error_ = keys::kModifySpecialError;
649 return false;
652 const BookmarkNode* parent = NULL;
653 if (!params->destination.parent_id.get()) {
654 // Optional, defaults to current parent.
655 parent = node->parent();
656 } else {
657 int64 parentId;
658 if (!GetBookmarkIdAsInt64(*params->destination.parent_id, &parentId))
659 return false;
661 parent = ::bookmarks::GetBookmarkNodeByID(model, parentId);
663 if (!CanBeModified(parent) || !CanBeModified(node))
664 return false;
666 int index;
667 if (params->destination.index.get()) { // Optional (defaults to end).
668 index = *params->destination.index;
669 if (index > parent->child_count() || index < 0) {
670 error_ = keys::kInvalidIndexError;
671 return false;
673 } else {
674 index = parent->child_count();
677 model->Move(node, parent, index);
679 scoped_ptr<BookmarkTreeNode> tree_node(
680 bookmark_api_helpers::GetBookmarkTreeNode(GetManagedBookmarkService(),
681 node, false, false));
682 results_ = bookmarks::Move::Results::Create(*tree_node);
684 return true;
687 // static
688 bool BookmarksUpdateFunction::ExtractIds(const base::ListValue* args,
689 std::list<int64>* ids,
690 bool* invalid_id) {
691 // For now, Update accepts ID parameters in the same way as an Remove.
692 return BookmarksRemoveFunction::ExtractIds(args, ids, invalid_id);
695 bool BookmarksUpdateFunction::RunOnReady() {
696 if (!EditBookmarksEnabled())
697 return false;
699 scoped_ptr<bookmarks::Update::Params> params(
700 bookmarks::Update::Params::Create(*args_));
701 EXTENSION_FUNCTION_VALIDATE(params.get());
703 // Optional but we need to distinguish non present from an empty title.
704 base::string16 title;
705 bool has_title = false;
706 if (params->changes.title.get()) {
707 title = base::UTF8ToUTF16(*params->changes.title);
708 has_title = true;
711 // Optional.
712 std::string url_string;
713 if (params->changes.url.get())
714 url_string = *params->changes.url;
715 GURL url(url_string);
716 if (!url_string.empty() && !url.is_valid()) {
717 error_ = keys::kInvalidUrlError;
718 return false;
721 const BookmarkNode* node = GetBookmarkNodeFromId(params->id);
722 if (!CanBeModified(node))
723 return false;
725 BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile());
726 if (model->is_permanent_node(node)) {
727 error_ = keys::kModifySpecialError;
728 return false;
730 if (has_title)
731 model->SetTitle(node, title);
732 if (!url.is_empty())
733 model->SetURL(node, url);
735 scoped_ptr<BookmarkTreeNode> tree_node(
736 bookmark_api_helpers::GetBookmarkTreeNode(GetManagedBookmarkService(),
737 node, false, false));
738 results_ = bookmarks::Update::Results::Create(*tree_node);
739 return true;
742 BookmarksIOFunction::BookmarksIOFunction() {}
744 BookmarksIOFunction::~BookmarksIOFunction() {
745 // There may be pending file dialogs, we need to tell them that we've gone
746 // away so they don't try and call back to us.
747 if (select_file_dialog_.get())
748 select_file_dialog_->ListenerDestroyed();
751 void BookmarksIOFunction::SelectFile(ui::SelectFileDialog::Type type) {
752 // GetDefaultFilepathForBookmarkExport() might have to touch the filesystem
753 // (stat or access, for example), so this requires a thread with IO allowed.
754 if (!BrowserThread::CurrentlyOn(BrowserThread::FILE)) {
755 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
756 base::Bind(&BookmarksIOFunction::SelectFile, this, type));
757 return;
760 // Pre-populating the filename field in case this is a SELECT_SAVEAS_FILE
761 // dialog. If not, there is no filename field in the dialog box.
762 base::FilePath default_path;
763 if (type == ui::SelectFileDialog::SELECT_SAVEAS_FILE)
764 default_path = GetDefaultFilepathForBookmarkExport();
765 else
766 DCHECK(type == ui::SelectFileDialog::SELECT_OPEN_FILE);
768 // After getting the |default_path|, ask the UI to display the file dialog.
769 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
770 base::Bind(&BookmarksIOFunction::ShowSelectFileDialog, this,
771 type, default_path));
774 void BookmarksIOFunction::ShowSelectFileDialog(
775 ui::SelectFileDialog::Type type,
776 const base::FilePath& default_path) {
777 if (!dispatcher())
778 return; // Extension was unloaded.
780 // Balanced in one of the three callbacks of SelectFileDialog:
781 // either FileSelectionCanceled, MultiFilesSelected, or FileSelected
782 AddRef();
784 WebContents* web_contents = GetAssociatedWebContents();
786 select_file_dialog_ = ui::SelectFileDialog::Create(
787 this, new ChromeSelectFilePolicy(web_contents));
788 ui::SelectFileDialog::FileTypeInfo file_type_info;
789 file_type_info.extensions.resize(1);
790 file_type_info.extensions[0].push_back(FILE_PATH_LITERAL("html"));
791 gfx::NativeWindow owning_window = web_contents ?
792 platform_util::GetTopLevel(web_contents->GetNativeView())
793 : NULL;
794 #if defined(OS_WIN)
795 if (!owning_window &&
796 chrome::GetActiveDesktop() == chrome::HOST_DESKTOP_TYPE_ASH)
797 owning_window = aura::RemoteWindowTreeHostWin::Instance()->GetAshWindow();
798 #endif
799 // |web_contents| can be NULL (for background pages), which is fine. In such
800 // a case if file-selection dialogs are forbidden by policy, we will not
801 // show an InfoBar, which is better than letting one appear out of the blue.
802 select_file_dialog_->SelectFile(type,
803 base::string16(),
804 default_path,
805 &file_type_info,
807 base::FilePath::StringType(),
808 owning_window,
809 NULL);
812 void BookmarksIOFunction::FileSelectionCanceled(void* params) {
813 Release(); // Balanced in BookmarksIOFunction::SelectFile()
816 void BookmarksIOFunction::MultiFilesSelected(
817 const std::vector<base::FilePath>& files, void* params) {
818 Release(); // Balanced in BookmarsIOFunction::SelectFile()
819 NOTREACHED() << "Should not be able to select multiple files";
822 bool BookmarksImportFunction::RunOnReady() {
823 if (!EditBookmarksEnabled())
824 return false;
825 SelectFile(ui::SelectFileDialog::SELECT_OPEN_FILE);
826 return true;
829 void BookmarksImportFunction::FileSelected(const base::FilePath& path,
830 int index,
831 void* params) {
832 // Deletes itself.
833 ExternalProcessImporterHost* importer_host = new ExternalProcessImporterHost;
834 importer::SourceProfile source_profile;
835 source_profile.importer_type = importer::TYPE_BOOKMARKS_FILE;
836 source_profile.source_path = path;
837 importer_host->StartImportSettings(source_profile,
838 GetProfile(),
839 importer::FAVORITES,
840 new ProfileWriter(GetProfile()));
842 importer::LogImporterUseToMetrics("BookmarksAPI",
843 importer::TYPE_BOOKMARKS_FILE);
844 Release(); // Balanced in BookmarksIOFunction::SelectFile()
847 bool BookmarksExportFunction::RunOnReady() {
848 SelectFile(ui::SelectFileDialog::SELECT_SAVEAS_FILE);
849 return true;
852 void BookmarksExportFunction::FileSelected(const base::FilePath& path,
853 int index,
854 void* params) {
855 bookmark_html_writer::WriteBookmarks(GetProfile(), path, NULL);
856 Release(); // Balanced in BookmarksIOFunction::SelectFile()
859 } // namespace extensions