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"
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/json/json_writer.h"
12 #include "base/lazy_instance.h"
13 #include "base/memory/scoped_ptr.h"
14 #include "base/path_service.h"
15 #include "base/prefs/pref_service.h"
16 #include "base/sha1.h"
17 #include "base/stl_util.h"
18 #include "base/strings/string16.h"
19 #include "base/strings/string_number_conversions.h"
20 #include "base/strings/string_util.h"
21 #include "base/strings/utf_string_conversions.h"
22 #include "base/time/time.h"
23 #include "chrome/browser/bookmarks/bookmark_html_writer.h"
24 #include "chrome/browser/bookmarks/bookmark_model.h"
25 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
26 #include "chrome/browser/bookmarks/bookmark_utils.h"
27 #include "chrome/browser/chrome_notification_types.h"
28 #include "chrome/browser/extensions/api/bookmarks/bookmark_api_constants.h"
29 #include "chrome/browser/extensions/api/bookmarks/bookmark_api_helpers.h"
30 #include "chrome/browser/extensions/extension_function_dispatcher.h"
31 #include "chrome/browser/importer/external_process_importer_host.h"
32 #include "chrome/browser/importer/importer_uma.h"
33 #include "chrome/browser/platform_util.h"
34 #include "chrome/browser/profiles/profile.h"
35 #include "chrome/browser/ui/chrome_select_file_policy.h"
36 #include "chrome/browser/ui/host_desktop.h"
37 #include "chrome/common/chrome_paths.h"
38 #include "chrome/common/extensions/api/bookmarks.h"
39 #include "chrome/common/importer/importer_data_types.h"
40 #include "chrome/common/pref_names.h"
41 #include "components/user_prefs/user_prefs.h"
42 #include "content/public/browser/notification_service.h"
43 #include "content/public/browser/web_contents.h"
44 #include "content/public/browser/web_contents_view.h"
45 #include "extensions/browser/event_router.h"
46 #include "extensions/browser/extension_system.h"
47 #include "extensions/browser/quota_service.h"
48 #include "grit/generated_resources.h"
49 #include "ui/base/l10n/l10n_util.h"
52 #include "ui/aura/remote_window_tree_host_win.h"
55 namespace extensions {
57 namespace keys = bookmark_api_constants;
58 namespace bookmarks = api::bookmarks;
60 using base::TimeDelta;
61 using bookmarks::BookmarkTreeNode;
62 using content::BrowserThread;
63 using content::WebContents;
65 typedef QuotaLimitHeuristic::Bucket Bucket;
66 typedef QuotaLimitHeuristic::Config Config;
67 typedef QuotaLimitHeuristic::BucketList BucketList;
68 typedef QuotaService::TimedLimit TimedLimit;
69 typedef QuotaService::SustainedLimit SustainedLimit;
70 typedef QuotaLimitHeuristic::BucketMapper BucketMapper;
74 // Generates a default path (including a default filename) that will be
75 // used for pre-populating the "Export Bookmarks" file chooser dialog box.
76 base::FilePath GetDefaultFilepathForBookmarkExport() {
77 base::Time time = base::Time::Now();
79 // Concatenate a date stamp to the filename.
81 base::FilePath::StringType filename =
82 l10n_util::GetStringFUTF8(IDS_EXPORT_BOOKMARKS_DEFAULT_FILENAME,
83 base::TimeFormatShortDateNumeric(time));
85 base::FilePath::StringType filename =
86 l10n_util::GetStringFUTF16(IDS_EXPORT_BOOKMARKS_DEFAULT_FILENAME,
87 base::TimeFormatShortDateNumeric(time));
90 file_util::ReplaceIllegalCharactersInPath(&filename, '_');
92 base::FilePath default_path;
93 PathService::Get(chrome::DIR_USER_DOCUMENTS, &default_path);
94 return default_path.Append(filename);
99 void BookmarksFunction::Run() {
100 BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile());
101 if (!model->loaded()) {
102 // Bookmarks are not ready yet. We'll wait.
103 model->AddObserver(this);
104 AddRef(); // Balanced in Loaded().
108 bool success = RunImpl();
110 content::NotificationService::current()->Notify(
111 chrome::NOTIFICATION_EXTENSION_BOOKMARKS_API_INVOKED,
112 content::Source<const Extension>(GetExtension()),
113 content::Details<const BookmarksFunction>(this));
115 SendResponse(success);
118 bool BookmarksFunction::GetBookmarkIdAsInt64(const std::string& id_string,
120 if (base::StringToInt64(id_string, id))
123 error_ = keys::kInvalidIdError;
127 const BookmarkNode* BookmarksFunction::GetBookmarkNodeFromId(
128 const std::string& id_string) {
130 if (!GetBookmarkIdAsInt64(id_string, &id))
133 BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile());
134 const BookmarkNode* node = model->GetNodeByID(id);
136 error_ = keys::kNoNodeError;
141 bool BookmarksFunction::EditBookmarksEnabled() {
142 PrefService* prefs = user_prefs::UserPrefs::Get(GetProfile());
143 if (prefs->GetBoolean(prefs::kEditBookmarksEnabled))
145 error_ = keys::kEditBookmarksDisabled;
149 void BookmarksFunction::BookmarkModelChanged() {
152 void BookmarksFunction::BookmarkModelLoaded(BookmarkModel* model,
153 bool ids_reassigned) {
154 model->RemoveObserver(this);
156 Release(); // Balanced in Run().
159 BookmarkEventRouter::BookmarkEventRouter(Profile* profile, BookmarkModel* model)
162 model_->AddObserver(this);
165 BookmarkEventRouter::~BookmarkEventRouter() {
167 model_->RemoveObserver(this);
171 void BookmarkEventRouter::DispatchEvent(
172 const std::string& event_name,
173 scoped_ptr<base::ListValue> event_args) {
174 if (extensions::ExtensionSystem::Get(profile_)->event_router()) {
175 extensions::ExtensionSystem::Get(profile_)->event_router()->BroadcastEvent(
176 make_scoped_ptr(new extensions::Event(event_name, event_args.Pass())));
180 void BookmarkEventRouter::BookmarkModelLoaded(BookmarkModel* model,
181 bool ids_reassigned) {
182 // TODO(erikkay): Perhaps we should send this event down to the extension
183 // so they know when it's safe to use the API?
186 void BookmarkEventRouter::BookmarkModelBeingDeleted(BookmarkModel* model) {
190 void BookmarkEventRouter::BookmarkNodeMoved(BookmarkModel* model,
191 const BookmarkNode* old_parent,
193 const BookmarkNode* new_parent,
195 scoped_ptr<base::ListValue> args(new base::ListValue());
196 const BookmarkNode* node = new_parent->GetChild(new_index);
197 args->Append(new base::StringValue(base::Int64ToString(node->id())));
198 base::DictionaryValue* object_args = new base::DictionaryValue();
199 object_args->SetString(keys::kParentIdKey,
200 base::Int64ToString(new_parent->id()));
201 object_args->SetInteger(keys::kIndexKey, new_index);
202 object_args->SetString(keys::kOldParentIdKey,
203 base::Int64ToString(old_parent->id()));
204 object_args->SetInteger(keys::kOldIndexKey, old_index);
205 args->Append(object_args);
207 DispatchEvent(bookmarks::OnMoved::kEventName, args.Pass());
210 void BookmarkEventRouter::BookmarkNodeAdded(BookmarkModel* model,
211 const BookmarkNode* parent,
213 scoped_ptr<base::ListValue> args(new base::ListValue());
214 const BookmarkNode* node = parent->GetChild(index);
215 args->Append(new base::StringValue(base::Int64ToString(node->id())));
216 scoped_ptr<BookmarkTreeNode> tree_node(
217 bookmark_api_helpers::GetBookmarkTreeNode(node, false, false));
218 args->Append(tree_node->ToValue().release());
220 DispatchEvent(bookmarks::OnCreated::kEventName, args.Pass());
223 void BookmarkEventRouter::BookmarkNodeRemoved(BookmarkModel* model,
224 const BookmarkNode* parent,
226 const BookmarkNode* node) {
227 scoped_ptr<base::ListValue> args(new base::ListValue());
228 args->Append(new base::StringValue(base::Int64ToString(node->id())));
229 base::DictionaryValue* object_args = new base::DictionaryValue();
230 object_args->SetString(keys::kParentIdKey,
231 base::Int64ToString(parent->id()));
232 object_args->SetInteger(keys::kIndexKey, index);
233 args->Append(object_args);
235 DispatchEvent(bookmarks::OnRemoved::kEventName, args.Pass());
238 void BookmarkEventRouter::BookmarkAllNodesRemoved(BookmarkModel* model) {
240 // TODO(shashishekhar) Currently this notification is only used on Android,
241 // which does not support extensions. If Desktop needs to support this, add
242 // a new event to the extensions api.
245 void BookmarkEventRouter::BookmarkNodeChanged(BookmarkModel* model,
246 const BookmarkNode* node) {
247 scoped_ptr<base::ListValue> args(new base::ListValue());
248 args->Append(new base::StringValue(base::Int64ToString(node->id())));
250 // TODO(erikkay) The only three things that BookmarkModel sends this
251 // notification for are title, url and favicon. Since we're currently
252 // ignoring favicon and since the notification doesn't say which one anyway,
253 // for now we only include title and url. The ideal thing would be to change
254 // BookmarkModel to indicate what changed.
255 base::DictionaryValue* object_args = new base::DictionaryValue();
256 object_args->SetString(keys::kTitleKey, node->GetTitle());
258 object_args->SetString(keys::kUrlKey, node->url().spec());
259 args->Append(object_args);
261 DispatchEvent(bookmarks::OnChanged::kEventName, args.Pass());
264 void BookmarkEventRouter::BookmarkNodeFaviconChanged(BookmarkModel* model,
265 const BookmarkNode* node) {
266 // TODO(erikkay) anything we should do here?
269 void BookmarkEventRouter::BookmarkNodeChildrenReordered(
270 BookmarkModel* model,
271 const BookmarkNode* node) {
272 scoped_ptr<base::ListValue> args(new base::ListValue());
273 args->Append(new base::StringValue(base::Int64ToString(node->id())));
274 int childCount = node->child_count();
275 base::ListValue* children = new base::ListValue();
276 for (int i = 0; i < childCount; ++i) {
277 const BookmarkNode* child = node->GetChild(i);
278 base::Value* child_id =
279 new base::StringValue(base::Int64ToString(child->id()));
280 children->Append(child_id);
282 base::DictionaryValue* reorder_info = new base::DictionaryValue();
283 reorder_info->Set(keys::kChildIdsKey, children);
284 args->Append(reorder_info);
286 DispatchEvent(bookmarks::OnChildrenReordered::kEventName, args.Pass());
289 void BookmarkEventRouter::ExtensiveBookmarkChangesBeginning(
290 BookmarkModel* model) {
291 scoped_ptr<base::ListValue> args(new base::ListValue());
292 DispatchEvent(bookmarks::OnImportBegan::kEventName, args.Pass());
295 void BookmarkEventRouter::ExtensiveBookmarkChangesEnded(BookmarkModel* model) {
296 scoped_ptr<base::ListValue> args(new base::ListValue());
297 DispatchEvent(bookmarks::OnImportEnded::kEventName, args.Pass());
300 BookmarksAPI::BookmarksAPI(Profile* profile) : profile_(profile) {
301 ExtensionSystem::Get(profile_)->event_router()->RegisterObserver(
302 this, bookmarks::OnCreated::kEventName);
303 ExtensionSystem::Get(profile_)->event_router()->RegisterObserver(
304 this, bookmarks::OnRemoved::kEventName);
305 ExtensionSystem::Get(profile_)->event_router()->RegisterObserver(
306 this, bookmarks::OnChanged::kEventName);
307 ExtensionSystem::Get(profile_)->event_router()->RegisterObserver(
308 this, bookmarks::OnMoved::kEventName);
309 ExtensionSystem::Get(profile_)->event_router()->RegisterObserver(
310 this, bookmarks::OnChildrenReordered::kEventName);
311 ExtensionSystem::Get(profile_)->event_router()->RegisterObserver(
312 this, bookmarks::OnImportBegan::kEventName);
313 ExtensionSystem::Get(profile_)->event_router()->RegisterObserver(
314 this, bookmarks::OnImportEnded::kEventName);
317 BookmarksAPI::~BookmarksAPI() {
320 void BookmarksAPI::Shutdown() {
321 ExtensionSystem::Get(profile_)->event_router()->UnregisterObserver(this);
324 static base::LazyInstance<ProfileKeyedAPIFactory<BookmarksAPI> >
325 g_factory = LAZY_INSTANCE_INITIALIZER;
328 ProfileKeyedAPIFactory<BookmarksAPI>* BookmarksAPI::GetFactoryInstance() {
329 return g_factory.Pointer();
332 void BookmarksAPI::OnListenerAdded(const EventListenerInfo& details) {
333 bookmark_event_router_.reset(new BookmarkEventRouter(profile_,
334 BookmarkModelFactory::GetForProfile(profile_)));
335 ExtensionSystem::Get(profile_)->event_router()->UnregisterObserver(this);
338 bool BookmarksGetFunction::RunImpl() {
339 scoped_ptr<bookmarks::Get::Params> params(
340 bookmarks::Get::Params::Create(*args_));
341 EXTENSION_FUNCTION_VALIDATE(params.get());
343 std::vector<linked_ptr<BookmarkTreeNode> > nodes;
344 if (params->id_or_id_list.as_strings) {
345 std::vector<std::string>& ids = *params->id_or_id_list.as_strings;
346 size_t count = ids.size();
347 EXTENSION_FUNCTION_VALIDATE(count > 0);
348 for (size_t i = 0; i < count; ++i) {
349 const BookmarkNode* node = GetBookmarkNodeFromId(ids[i]);
352 bookmark_api_helpers::AddNode(node, &nodes, false);
355 const BookmarkNode* node =
356 GetBookmarkNodeFromId(*params->id_or_id_list.as_string);
359 bookmark_api_helpers::AddNode(node, &nodes, false);
362 results_ = bookmarks::Get::Results::Create(nodes);
366 bool BookmarksGetChildrenFunction::RunImpl() {
367 scoped_ptr<bookmarks::GetChildren::Params> params(
368 bookmarks::GetChildren::Params::Create(*args_));
369 EXTENSION_FUNCTION_VALIDATE(params.get());
371 const BookmarkNode* node = GetBookmarkNodeFromId(params->id);
375 std::vector<linked_ptr<BookmarkTreeNode> > nodes;
376 int child_count = node->child_count();
377 for (int i = 0; i < child_count; ++i) {
378 const BookmarkNode* child = node->GetChild(i);
379 bookmark_api_helpers::AddNode(child, &nodes, false);
382 results_ = bookmarks::GetChildren::Results::Create(nodes);
386 bool BookmarksGetRecentFunction::RunImpl() {
387 scoped_ptr<bookmarks::GetRecent::Params> params(
388 bookmarks::GetRecent::Params::Create(*args_));
389 EXTENSION_FUNCTION_VALIDATE(params.get());
390 if (params->number_of_items < 1)
393 std::vector<const BookmarkNode*> nodes;
394 bookmark_utils::GetMostRecentlyAddedEntries(
395 BookmarkModelFactory::GetForProfile(GetProfile()),
396 params->number_of_items,
399 std::vector<linked_ptr<BookmarkTreeNode> > tree_nodes;
400 std::vector<const BookmarkNode*>::iterator i = nodes.begin();
401 for (; i != nodes.end(); ++i) {
402 const BookmarkNode* node = *i;
403 bookmark_api_helpers::AddNode(node, &tree_nodes, false);
406 results_ = bookmarks::GetRecent::Results::Create(tree_nodes);
410 bool BookmarksGetTreeFunction::RunImpl() {
411 std::vector<linked_ptr<BookmarkTreeNode> > nodes;
412 const BookmarkNode* node =
413 BookmarkModelFactory::GetForProfile(GetProfile())->root_node();
414 bookmark_api_helpers::AddNode(node, &nodes, true);
415 results_ = bookmarks::GetTree::Results::Create(nodes);
419 bool BookmarksGetSubTreeFunction::RunImpl() {
420 scoped_ptr<bookmarks::GetSubTree::Params> params(
421 bookmarks::GetSubTree::Params::Create(*args_));
422 EXTENSION_FUNCTION_VALIDATE(params.get());
424 const BookmarkNode* node = GetBookmarkNodeFromId(params->id);
428 std::vector<linked_ptr<BookmarkTreeNode> > nodes;
429 bookmark_api_helpers::AddNode(node, &nodes, true);
430 results_ = bookmarks::GetSubTree::Results::Create(nodes);
434 bool BookmarksSearchFunction::RunImpl() {
435 scoped_ptr<bookmarks::Search::Params> params(
436 bookmarks::Search::Params::Create(*args_));
437 EXTENSION_FUNCTION_VALIDATE(params.get());
439 PrefService* prefs = user_prefs::UserPrefs::Get(GetProfile());
440 std::string lang = prefs->GetString(prefs::kAcceptLanguages);
441 std::vector<const BookmarkNode*> nodes;
442 if (params->query.as_string) {
443 bookmark_utils::QueryFields query;
444 query.word_phrase_query.reset(
445 new base::string16(base::UTF8ToUTF16(*params->query.as_string)));
446 bookmark_utils::GetBookmarksMatchingProperties(
447 BookmarkModelFactory::GetForProfile(GetProfile()),
449 std::numeric_limits<int>::max(),
453 DCHECK(params->query.as_object);
454 const bookmarks::Search::Params::Query::Object& object =
455 *params->query.as_object;
456 bookmark_utils::QueryFields query;
458 query.word_phrase_query.reset(
459 new base::string16(base::UTF8ToUTF16(*object.query)));
462 query.url.reset(new base::string16(base::UTF8ToUTF16(*object.url)));
464 query.title.reset(new base::string16(base::UTF8ToUTF16(*object.title)));
465 bookmark_utils::GetBookmarksMatchingProperties(
466 BookmarkModelFactory::GetForProfile(GetProfile()),
468 std::numeric_limits<int>::max(),
473 std::vector<linked_ptr<BookmarkTreeNode> > tree_nodes;
474 for (std::vector<const BookmarkNode*>::iterator node_iter = nodes.begin();
475 node_iter != nodes.end(); ++node_iter) {
476 bookmark_api_helpers::AddNode(*node_iter, &tree_nodes, false);
479 results_ = bookmarks::Search::Results::Create(tree_nodes);
484 bool BookmarksRemoveFunction::ExtractIds(const base::ListValue* args,
485 std::list<int64>* ids,
487 std::string id_string;
488 if (!args->GetString(0, &id_string))
491 if (base::StringToInt64(id_string, &id))
498 bool BookmarksRemoveFunction::RunImpl() {
499 if (!EditBookmarksEnabled())
502 scoped_ptr<bookmarks::Remove::Params> params(
503 bookmarks::Remove::Params::Create(*args_));
504 EXTENSION_FUNCTION_VALIDATE(params.get());
507 if (!GetBookmarkIdAsInt64(params->id, &id))
510 bool recursive = false;
511 if (name() == BookmarksRemoveTreeFunction::function_name())
514 BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile());
515 if (!bookmark_api_helpers::RemoveNode(model, id, recursive, &error_))
521 bool BookmarksCreateFunction::RunImpl() {
522 if (!EditBookmarksEnabled())
525 scoped_ptr<bookmarks::Create::Params> params(
526 bookmarks::Create::Params::Create(*args_));
527 EXTENSION_FUNCTION_VALIDATE(params.get());
529 BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile());
532 if (!params->bookmark.parent_id.get()) {
533 // Optional, default to "other bookmarks".
534 parentId = model->other_node()->id();
536 if (!GetBookmarkIdAsInt64(*params->bookmark.parent_id, &parentId))
539 const BookmarkNode* parent = model->GetNodeByID(parentId);
541 error_ = keys::kNoParentError;
544 if (parent->is_root()) { // Can't create children of the root.
545 error_ = keys::kModifySpecialError;
550 if (!params->bookmark.index.get()) { // Optional (defaults to end).
551 index = parent->child_count();
553 index = *params->bookmark.index;
554 if (index > parent->child_count() || index < 0) {
555 error_ = keys::kInvalidIndexError;
560 base::string16 title; // Optional.
561 if (params->bookmark.title.get())
562 title = base::UTF8ToUTF16(*params->bookmark.title.get());
564 std::string url_string; // Optional.
565 if (params->bookmark.url.get())
566 url_string = *params->bookmark.url.get();
568 GURL url(url_string);
569 if (!url_string.empty() && !url.is_valid()) {
570 error_ = keys::kInvalidUrlError;
574 const BookmarkNode* node;
575 if (url_string.length())
576 node = model->AddURL(parent, index, title, url);
578 node = model->AddFolder(parent, index, title);
581 error_ = keys::kNoNodeError;
585 scoped_ptr<BookmarkTreeNode> ret(
586 bookmark_api_helpers::GetBookmarkTreeNode(node, false, false));
587 results_ = bookmarks::Create::Results::Create(*ret);
593 bool BookmarksMoveFunction::ExtractIds(const base::ListValue* args,
594 std::list<int64>* ids,
596 // For now, Move accepts ID parameters in the same way as an Update.
597 return BookmarksUpdateFunction::ExtractIds(args, ids, invalid_id);
600 bool BookmarksMoveFunction::RunImpl() {
601 if (!EditBookmarksEnabled())
604 scoped_ptr<bookmarks::Move::Params> params(
605 bookmarks::Move::Params::Create(*args_));
606 EXTENSION_FUNCTION_VALIDATE(params.get());
608 const BookmarkNode* node = GetBookmarkNodeFromId(params->id);
612 BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile());
613 if (model->is_permanent_node(node)) {
614 error_ = keys::kModifySpecialError;
618 const BookmarkNode* parent = NULL;
619 if (!params->destination.parent_id.get()) {
620 // Optional, defaults to current parent.
621 parent = node->parent();
624 if (!GetBookmarkIdAsInt64(*params->destination.parent_id, &parentId))
627 parent = model->GetNodeByID(parentId);
630 error_ = keys::kNoParentError;
631 // TODO(erikkay) return an error message.
634 if (parent == model->root_node()) {
635 error_ = keys::kModifySpecialError;
640 if (params->destination.index.get()) { // Optional (defaults to end).
641 index = *params->destination.index;
642 if (index > parent->child_count() || index < 0) {
643 error_ = keys::kInvalidIndexError;
647 index = parent->child_count();
650 model->Move(node, parent, index);
652 scoped_ptr<BookmarkTreeNode> tree_node(
653 bookmark_api_helpers::GetBookmarkTreeNode(node, false, false));
654 results_ = bookmarks::Move::Results::Create(*tree_node);
660 bool BookmarksUpdateFunction::ExtractIds(const base::ListValue* args,
661 std::list<int64>* ids,
663 // For now, Update accepts ID parameters in the same way as an Remove.
664 return BookmarksRemoveFunction::ExtractIds(args, ids, invalid_id);
667 bool BookmarksUpdateFunction::RunImpl() {
668 if (!EditBookmarksEnabled())
671 scoped_ptr<bookmarks::Update::Params> params(
672 bookmarks::Update::Params::Create(*args_));
673 EXTENSION_FUNCTION_VALIDATE(params.get());
675 // Optional but we need to distinguish non present from an empty title.
676 base::string16 title;
677 bool has_title = false;
678 if (params->changes.title.get()) {
679 title = base::UTF8ToUTF16(*params->changes.title);
684 std::string url_string;
685 if (params->changes.url.get())
686 url_string = *params->changes.url;
687 GURL url(url_string);
688 if (!url_string.empty() && !url.is_valid()) {
689 error_ = keys::kInvalidUrlError;
693 const BookmarkNode* node = GetBookmarkNodeFromId(params->id);
697 BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile());
698 if (model->is_permanent_node(node)) {
699 error_ = keys::kModifySpecialError;
703 model->SetTitle(node, title);
705 model->SetURL(node, url);
707 scoped_ptr<BookmarkTreeNode> tree_node(
708 bookmark_api_helpers::GetBookmarkTreeNode(node, false, false));
709 results_ = bookmarks::Update::Results::Create(*tree_node);
713 // Mapper superclass for BookmarkFunctions.
714 template <typename BucketIdType>
715 class BookmarkBucketMapper : public BucketMapper {
717 virtual ~BookmarkBucketMapper() { STLDeleteValues(&buckets_); }
719 Bucket* GetBucket(const BucketIdType& id) {
720 Bucket* b = buckets_[id];
728 std::map<BucketIdType, Bucket*> buckets_;
731 // Mapper for 'bookmarks.create'. Maps "same input to bookmarks.create" to a
733 class CreateBookmarkBucketMapper : public BookmarkBucketMapper<std::string> {
735 explicit CreateBookmarkBucketMapper(Profile* profile) : profile_(profile) {}
736 // TODO(tim): This should share code with BookmarksCreateFunction::RunImpl,
737 // but I can't figure out a good way to do that with all the macros.
738 virtual void GetBucketsForArgs(const base::ListValue* args,
739 BucketList* buckets) OVERRIDE {
740 const base::DictionaryValue* json;
741 if (!args->GetDictionary(0, &json))
744 std::string parent_id;
745 if (json->HasKey(keys::kParentIdKey)) {
746 if (!json->GetString(keys::kParentIdKey, &parent_id))
749 BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile_);
751 int64 parent_id_int64;
752 base::StringToInt64(parent_id, &parent_id_int64);
753 const BookmarkNode* parent = model->GetNodeByID(parent_id_int64);
757 std::string bucket_id = base::UTF16ToUTF8(parent->GetTitle());
759 json->GetString(keys::kTitleKey, &title);
760 std::string url_string;
761 json->GetString(keys::kUrlKey, &url_string);
764 bucket_id += url_string;
765 // 20 bytes (SHA1 hash length) is very likely less than most of the
766 // |bucket_id| strings we construct here, so we hash it to save space.
767 buckets->push_back(GetBucket(base::SHA1HashString(bucket_id)));
773 // Mapper for 'bookmarks.remove'.
774 class RemoveBookmarksBucketMapper : public BookmarkBucketMapper<std::string> {
776 explicit RemoveBookmarksBucketMapper(Profile* profile) : profile_(profile) {}
777 virtual void GetBucketsForArgs(const base::ListValue* args,
778 BucketList* buckets) OVERRIDE {
779 typedef std::list<int64> IdList;
781 bool invalid_id = false;
782 if (!BookmarksRemoveFunction::ExtractIds(args, &ids, &invalid_id) ||
787 for (IdList::iterator it = ids.begin(); it != ids.end(); ++it) {
788 BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile_);
789 const BookmarkNode* node = model->GetNodeByID(*it);
790 if (!node || node->is_root())
793 std::string bucket_id;
794 bucket_id += base::UTF16ToUTF8(node->parent()->GetTitle());
795 bucket_id += base::UTF16ToUTF8(node->GetTitle());
796 bucket_id += node->url().spec();
797 buckets->push_back(GetBucket(base::SHA1HashString(bucket_id)));
804 // Mapper for any bookmark function accepting bookmark IDs as parameters, where
805 // a distinct ID corresponds to a single item in terms of quota limiting. This
806 // is inappropriate for bookmarks.remove, for example, since repeated removals
807 // of the same item will actually have a different ID each time.
808 template <class FunctionType>
809 class BookmarkIdMapper : public BookmarkBucketMapper<int64> {
811 typedef std::list<int64> IdList;
812 virtual void GetBucketsForArgs(const base::ListValue* args,
813 BucketList* buckets) {
815 bool invalid_id = false;
816 if (!FunctionType::ExtractIds(args, &ids, &invalid_id) || invalid_id)
818 for (IdList::iterator it = ids.begin(); it != ids.end(); ++it)
819 buckets->push_back(GetBucket(*it));
823 // Builds heuristics for all BookmarkFunctions using specialized BucketMappers.
824 class BookmarksQuotaLimitFactory {
826 // For id-based bookmark functions.
827 template <class FunctionType>
828 static void Build(QuotaLimitHeuristics* heuristics) {
829 BuildWithMappers(heuristics, new BookmarkIdMapper<FunctionType>(),
830 new BookmarkIdMapper<FunctionType>());
833 // For bookmarks.create.
834 static void BuildForCreate(QuotaLimitHeuristics* heuristics,
836 BuildWithMappers(heuristics, new CreateBookmarkBucketMapper(profile),
837 new CreateBookmarkBucketMapper(profile));
840 // For bookmarks.remove.
841 static void BuildForRemove(QuotaLimitHeuristics* heuristics,
843 BuildWithMappers(heuristics, new RemoveBookmarksBucketMapper(profile),
844 new RemoveBookmarksBucketMapper(profile));
848 static void BuildWithMappers(QuotaLimitHeuristics* heuristics,
849 BucketMapper* short_mapper, BucketMapper* long_mapper) {
850 const Config kSustainedLimitConfig = {
851 // See bookmarks.json for current value.
852 bookmarks::MAX_SUSTAINED_WRITE_OPERATIONS_PER_MINUTE,
853 TimeDelta::FromMinutes(1)
855 heuristics->push_back(new SustainedLimit(
856 TimeDelta::FromMinutes(10),
857 kSustainedLimitConfig,
859 "MAX_SUSTAINED_WRITE_OPERATIONS_PER_MINUTE"));
861 const Config kTimedLimitConfig = {
862 // See bookmarks.json for current value.
863 bookmarks::MAX_WRITE_OPERATIONS_PER_HOUR,
864 TimeDelta::FromHours(1)
866 heuristics->push_back(new TimedLimit(
869 "MAX_WRITE_OPERATIONS_PER_HOUR"));
872 DISALLOW_IMPLICIT_CONSTRUCTORS(BookmarksQuotaLimitFactory);
875 // And finally, building the individual heuristics for each function.
876 void BookmarksRemoveFunction::GetQuotaLimitHeuristics(
877 QuotaLimitHeuristics* heuristics) const {
878 BookmarksQuotaLimitFactory::BuildForRemove(heuristics, GetProfile());
881 void BookmarksMoveFunction::GetQuotaLimitHeuristics(
882 QuotaLimitHeuristics* heuristics) const {
883 BookmarksQuotaLimitFactory::Build<BookmarksMoveFunction>(heuristics);
886 void BookmarksUpdateFunction::GetQuotaLimitHeuristics(
887 QuotaLimitHeuristics* heuristics) const {
888 BookmarksQuotaLimitFactory::Build<BookmarksUpdateFunction>(heuristics);
891 void BookmarksCreateFunction::GetQuotaLimitHeuristics(
892 QuotaLimitHeuristics* heuristics) const {
893 BookmarksQuotaLimitFactory::BuildForCreate(heuristics, GetProfile());
896 BookmarksIOFunction::BookmarksIOFunction() {}
898 BookmarksIOFunction::~BookmarksIOFunction() {
899 // There may be pending file dialogs, we need to tell them that we've gone
900 // away so they don't try and call back to us.
901 if (select_file_dialog_.get())
902 select_file_dialog_->ListenerDestroyed();
905 void BookmarksIOFunction::SelectFile(ui::SelectFileDialog::Type type) {
906 // GetDefaultFilepathForBookmarkExport() might have to touch the filesystem
907 // (stat or access, for example), so this requires a thread with IO allowed.
908 if (!BrowserThread::CurrentlyOn(BrowserThread::FILE)) {
909 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
910 base::Bind(&BookmarksIOFunction::SelectFile, this, type));
914 // Pre-populating the filename field in case this is a SELECT_SAVEAS_FILE
915 // dialog. If not, there is no filename field in the dialog box.
916 base::FilePath default_path;
917 if (type == ui::SelectFileDialog::SELECT_SAVEAS_FILE)
918 default_path = GetDefaultFilepathForBookmarkExport();
920 DCHECK(type == ui::SelectFileDialog::SELECT_OPEN_FILE);
922 // After getting the |default_path|, ask the UI to display the file dialog.
923 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
924 base::Bind(&BookmarksIOFunction::ShowSelectFileDialog, this,
925 type, default_path));
928 void BookmarksIOFunction::ShowSelectFileDialog(
929 ui::SelectFileDialog::Type type,
930 const base::FilePath& default_path) {
932 return; // Extension was unloaded.
934 // Balanced in one of the three callbacks of SelectFileDialog:
935 // either FileSelectionCanceled, MultiFilesSelected, or FileSelected
938 WebContents* web_contents = dispatcher()->delegate()->
939 GetAssociatedWebContents();
941 select_file_dialog_ = ui::SelectFileDialog::Create(
942 this, new ChromeSelectFilePolicy(web_contents));
943 ui::SelectFileDialog::FileTypeInfo file_type_info;
944 file_type_info.extensions.resize(1);
945 file_type_info.extensions[0].push_back(FILE_PATH_LITERAL("html"));
946 gfx::NativeWindow owning_window = web_contents ?
947 platform_util::GetTopLevel(web_contents->GetView()->GetNativeView())
950 if (!owning_window &&
951 chrome::GetActiveDesktop() == chrome::HOST_DESKTOP_TYPE_ASH)
952 owning_window = aura::RemoteWindowTreeHostWin::Instance()->GetAshWindow();
954 // |web_contents| can be NULL (for background pages), which is fine. In such
955 // a case if file-selection dialogs are forbidden by policy, we will not
956 // show an InfoBar, which is better than letting one appear out of the blue.
957 select_file_dialog_->SelectFile(type,
962 base::FilePath::StringType(),
967 void BookmarksIOFunction::FileSelectionCanceled(void* params) {
968 Release(); // Balanced in BookmarksIOFunction::SelectFile()
971 void BookmarksIOFunction::MultiFilesSelected(
972 const std::vector<base::FilePath>& files, void* params) {
973 Release(); // Balanced in BookmarsIOFunction::SelectFile()
974 NOTREACHED() << "Should not be able to select multiple files";
977 bool BookmarksImportFunction::RunImpl() {
978 if (!EditBookmarksEnabled())
980 SelectFile(ui::SelectFileDialog::SELECT_OPEN_FILE);
984 void BookmarksImportFunction::FileSelected(const base::FilePath& path,
987 #if !defined(OS_ANDROID)
988 // Android does not have support for the standard importers.
989 // TODO(jgreenwald): remove ifdef once extensions are no longer built on
992 ExternalProcessImporterHost* importer_host = new ExternalProcessImporterHost;
993 importer::SourceProfile source_profile;
994 source_profile.importer_type = importer::TYPE_BOOKMARKS_FILE;
995 source_profile.source_path = path;
996 importer_host->StartImportSettings(source_profile,
999 new ProfileWriter(GetProfile()));
1001 importer::LogImporterUseToMetrics("BookmarksAPI",
1002 importer::TYPE_BOOKMARKS_FILE);
1004 Release(); // Balanced in BookmarksIOFunction::SelectFile()
1007 bool BookmarksExportFunction::RunImpl() {
1008 SelectFile(ui::SelectFileDialog::SELECT_SAVEAS_FILE);
1012 void BookmarksExportFunction::FileSelected(const base::FilePath& path,
1015 #if !defined(OS_ANDROID)
1016 // Android does not have support for the standard exporter.
1017 // TODO(jgreenwald): remove ifdef once extensions are no longer built on
1019 bookmark_html_writer::WriteBookmarks(GetProfile(), path, NULL);
1021 Release(); // Balanced in BookmarksIOFunction::SelectFile()
1024 } // namespace extensions