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/event_router.h"
31 #include "chrome/browser/extensions/extension_function_dispatcher.h"
32 #include "chrome/browser/extensions/extension_system.h"
33 #include "chrome/browser/extensions/extensions_quota_service.h"
34 #include "chrome/browser/importer/external_process_importer_host.h"
35 #include "chrome/browser/importer/importer_uma.h"
36 #include "chrome/browser/platform_util.h"
37 #include "chrome/browser/profiles/profile.h"
38 #include "chrome/browser/ui/chrome_select_file_policy.h"
39 #include "chrome/browser/ui/host_desktop.h"
40 #include "chrome/common/chrome_paths.h"
41 #include "chrome/common/extensions/api/bookmarks.h"
42 #include "chrome/common/importer/importer_data_types.h"
43 #include "chrome/common/pref_names.h"
44 #include "components/user_prefs/user_prefs.h"
45 #include "content/public/browser/notification_service.h"
46 #include "content/public/browser/web_contents.h"
47 #include "content/public/browser/web_contents_view.h"
48 #include "grit/generated_resources.h"
49 #include "ui/base/l10n/l10n_util.h"
51 #if defined(OS_WIN) && defined(USE_AURA)
52 #include "ui/aura/remote_root_window_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 ExtensionsQuotaService::TimedLimit TimedLimit;
69 typedef ExtensionsQuotaService::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 bool BookmarksFunction::EditBookmarksEnabled() {
128 PrefService* prefs = user_prefs::UserPrefs::Get(GetProfile());
129 if (prefs->GetBoolean(prefs::kEditBookmarksEnabled))
131 error_ = keys::kEditBookmarksDisabled;
135 void BookmarksFunction::BookmarkModelChanged() {
138 void BookmarksFunction::Loaded(BookmarkModel* model, bool ids_reassigned) {
139 model->RemoveObserver(this);
141 Release(); // Balanced in Run().
144 BookmarkEventRouter::BookmarkEventRouter(Profile* profile,
145 BookmarkModel* model)
148 model_->AddObserver(this);
151 BookmarkEventRouter::~BookmarkEventRouter() {
153 model_->RemoveObserver(this);
157 void BookmarkEventRouter::DispatchEvent(
158 const std::string& event_name,
159 scoped_ptr<base::ListValue> event_args) {
160 if (extensions::ExtensionSystem::Get(profile_)->event_router()) {
161 extensions::ExtensionSystem::Get(profile_)->event_router()->BroadcastEvent(
162 make_scoped_ptr(new extensions::Event(event_name, event_args.Pass())));
166 void BookmarkEventRouter::Loaded(BookmarkModel* model, bool ids_reassigned) {
167 // TODO(erikkay): Perhaps we should send this event down to the extension
168 // so they know when it's safe to use the API?
171 void BookmarkEventRouter::BookmarkModelBeingDeleted(BookmarkModel* model) {
175 void BookmarkEventRouter::BookmarkNodeMoved(BookmarkModel* model,
176 const BookmarkNode* old_parent,
178 const BookmarkNode* new_parent,
180 scoped_ptr<base::ListValue> args(new base::ListValue());
181 const BookmarkNode* node = new_parent->GetChild(new_index);
182 args->Append(new base::StringValue(base::Int64ToString(node->id())));
183 base::DictionaryValue* object_args = new base::DictionaryValue();
184 object_args->SetString(keys::kParentIdKey,
185 base::Int64ToString(new_parent->id()));
186 object_args->SetInteger(keys::kIndexKey, new_index);
187 object_args->SetString(keys::kOldParentIdKey,
188 base::Int64ToString(old_parent->id()));
189 object_args->SetInteger(keys::kOldIndexKey, old_index);
190 args->Append(object_args);
192 DispatchEvent(bookmarks::OnMoved::kEventName, args.Pass());
195 void BookmarkEventRouter::BookmarkNodeAdded(BookmarkModel* model,
196 const BookmarkNode* parent,
198 scoped_ptr<base::ListValue> args(new base::ListValue());
199 const BookmarkNode* node = parent->GetChild(index);
200 args->Append(new base::StringValue(base::Int64ToString(node->id())));
201 scoped_ptr<BookmarkTreeNode> tree_node(
202 bookmark_api_helpers::GetBookmarkTreeNode(node, false, false));
203 args->Append(tree_node->ToValue().release());
205 DispatchEvent(bookmarks::OnCreated::kEventName, args.Pass());
208 void BookmarkEventRouter::BookmarkNodeRemoved(BookmarkModel* model,
209 const BookmarkNode* parent,
211 const BookmarkNode* node) {
212 scoped_ptr<base::ListValue> args(new base::ListValue());
213 args->Append(new base::StringValue(base::Int64ToString(node->id())));
214 base::DictionaryValue* object_args = new base::DictionaryValue();
215 object_args->SetString(keys::kParentIdKey,
216 base::Int64ToString(parent->id()));
217 object_args->SetInteger(keys::kIndexKey, index);
218 args->Append(object_args);
220 DispatchEvent(bookmarks::OnRemoved::kEventName, args.Pass());
223 void BookmarkEventRouter::BookmarkAllNodesRemoved(BookmarkModel* model) {
225 // TODO(shashishekhar) Currently this notification is only used on Android,
226 // which does not support extensions. If Desktop needs to support this, add
227 // a new event to the extensions api.
230 void BookmarkEventRouter::BookmarkNodeChanged(BookmarkModel* model,
231 const BookmarkNode* node) {
232 scoped_ptr<base::ListValue> args(new base::ListValue());
233 args->Append(new base::StringValue(base::Int64ToString(node->id())));
235 // TODO(erikkay) The only three things that BookmarkModel sends this
236 // notification for are title, url and favicon. Since we're currently
237 // ignoring favicon and since the notification doesn't say which one anyway,
238 // for now we only include title and url. The ideal thing would be to change
239 // BookmarkModel to indicate what changed.
240 base::DictionaryValue* object_args = new base::DictionaryValue();
241 object_args->SetString(keys::kTitleKey, node->GetTitle());
243 object_args->SetString(keys::kUrlKey, node->url().spec());
244 args->Append(object_args);
246 DispatchEvent(bookmarks::OnChanged::kEventName, args.Pass());
249 void BookmarkEventRouter::BookmarkNodeFaviconChanged(BookmarkModel* model,
250 const BookmarkNode* node) {
251 // TODO(erikkay) anything we should do here?
254 void BookmarkEventRouter::BookmarkNodeChildrenReordered(
255 BookmarkModel* model,
256 const BookmarkNode* node) {
257 scoped_ptr<base::ListValue> args(new base::ListValue());
258 args->Append(new base::StringValue(base::Int64ToString(node->id())));
259 int childCount = node->child_count();
260 base::ListValue* children = new base::ListValue();
261 for (int i = 0; i < childCount; ++i) {
262 const BookmarkNode* child = node->GetChild(i);
263 base::Value* child_id =
264 new base::StringValue(base::Int64ToString(child->id()));
265 children->Append(child_id);
267 base::DictionaryValue* reorder_info = new base::DictionaryValue();
268 reorder_info->Set(keys::kChildIdsKey, children);
269 args->Append(reorder_info);
271 DispatchEvent(bookmarks::OnChildrenReordered::kEventName, args.Pass());
274 void BookmarkEventRouter::ExtensiveBookmarkChangesBeginning(
275 BookmarkModel* model) {
276 scoped_ptr<base::ListValue> args(new base::ListValue());
277 DispatchEvent(bookmarks::OnImportBegan::kEventName, args.Pass());
280 void BookmarkEventRouter::ExtensiveBookmarkChangesEnded(BookmarkModel* model) {
281 scoped_ptr<base::ListValue> args(new base::ListValue());
282 DispatchEvent(bookmarks::OnImportEnded::kEventName, args.Pass());
285 BookmarksAPI::BookmarksAPI(Profile* profile) : profile_(profile) {
286 ExtensionSystem::Get(profile_)->event_router()->RegisterObserver(
287 this, bookmarks::OnCreated::kEventName);
288 ExtensionSystem::Get(profile_)->event_router()->RegisterObserver(
289 this, bookmarks::OnRemoved::kEventName);
290 ExtensionSystem::Get(profile_)->event_router()->RegisterObserver(
291 this, bookmarks::OnChanged::kEventName);
292 ExtensionSystem::Get(profile_)->event_router()->RegisterObserver(
293 this, bookmarks::OnMoved::kEventName);
294 ExtensionSystem::Get(profile_)->event_router()->RegisterObserver(
295 this, bookmarks::OnChildrenReordered::kEventName);
296 ExtensionSystem::Get(profile_)->event_router()->RegisterObserver(
297 this, bookmarks::OnImportBegan::kEventName);
298 ExtensionSystem::Get(profile_)->event_router()->RegisterObserver(
299 this, bookmarks::OnImportEnded::kEventName);
302 BookmarksAPI::~BookmarksAPI() {
305 void BookmarksAPI::Shutdown() {
306 ExtensionSystem::Get(profile_)->event_router()->UnregisterObserver(this);
309 static base::LazyInstance<ProfileKeyedAPIFactory<BookmarksAPI> >
310 g_factory = LAZY_INSTANCE_INITIALIZER;
313 ProfileKeyedAPIFactory<BookmarksAPI>* BookmarksAPI::GetFactoryInstance() {
314 return &g_factory.Get();
317 void BookmarksAPI::OnListenerAdded(const EventListenerInfo& details) {
318 bookmark_event_router_.reset(new BookmarkEventRouter(profile_,
319 BookmarkModelFactory::GetForProfile(profile_)));
320 ExtensionSystem::Get(profile_)->event_router()->UnregisterObserver(this);
323 bool BookmarksGetFunction::RunImpl() {
324 scoped_ptr<bookmarks::Get::Params> params(
325 bookmarks::Get::Params::Create(*args_));
326 EXTENSION_FUNCTION_VALIDATE(params.get());
328 std::vector<linked_ptr<BookmarkTreeNode> > nodes;
329 BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile());
330 if (params->id_or_id_list.as_strings) {
331 std::vector<std::string>& ids = *params->id_or_id_list.as_strings;
332 size_t count = ids.size();
333 EXTENSION_FUNCTION_VALIDATE(count > 0);
334 for (size_t i = 0; i < count; ++i) {
336 if (!GetBookmarkIdAsInt64(ids[i], &id))
338 const BookmarkNode* node = model->GetNodeByID(id);
340 error_ = keys::kNoNodeError;
343 bookmark_api_helpers::AddNode(node, &nodes, false);
348 if (!GetBookmarkIdAsInt64(*params->id_or_id_list.as_string, &id))
350 const BookmarkNode* node = model->GetNodeByID(id);
352 error_ = keys::kNoNodeError;
355 bookmark_api_helpers::AddNode(node, &nodes, false);
358 results_ = bookmarks::Get::Results::Create(nodes);
362 bool BookmarksGetChildrenFunction::RunImpl() {
363 scoped_ptr<bookmarks::GetChildren::Params> params(
364 bookmarks::GetChildren::Params::Create(*args_));
365 EXTENSION_FUNCTION_VALIDATE(params.get());
368 if (!GetBookmarkIdAsInt64(params->id, &id))
371 std::vector<linked_ptr<BookmarkTreeNode> > nodes;
372 const BookmarkNode* node =
373 BookmarkModelFactory::GetForProfile(GetProfile())->GetNodeByID(id);
375 error_ = keys::kNoNodeError;
378 int child_count = node->child_count();
379 for (int i = 0; i < child_count; ++i) {
380 const BookmarkNode* child = node->GetChild(i);
381 bookmark_api_helpers::AddNode(child, &nodes, false);
384 results_ = bookmarks::GetChildren::Results::Create(nodes);
388 bool BookmarksGetRecentFunction::RunImpl() {
389 scoped_ptr<bookmarks::GetRecent::Params> params(
390 bookmarks::GetRecent::Params::Create(*args_));
391 EXTENSION_FUNCTION_VALIDATE(params.get());
392 if (params->number_of_items < 1)
395 std::vector<const BookmarkNode*> nodes;
396 bookmark_utils::GetMostRecentlyAddedEntries(
397 BookmarkModelFactory::GetForProfile(GetProfile()),
398 params->number_of_items,
401 std::vector<linked_ptr<BookmarkTreeNode> > tree_nodes;
402 std::vector<const BookmarkNode*>::iterator i = nodes.begin();
403 for (; i != nodes.end(); ++i) {
404 const BookmarkNode* node = *i;
405 bookmark_api_helpers::AddNode(node, &tree_nodes, false);
408 results_ = bookmarks::GetRecent::Results::Create(tree_nodes);
412 bool BookmarksGetTreeFunction::RunImpl() {
413 std::vector<linked_ptr<BookmarkTreeNode> > nodes;
414 const BookmarkNode* node =
415 BookmarkModelFactory::GetForProfile(GetProfile())->root_node();
416 bookmark_api_helpers::AddNode(node, &nodes, true);
417 results_ = bookmarks::GetTree::Results::Create(nodes);
421 bool BookmarksGetSubTreeFunction::RunImpl() {
422 scoped_ptr<bookmarks::GetSubTree::Params> params(
423 bookmarks::GetSubTree::Params::Create(*args_));
424 EXTENSION_FUNCTION_VALIDATE(params.get());
427 if (!GetBookmarkIdAsInt64(params->id, &id))
430 const BookmarkNode* node =
431 BookmarkModelFactory::GetForProfile(GetProfile())->GetNodeByID(id);
433 error_ = keys::kNoNodeError;
437 std::vector<linked_ptr<BookmarkTreeNode> > nodes;
438 bookmark_api_helpers::AddNode(node, &nodes, true);
439 results_ = bookmarks::GetSubTree::Results::Create(nodes);
443 bool BookmarksSearchFunction::RunImpl() {
444 scoped_ptr<bookmarks::Search::Params> params(
445 bookmarks::Search::Params::Create(*args_));
446 EXTENSION_FUNCTION_VALIDATE(params.get());
448 PrefService* prefs = user_prefs::UserPrefs::Get(GetProfile());
449 std::string lang = prefs->GetString(prefs::kAcceptLanguages);
450 std::vector<const BookmarkNode*> nodes;
451 bookmark_utils::GetBookmarksContainingText(
452 BookmarkModelFactory::GetForProfile(GetProfile()),
453 UTF8ToUTF16(params->query),
454 std::numeric_limits<int>::max(),
458 std::vector<linked_ptr<BookmarkTreeNode> > tree_nodes;
459 for (std::vector<const BookmarkNode*>::iterator node_iter = nodes.begin();
460 node_iter != nodes.end(); ++node_iter) {
461 bookmark_api_helpers::AddNode(*node_iter, &tree_nodes, false);
464 results_ = bookmarks::Search::Results::Create(tree_nodes);
469 bool BookmarksRemoveFunction::ExtractIds(const base::ListValue* args,
470 std::list<int64>* ids,
472 std::string id_string;
473 if (!args->GetString(0, &id_string))
476 if (base::StringToInt64(id_string, &id))
483 bool BookmarksRemoveFunction::RunImpl() {
484 if (!EditBookmarksEnabled())
487 scoped_ptr<bookmarks::Remove::Params> params(
488 bookmarks::Remove::Params::Create(*args_));
489 EXTENSION_FUNCTION_VALIDATE(params.get());
492 if (!base::StringToInt64(params->id, &id)) {
493 error_ = keys::kInvalidIdError;
497 bool recursive = false;
498 if (name() == BookmarksRemoveTreeFunction::function_name())
501 BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile());
502 if (!bookmark_api_helpers::RemoveNode(model, id, recursive, &error_))
508 bool BookmarksCreateFunction::RunImpl() {
509 if (!EditBookmarksEnabled())
512 scoped_ptr<bookmarks::Create::Params> params(
513 bookmarks::Create::Params::Create(*args_));
514 EXTENSION_FUNCTION_VALIDATE(params.get());
516 BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile());
519 if (!params->bookmark.parent_id.get()) {
520 // Optional, default to "other bookmarks".
521 parentId = model->other_node()->id();
523 if (!GetBookmarkIdAsInt64(*params->bookmark.parent_id, &parentId))
526 const BookmarkNode* parent = model->GetNodeByID(parentId);
528 error_ = keys::kNoParentError;
531 if (parent->is_root()) { // Can't create children of the root.
532 error_ = keys::kModifySpecialError;
537 if (!params->bookmark.index.get()) { // Optional (defaults to end).
538 index = parent->child_count();
540 index = *params->bookmark.index;
541 if (index > parent->child_count() || index < 0) {
542 error_ = keys::kInvalidIndexError;
547 string16 title; // Optional.
548 if (params->bookmark.title.get())
549 title = UTF8ToUTF16(*params->bookmark.title.get());
551 std::string url_string; // Optional.
552 if (params->bookmark.url.get())
553 url_string = *params->bookmark.url.get();
555 GURL url(url_string);
556 if (!url_string.empty() && !url.is_valid()) {
557 error_ = keys::kInvalidUrlError;
561 const BookmarkNode* node;
562 if (url_string.length())
563 node = model->AddURL(parent, index, title, url);
565 node = model->AddFolder(parent, index, title);
568 error_ = keys::kNoNodeError;
572 scoped_ptr<BookmarkTreeNode> ret(
573 bookmark_api_helpers::GetBookmarkTreeNode(node, false, false));
574 results_ = bookmarks::Create::Results::Create(*ret);
580 bool BookmarksMoveFunction::ExtractIds(const base::ListValue* args,
581 std::list<int64>* ids,
583 // For now, Move accepts ID parameters in the same way as an Update.
584 return BookmarksUpdateFunction::ExtractIds(args, ids, invalid_id);
587 bool BookmarksMoveFunction::RunImpl() {
588 if (!EditBookmarksEnabled())
591 scoped_ptr<bookmarks::Move::Params> params(
592 bookmarks::Move::Params::Create(*args_));
593 EXTENSION_FUNCTION_VALIDATE(params.get());
596 if (!base::StringToInt64(params->id, &id)) {
597 error_ = keys::kInvalidIdError;
601 BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile());
602 const BookmarkNode* node = model->GetNodeByID(id);
604 error_ = keys::kNoNodeError;
607 if (model->is_permanent_node(node)) {
608 error_ = keys::kModifySpecialError;
612 const BookmarkNode* parent = NULL;
613 if (!params->destination.parent_id.get()) {
614 // Optional, defaults to current parent.
615 parent = node->parent();
618 if (!GetBookmarkIdAsInt64(*params->destination.parent_id, &parentId))
621 parent = model->GetNodeByID(parentId);
624 error_ = keys::kNoParentError;
625 // TODO(erikkay) return an error message.
628 if (parent == model->root_node()) {
629 error_ = keys::kModifySpecialError;
634 if (params->destination.index.get()) { // Optional (defaults to end).
635 index = *params->destination.index;
636 if (index > parent->child_count() || index < 0) {
637 error_ = keys::kInvalidIndexError;
641 index = parent->child_count();
644 model->Move(node, parent, index);
646 scoped_ptr<BookmarkTreeNode> tree_node(
647 bookmark_api_helpers::GetBookmarkTreeNode(node, false, false));
648 results_ = bookmarks::Move::Results::Create(*tree_node);
654 bool BookmarksUpdateFunction::ExtractIds(const base::ListValue* args,
655 std::list<int64>* ids,
657 // For now, Update accepts ID parameters in the same way as an Remove.
658 return BookmarksRemoveFunction::ExtractIds(args, ids, invalid_id);
661 bool BookmarksUpdateFunction::RunImpl() {
662 if (!EditBookmarksEnabled())
665 scoped_ptr<bookmarks::Update::Params> params(
666 bookmarks::Update::Params::Create(*args_));
667 EXTENSION_FUNCTION_VALIDATE(params.get());
670 if (!base::StringToInt64(params->id, &id)) {
671 error_ = keys::kInvalidIdError;
675 BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile());
677 // Optional but we need to distinguish non present from an empty title.
679 bool has_title = false;
680 if (params->changes.title.get()) {
681 title = UTF8ToUTF16(*params->changes.title);
686 std::string url_string;
687 if (params->changes.url.get())
688 url_string = *params->changes.url;
689 GURL url(url_string);
690 if (!url_string.empty() && !url.is_valid()) {
691 error_ = keys::kInvalidUrlError;
695 const BookmarkNode* node = model->GetNodeByID(id);
697 error_ = keys::kNoNodeError;
700 if (model->is_permanent_node(node)) {
701 error_ = keys::kModifySpecialError;
705 model->SetTitle(node, title);
707 model->SetURL(node, url);
709 scoped_ptr<BookmarkTreeNode> tree_node(
710 bookmark_api_helpers::GetBookmarkTreeNode(node, false, false));
711 results_ = bookmarks::Update::Results::Create(*tree_node);
715 // Mapper superclass for BookmarkFunctions.
716 template <typename BucketIdType>
717 class BookmarkBucketMapper : public BucketMapper {
719 virtual ~BookmarkBucketMapper() { STLDeleteValues(&buckets_); }
721 Bucket* GetBucket(const BucketIdType& id) {
722 Bucket* b = buckets_[id];
730 std::map<BucketIdType, Bucket*> buckets_;
733 // Mapper for 'bookmarks.create'. Maps "same input to bookmarks.create" to a
735 class CreateBookmarkBucketMapper : public BookmarkBucketMapper<std::string> {
737 explicit CreateBookmarkBucketMapper(Profile* profile) : profile_(profile) {}
738 // TODO(tim): This should share code with BookmarksCreateFunction::RunImpl,
739 // but I can't figure out a good way to do that with all the macros.
740 virtual void GetBucketsForArgs(const base::ListValue* args,
741 BucketList* buckets) OVERRIDE {
742 const base::DictionaryValue* json;
743 if (!args->GetDictionary(0, &json))
746 std::string parent_id;
747 if (json->HasKey(keys::kParentIdKey)) {
748 if (!json->GetString(keys::kParentIdKey, &parent_id))
751 BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile_);
753 int64 parent_id_int64;
754 base::StringToInt64(parent_id, &parent_id_int64);
755 const BookmarkNode* parent = model->GetNodeByID(parent_id_int64);
759 std::string bucket_id = UTF16ToUTF8(parent->GetTitle());
761 json->GetString(keys::kTitleKey, &title);
762 std::string url_string;
763 json->GetString(keys::kUrlKey, &url_string);
766 bucket_id += url_string;
767 // 20 bytes (SHA1 hash length) is very likely less than most of the
768 // |bucket_id| strings we construct here, so we hash it to save space.
769 buckets->push_back(GetBucket(base::SHA1HashString(bucket_id)));
775 // Mapper for 'bookmarks.remove'.
776 class RemoveBookmarksBucketMapper : public BookmarkBucketMapper<std::string> {
778 explicit RemoveBookmarksBucketMapper(Profile* profile) : profile_(profile) {}
779 virtual void GetBucketsForArgs(const base::ListValue* args,
780 BucketList* buckets) OVERRIDE {
781 typedef std::list<int64> IdList;
783 bool invalid_id = false;
784 if (!BookmarksRemoveFunction::ExtractIds(args, &ids, &invalid_id) ||
789 for (IdList::iterator it = ids.begin(); it != ids.end(); ++it) {
790 BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile_);
791 const BookmarkNode* node = model->GetNodeByID(*it);
792 if (!node || node->is_root())
795 std::string bucket_id;
796 bucket_id += UTF16ToUTF8(node->parent()->GetTitle());
797 bucket_id += UTF16ToUTF8(node->GetTitle());
798 bucket_id += node->url().spec();
799 buckets->push_back(GetBucket(base::SHA1HashString(bucket_id)));
806 // Mapper for any bookmark function accepting bookmark IDs as parameters, where
807 // a distinct ID corresponds to a single item in terms of quota limiting. This
808 // is inappropriate for bookmarks.remove, for example, since repeated removals
809 // of the same item will actually have a different ID each time.
810 template <class FunctionType>
811 class BookmarkIdMapper : public BookmarkBucketMapper<int64> {
813 typedef std::list<int64> IdList;
814 virtual void GetBucketsForArgs(const base::ListValue* args,
815 BucketList* buckets) {
817 bool invalid_id = false;
818 if (!FunctionType::ExtractIds(args, &ids, &invalid_id) || invalid_id)
820 for (IdList::iterator it = ids.begin(); it != ids.end(); ++it)
821 buckets->push_back(GetBucket(*it));
825 // Builds heuristics for all BookmarkFunctions using specialized BucketMappers.
826 class BookmarksQuotaLimitFactory {
828 // For id-based bookmark functions.
829 template <class FunctionType>
830 static void Build(QuotaLimitHeuristics* heuristics) {
831 BuildWithMappers(heuristics, new BookmarkIdMapper<FunctionType>(),
832 new BookmarkIdMapper<FunctionType>());
835 // For bookmarks.create.
836 static void BuildForCreate(QuotaLimitHeuristics* heuristics,
838 BuildWithMappers(heuristics, new CreateBookmarkBucketMapper(profile),
839 new CreateBookmarkBucketMapper(profile));
842 // For bookmarks.remove.
843 static void BuildForRemove(QuotaLimitHeuristics* heuristics,
845 BuildWithMappers(heuristics, new RemoveBookmarksBucketMapper(profile),
846 new RemoveBookmarksBucketMapper(profile));
850 static void BuildWithMappers(QuotaLimitHeuristics* heuristics,
851 BucketMapper* short_mapper, BucketMapper* long_mapper) {
852 const Config kSustainedLimitConfig = {
853 // See bookmarks.json for current value.
854 bookmarks::MAX_SUSTAINED_WRITE_OPERATIONS_PER_MINUTE,
855 TimeDelta::FromMinutes(1)
857 heuristics->push_back(new SustainedLimit(
858 TimeDelta::FromMinutes(10),
859 kSustainedLimitConfig,
861 "MAX_SUSTAINED_WRITE_OPERATIONS_PER_MINUTE"));
863 const Config kTimedLimitConfig = {
864 // See bookmarks.json for current value.
865 bookmarks::MAX_WRITE_OPERATIONS_PER_HOUR,
866 TimeDelta::FromHours(1)
868 heuristics->push_back(new TimedLimit(
871 "MAX_WRITE_OPERATIONS_PER_HOUR"));
874 DISALLOW_IMPLICIT_CONSTRUCTORS(BookmarksQuotaLimitFactory);
877 // And finally, building the individual heuristics for each function.
878 void BookmarksRemoveFunction::GetQuotaLimitHeuristics(
879 QuotaLimitHeuristics* heuristics) const {
880 BookmarksQuotaLimitFactory::BuildForRemove(heuristics, GetProfile());
883 void BookmarksMoveFunction::GetQuotaLimitHeuristics(
884 QuotaLimitHeuristics* heuristics) const {
885 BookmarksQuotaLimitFactory::Build<BookmarksMoveFunction>(heuristics);
888 void BookmarksUpdateFunction::GetQuotaLimitHeuristics(
889 QuotaLimitHeuristics* heuristics) const {
890 BookmarksQuotaLimitFactory::Build<BookmarksUpdateFunction>(heuristics);
893 void BookmarksCreateFunction::GetQuotaLimitHeuristics(
894 QuotaLimitHeuristics* heuristics) const {
895 BookmarksQuotaLimitFactory::BuildForCreate(heuristics, GetProfile());
898 BookmarksIOFunction::BookmarksIOFunction() {}
900 BookmarksIOFunction::~BookmarksIOFunction() {
901 // There may be pending file dialogs, we need to tell them that we've gone
902 // away so they don't try and call back to us.
903 if (select_file_dialog_.get())
904 select_file_dialog_->ListenerDestroyed();
907 void BookmarksIOFunction::SelectFile(ui::SelectFileDialog::Type type) {
908 // GetDefaultFilepathForBookmarkExport() might have to touch the filesystem
909 // (stat or access, for example), so this requires a thread with IO allowed.
910 if (!BrowserThread::CurrentlyOn(BrowserThread::FILE)) {
911 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
912 base::Bind(&BookmarksIOFunction::SelectFile, this, type));
916 // Pre-populating the filename field in case this is a SELECT_SAVEAS_FILE
917 // dialog. If not, there is no filename field in the dialog box.
918 base::FilePath default_path;
919 if (type == ui::SelectFileDialog::SELECT_SAVEAS_FILE)
920 default_path = GetDefaultFilepathForBookmarkExport();
922 DCHECK(type == ui::SelectFileDialog::SELECT_OPEN_FILE);
924 // After getting the |default_path|, ask the UI to display the file dialog.
925 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
926 base::Bind(&BookmarksIOFunction::ShowSelectFileDialog, this,
927 type, default_path));
930 void BookmarksIOFunction::ShowSelectFileDialog(
931 ui::SelectFileDialog::Type type,
932 const base::FilePath& default_path) {
934 return; // Extension was unloaded.
936 // Balanced in one of the three callbacks of SelectFileDialog:
937 // either FileSelectionCanceled, MultiFilesSelected, or FileSelected
940 WebContents* web_contents = dispatcher()->delegate()->
941 GetAssociatedWebContents();
943 select_file_dialog_ = ui::SelectFileDialog::Create(
944 this, new ChromeSelectFilePolicy(web_contents));
945 ui::SelectFileDialog::FileTypeInfo file_type_info;
946 file_type_info.extensions.resize(1);
947 file_type_info.extensions[0].push_back(FILE_PATH_LITERAL("html"));
948 gfx::NativeWindow owning_window = web_contents ?
949 platform_util::GetTopLevel(web_contents->GetView()->GetNativeView())
951 #if defined(OS_WIN) && defined(USE_AURA)
952 if (!owning_window &&
953 chrome::GetActiveDesktop() == chrome::HOST_DESKTOP_TYPE_ASH)
954 owning_window = aura::RemoteRootWindowHostWin::Instance()->GetAshWindow();
956 // |web_contents| can be NULL (for background pages), which is fine. In such
957 // a case if file-selection dialogs are forbidden by policy, we will not
958 // show an InfoBar, which is better than letting one appear out of the blue.
959 select_file_dialog_->SelectFile(type,
964 base::FilePath::StringType(),
969 void BookmarksIOFunction::FileSelectionCanceled(void* params) {
970 Release(); // Balanced in BookmarksIOFunction::SelectFile()
973 void BookmarksIOFunction::MultiFilesSelected(
974 const std::vector<base::FilePath>& files, void* params) {
975 Release(); // Balanced in BookmarsIOFunction::SelectFile()
976 NOTREACHED() << "Should not be able to select multiple files";
979 bool BookmarksImportFunction::RunImpl() {
980 if (!EditBookmarksEnabled())
982 SelectFile(ui::SelectFileDialog::SELECT_OPEN_FILE);
986 void BookmarksImportFunction::FileSelected(const base::FilePath& path,
989 #if !defined(OS_ANDROID)
990 // Android does not have support for the standard importers.
991 // TODO(jgreenwald): remove ifdef once extensions are no longer built on
994 ExternalProcessImporterHost* importer_host = new ExternalProcessImporterHost;
995 importer::SourceProfile source_profile;
996 source_profile.importer_type = importer::TYPE_BOOKMARKS_FILE;
997 source_profile.source_path = path;
998 importer_host->StartImportSettings(source_profile,
1000 importer::FAVORITES,
1001 new ProfileWriter(GetProfile()));
1003 importer::LogImporterUseToMetrics("BookmarksAPI",
1004 importer::TYPE_BOOKMARKS_FILE);
1006 Release(); // Balanced in BookmarksIOFunction::SelectFile()
1009 bool BookmarksExportFunction::RunImpl() {
1010 SelectFile(ui::SelectFileDialog::SELECT_SAVEAS_FILE);
1014 void BookmarksExportFunction::FileSelected(const base::FilePath& path,
1017 #if !defined(OS_ANDROID)
1018 // Android does not have support for the standard exporter.
1019 // TODO(jgreenwald): remove ifdef once extensions are no longer built on
1021 bookmark_html_writer::WriteBookmarks(GetProfile(), path, NULL);
1023 Release(); // Balanced in BookmarksIOFunction::SelectFile()
1026 } // namespace extensions