- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / extensions / api / bookmarks / bookmarks_api.cc
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/extensions/api/bookmarks/bookmarks_api.h"
6
7 #include "base/bind.h"
8 #include "base/files/file_path.h"
9 #include "base/i18n/file_util_icu.h"
10 #include "base/i18n/time_formatting.h"
11 #include "base/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"
50
51 #if defined(OS_WIN) && defined(USE_AURA)
52 #include "ui/aura/remote_root_window_host_win.h"
53 #endif
54
55 namespace extensions {
56
57 namespace keys = bookmark_api_constants;
58 namespace bookmarks = api::bookmarks;
59
60 using base::TimeDelta;
61 using bookmarks::BookmarkTreeNode;
62 using content::BrowserThread;
63 using content::WebContents;
64
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;
71
72 namespace {
73
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();
78
79   // Concatenate a date stamp to the filename.
80 #if defined(OS_POSIX)
81   base::FilePath::StringType filename =
82       l10n_util::GetStringFUTF8(IDS_EXPORT_BOOKMARKS_DEFAULT_FILENAME,
83                                 base::TimeFormatShortDateNumeric(time));
84 #elif defined(OS_WIN)
85   base::FilePath::StringType filename =
86       l10n_util::GetStringFUTF16(IDS_EXPORT_BOOKMARKS_DEFAULT_FILENAME,
87                                  base::TimeFormatShortDateNumeric(time));
88 #endif
89
90   file_util::ReplaceIllegalCharactersInPath(&filename, '_');
91
92   base::FilePath default_path;
93   PathService::Get(chrome::DIR_USER_DOCUMENTS, &default_path);
94   return default_path.Append(filename);
95 }
96
97 }  // namespace
98
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().
105     return;
106   }
107
108   bool success = RunImpl();
109   if (success) {
110     content::NotificationService::current()->Notify(
111         chrome::NOTIFICATION_EXTENSION_BOOKMARKS_API_INVOKED,
112         content::Source<const Extension>(GetExtension()),
113         content::Details<const BookmarksFunction>(this));
114   }
115   SendResponse(success);
116 }
117
118 bool BookmarksFunction::GetBookmarkIdAsInt64(const std::string& id_string,
119                                              int64* id) {
120   if (base::StringToInt64(id_string, id))
121     return true;
122
123   error_ = keys::kInvalidIdError;
124   return false;
125 }
126
127 bool BookmarksFunction::EditBookmarksEnabled() {
128   PrefService* prefs = user_prefs::UserPrefs::Get(GetProfile());
129   if (prefs->GetBoolean(prefs::kEditBookmarksEnabled))
130     return true;
131   error_ = keys::kEditBookmarksDisabled;
132   return false;
133 }
134
135 void BookmarksFunction::BookmarkModelChanged() {
136 }
137
138 void BookmarksFunction::Loaded(BookmarkModel* model, bool ids_reassigned) {
139   model->RemoveObserver(this);
140   Run();
141   Release();  // Balanced in Run().
142 }
143
144 BookmarkEventRouter::BookmarkEventRouter(Profile* profile,
145                                          BookmarkModel* model)
146     : profile_(profile),
147       model_(model) {
148   model_->AddObserver(this);
149 }
150
151 BookmarkEventRouter::~BookmarkEventRouter() {
152   if (model_) {
153     model_->RemoveObserver(this);
154   }
155 }
156
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())));
163   }
164 }
165
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?
169 }
170
171 void BookmarkEventRouter::BookmarkModelBeingDeleted(BookmarkModel* model) {
172   model_ = NULL;
173 }
174
175 void BookmarkEventRouter::BookmarkNodeMoved(BookmarkModel* model,
176                                             const BookmarkNode* old_parent,
177                                             int old_index,
178                                             const BookmarkNode* new_parent,
179                                             int new_index) {
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);
191
192   DispatchEvent(bookmarks::OnMoved::kEventName, args.Pass());
193 }
194
195 void BookmarkEventRouter::BookmarkNodeAdded(BookmarkModel* model,
196                                             const BookmarkNode* parent,
197                                             int index) {
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());
204
205   DispatchEvent(bookmarks::OnCreated::kEventName, args.Pass());
206 }
207
208 void BookmarkEventRouter::BookmarkNodeRemoved(BookmarkModel* model,
209                                               const BookmarkNode* parent,
210                                               int index,
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);
219
220   DispatchEvent(bookmarks::OnRemoved::kEventName, args.Pass());
221 }
222
223 void BookmarkEventRouter::BookmarkAllNodesRemoved(BookmarkModel* model) {
224   NOTREACHED();
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.
228 }
229
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())));
234
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());
242   if (node->is_url())
243     object_args->SetString(keys::kUrlKey, node->url().spec());
244   args->Append(object_args);
245
246   DispatchEvent(bookmarks::OnChanged::kEventName, args.Pass());
247 }
248
249 void BookmarkEventRouter::BookmarkNodeFaviconChanged(BookmarkModel* model,
250                                                      const BookmarkNode* node) {
251   // TODO(erikkay) anything we should do here?
252 }
253
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);
266   }
267   base::DictionaryValue* reorder_info = new base::DictionaryValue();
268   reorder_info->Set(keys::kChildIdsKey, children);
269   args->Append(reorder_info);
270
271   DispatchEvent(bookmarks::OnChildrenReordered::kEventName, args.Pass());
272 }
273
274 void BookmarkEventRouter::ExtensiveBookmarkChangesBeginning(
275     BookmarkModel* model) {
276   scoped_ptr<base::ListValue> args(new base::ListValue());
277   DispatchEvent(bookmarks::OnImportBegan::kEventName, args.Pass());
278 }
279
280 void BookmarkEventRouter::ExtensiveBookmarkChangesEnded(BookmarkModel* model) {
281   scoped_ptr<base::ListValue> args(new base::ListValue());
282   DispatchEvent(bookmarks::OnImportEnded::kEventName, args.Pass());
283 }
284
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);
300 }
301
302 BookmarksAPI::~BookmarksAPI() {
303 }
304
305 void BookmarksAPI::Shutdown() {
306   ExtensionSystem::Get(profile_)->event_router()->UnregisterObserver(this);
307 }
308
309 static base::LazyInstance<ProfileKeyedAPIFactory<BookmarksAPI> >
310 g_factory = LAZY_INSTANCE_INITIALIZER;
311
312 // static
313 ProfileKeyedAPIFactory<BookmarksAPI>* BookmarksAPI::GetFactoryInstance() {
314   return &g_factory.Get();
315 }
316
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);
321 }
322
323 bool BookmarksGetFunction::RunImpl() {
324   scoped_ptr<bookmarks::Get::Params> params(
325       bookmarks::Get::Params::Create(*args_));
326   EXTENSION_FUNCTION_VALIDATE(params.get());
327
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) {
335       int64 id;
336       if (!GetBookmarkIdAsInt64(ids[i], &id))
337         return false;
338       const BookmarkNode* node = model->GetNodeByID(id);
339       if (!node) {
340         error_ = keys::kNoNodeError;
341         return false;
342       } else {
343         bookmark_api_helpers::AddNode(node, &nodes, false);
344       }
345     }
346   } else {
347     int64 id;
348     if (!GetBookmarkIdAsInt64(*params->id_or_id_list.as_string, &id))
349       return false;
350     const BookmarkNode* node = model->GetNodeByID(id);
351     if (!node) {
352       error_ = keys::kNoNodeError;
353       return false;
354     }
355     bookmark_api_helpers::AddNode(node, &nodes, false);
356   }
357
358   results_ = bookmarks::Get::Results::Create(nodes);
359   return true;
360 }
361
362 bool BookmarksGetChildrenFunction::RunImpl() {
363   scoped_ptr<bookmarks::GetChildren::Params> params(
364       bookmarks::GetChildren::Params::Create(*args_));
365   EXTENSION_FUNCTION_VALIDATE(params.get());
366
367   int64 id;
368   if (!GetBookmarkIdAsInt64(params->id, &id))
369     return false;
370
371   std::vector<linked_ptr<BookmarkTreeNode> > nodes;
372   const BookmarkNode* node =
373       BookmarkModelFactory::GetForProfile(GetProfile())->GetNodeByID(id);
374   if (!node) {
375     error_ = keys::kNoNodeError;
376     return false;
377   }
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);
382   }
383
384   results_ = bookmarks::GetChildren::Results::Create(nodes);
385   return true;
386 }
387
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)
393     return false;
394
395   std::vector<const BookmarkNode*> nodes;
396   bookmark_utils::GetMostRecentlyAddedEntries(
397       BookmarkModelFactory::GetForProfile(GetProfile()),
398       params->number_of_items,
399       &nodes);
400
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);
406   }
407
408   results_ = bookmarks::GetRecent::Results::Create(tree_nodes);
409   return true;
410 }
411
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);
418   return true;
419 }
420
421 bool BookmarksGetSubTreeFunction::RunImpl() {
422   scoped_ptr<bookmarks::GetSubTree::Params> params(
423       bookmarks::GetSubTree::Params::Create(*args_));
424   EXTENSION_FUNCTION_VALIDATE(params.get());
425
426   int64 id;
427   if (!GetBookmarkIdAsInt64(params->id, &id))
428     return false;
429
430   const BookmarkNode* node =
431       BookmarkModelFactory::GetForProfile(GetProfile())->GetNodeByID(id);
432   if (!node) {
433     error_ = keys::kNoNodeError;
434     return false;
435   }
436
437   std::vector<linked_ptr<BookmarkTreeNode> > nodes;
438   bookmark_api_helpers::AddNode(node, &nodes, true);
439   results_ = bookmarks::GetSubTree::Results::Create(nodes);
440   return true;
441 }
442
443 bool BookmarksSearchFunction::RunImpl() {
444   scoped_ptr<bookmarks::Search::Params> params(
445       bookmarks::Search::Params::Create(*args_));
446   EXTENSION_FUNCTION_VALIDATE(params.get());
447
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(),
455       lang,
456       &nodes);
457
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);
462   }
463
464   results_ = bookmarks::Search::Results::Create(tree_nodes);
465   return true;
466 }
467
468 // static
469 bool BookmarksRemoveFunction::ExtractIds(const base::ListValue* args,
470                                          std::list<int64>* ids,
471                                          bool* invalid_id) {
472   std::string id_string;
473   if (!args->GetString(0, &id_string))
474     return false;
475   int64 id;
476   if (base::StringToInt64(id_string, &id))
477     ids->push_back(id);
478   else
479     *invalid_id = true;
480   return true;
481 }
482
483 bool BookmarksRemoveFunction::RunImpl() {
484   if (!EditBookmarksEnabled())
485     return false;
486
487   scoped_ptr<bookmarks::Remove::Params> params(
488       bookmarks::Remove::Params::Create(*args_));
489   EXTENSION_FUNCTION_VALIDATE(params.get());
490
491   int64 id;
492   if (!base::StringToInt64(params->id, &id)) {
493     error_ = keys::kInvalidIdError;
494     return false;
495   }
496
497   bool recursive = false;
498   if (name() == BookmarksRemoveTreeFunction::function_name())
499     recursive = true;
500
501   BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile());
502   if (!bookmark_api_helpers::RemoveNode(model, id, recursive, &error_))
503     return false;
504
505   return true;
506 }
507
508 bool BookmarksCreateFunction::RunImpl() {
509   if (!EditBookmarksEnabled())
510     return false;
511
512   scoped_ptr<bookmarks::Create::Params> params(
513       bookmarks::Create::Params::Create(*args_));
514   EXTENSION_FUNCTION_VALIDATE(params.get());
515
516   BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile());
517   int64 parentId;
518
519   if (!params->bookmark.parent_id.get()) {
520     // Optional, default to "other bookmarks".
521     parentId = model->other_node()->id();
522   } else {
523     if (!GetBookmarkIdAsInt64(*params->bookmark.parent_id, &parentId))
524       return false;
525   }
526   const BookmarkNode* parent = model->GetNodeByID(parentId);
527   if (!parent) {
528     error_ = keys::kNoParentError;
529     return false;
530   }
531   if (parent->is_root()) {  // Can't create children of the root.
532     error_ = keys::kModifySpecialError;
533     return false;
534   }
535
536   int index;
537   if (!params->bookmark.index.get()) {  // Optional (defaults to end).
538     index = parent->child_count();
539   } else {
540     index = *params->bookmark.index;
541     if (index > parent->child_count() || index < 0) {
542       error_ = keys::kInvalidIndexError;
543       return false;
544     }
545   }
546
547   string16 title;  // Optional.
548   if (params->bookmark.title.get())
549     title = UTF8ToUTF16(*params->bookmark.title.get());
550
551   std::string url_string;  // Optional.
552   if (params->bookmark.url.get())
553     url_string = *params->bookmark.url.get();
554
555   GURL url(url_string);
556   if (!url_string.empty() && !url.is_valid()) {
557     error_ = keys::kInvalidUrlError;
558     return false;
559   }
560
561   const BookmarkNode* node;
562   if (url_string.length())
563     node = model->AddURL(parent, index, title, url);
564   else
565     node = model->AddFolder(parent, index, title);
566   DCHECK(node);
567   if (!node) {
568     error_ = keys::kNoNodeError;
569     return false;
570   }
571
572   scoped_ptr<BookmarkTreeNode> ret(
573       bookmark_api_helpers::GetBookmarkTreeNode(node, false, false));
574   results_ = bookmarks::Create::Results::Create(*ret);
575
576   return true;
577 }
578
579 // static
580 bool BookmarksMoveFunction::ExtractIds(const base::ListValue* args,
581                                        std::list<int64>* ids,
582                                        bool* invalid_id) {
583   // For now, Move accepts ID parameters in the same way as an Update.
584   return BookmarksUpdateFunction::ExtractIds(args, ids, invalid_id);
585 }
586
587 bool BookmarksMoveFunction::RunImpl() {
588   if (!EditBookmarksEnabled())
589     return false;
590
591   scoped_ptr<bookmarks::Move::Params> params(
592       bookmarks::Move::Params::Create(*args_));
593   EXTENSION_FUNCTION_VALIDATE(params.get());
594
595   int64 id;
596   if (!base::StringToInt64(params->id, &id)) {
597     error_ = keys::kInvalidIdError;
598     return false;
599   }
600
601   BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile());
602   const BookmarkNode* node = model->GetNodeByID(id);
603   if (!node) {
604     error_ = keys::kNoNodeError;
605     return false;
606   }
607   if (model->is_permanent_node(node)) {
608     error_ = keys::kModifySpecialError;
609     return false;
610   }
611
612   const BookmarkNode* parent = NULL;
613   if (!params->destination.parent_id.get()) {
614     // Optional, defaults to current parent.
615     parent = node->parent();
616   } else {
617     int64 parentId;
618     if (!GetBookmarkIdAsInt64(*params->destination.parent_id, &parentId))
619       return false;
620
621     parent = model->GetNodeByID(parentId);
622   }
623   if (!parent) {
624     error_ = keys::kNoParentError;
625     // TODO(erikkay) return an error message.
626     return false;
627   }
628   if (parent == model->root_node()) {
629     error_ = keys::kModifySpecialError;
630     return false;
631   }
632
633   int index;
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;
638       return false;
639     }
640   } else {
641     index = parent->child_count();
642   }
643
644   model->Move(node, parent, index);
645
646   scoped_ptr<BookmarkTreeNode> tree_node(
647       bookmark_api_helpers::GetBookmarkTreeNode(node, false, false));
648   results_ = bookmarks::Move::Results::Create(*tree_node);
649
650   return true;
651 }
652
653 // static
654 bool BookmarksUpdateFunction::ExtractIds(const base::ListValue* args,
655                                          std::list<int64>* ids,
656                                          bool* invalid_id) {
657   // For now, Update accepts ID parameters in the same way as an Remove.
658   return BookmarksRemoveFunction::ExtractIds(args, ids, invalid_id);
659 }
660
661 bool BookmarksUpdateFunction::RunImpl() {
662   if (!EditBookmarksEnabled())
663     return false;
664
665   scoped_ptr<bookmarks::Update::Params> params(
666       bookmarks::Update::Params::Create(*args_));
667   EXTENSION_FUNCTION_VALIDATE(params.get());
668
669   int64 id;
670   if (!base::StringToInt64(params->id, &id)) {
671     error_ = keys::kInvalidIdError;
672     return false;
673   }
674
675   BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile());
676
677   // Optional but we need to distinguish non present from an empty title.
678   string16 title;
679   bool has_title = false;
680   if (params->changes.title.get()) {
681     title = UTF8ToUTF16(*params->changes.title);
682     has_title = true;
683   }
684
685   // Optional.
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;
692     return false;
693   }
694
695   const BookmarkNode* node = model->GetNodeByID(id);
696   if (!node) {
697     error_ = keys::kNoNodeError;
698     return false;
699   }
700   if (model->is_permanent_node(node)) {
701     error_ = keys::kModifySpecialError;
702     return false;
703   }
704   if (has_title)
705     model->SetTitle(node, title);
706   if (!url.is_empty())
707     model->SetURL(node, url);
708
709   scoped_ptr<BookmarkTreeNode> tree_node(
710       bookmark_api_helpers::GetBookmarkTreeNode(node, false, false));
711   results_ = bookmarks::Update::Results::Create(*tree_node);
712   return true;
713 }
714
715 // Mapper superclass for BookmarkFunctions.
716 template <typename BucketIdType>
717 class BookmarkBucketMapper : public BucketMapper {
718  public:
719   virtual ~BookmarkBucketMapper() { STLDeleteValues(&buckets_); }
720  protected:
721   Bucket* GetBucket(const BucketIdType& id) {
722     Bucket* b = buckets_[id];
723     if (b == NULL) {
724       b = new Bucket();
725       buckets_[id] = b;
726     }
727     return b;
728   }
729  private:
730   std::map<BucketIdType, Bucket*> buckets_;
731 };
732
733 // Mapper for 'bookmarks.create'.  Maps "same input to bookmarks.create" to a
734 // unique bucket.
735 class CreateBookmarkBucketMapper : public BookmarkBucketMapper<std::string> {
736  public:
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))
744       return;
745
746     std::string parent_id;
747     if (json->HasKey(keys::kParentIdKey)) {
748       if (!json->GetString(keys::kParentIdKey, &parent_id))
749         return;
750     }
751     BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile_);
752
753     int64 parent_id_int64;
754     base::StringToInt64(parent_id, &parent_id_int64);
755     const BookmarkNode* parent = model->GetNodeByID(parent_id_int64);
756     if (!parent)
757       return;
758
759     std::string bucket_id = UTF16ToUTF8(parent->GetTitle());
760     std::string title;
761     json->GetString(keys::kTitleKey, &title);
762     std::string url_string;
763     json->GetString(keys::kUrlKey, &url_string);
764
765     bucket_id += title;
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)));
770   }
771  private:
772   Profile* profile_;
773 };
774
775 // Mapper for 'bookmarks.remove'.
776 class RemoveBookmarksBucketMapper : public BookmarkBucketMapper<std::string> {
777  public:
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;
782     IdList ids;
783     bool invalid_id = false;
784     if (!BookmarksRemoveFunction::ExtractIds(args, &ids, &invalid_id) ||
785         invalid_id) {
786       return;
787     }
788
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())
793         return;
794
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)));
800     }
801   }
802  private:
803   Profile* profile_;
804 };
805
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> {
812  public:
813   typedef std::list<int64> IdList;
814   virtual void GetBucketsForArgs(const base::ListValue* args,
815                                  BucketList* buckets) {
816     IdList ids;
817     bool invalid_id = false;
818     if (!FunctionType::ExtractIds(args, &ids, &invalid_id) || invalid_id)
819       return;
820     for (IdList::iterator it = ids.begin(); it != ids.end(); ++it)
821       buckets->push_back(GetBucket(*it));
822   }
823 };
824
825 // Builds heuristics for all BookmarkFunctions using specialized BucketMappers.
826 class BookmarksQuotaLimitFactory {
827  public:
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>());
833   }
834
835   // For bookmarks.create.
836   static void BuildForCreate(QuotaLimitHeuristics* heuristics,
837                              Profile* profile) {
838     BuildWithMappers(heuristics, new CreateBookmarkBucketMapper(profile),
839                                  new CreateBookmarkBucketMapper(profile));
840   }
841
842   // For bookmarks.remove.
843   static void BuildForRemove(QuotaLimitHeuristics* heuristics,
844                              Profile* profile) {
845     BuildWithMappers(heuristics, new RemoveBookmarksBucketMapper(profile),
846                                  new RemoveBookmarksBucketMapper(profile));
847   }
848
849  private:
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)
856     };
857     heuristics->push_back(new SustainedLimit(
858         TimeDelta::FromMinutes(10),
859         kSustainedLimitConfig,
860         short_mapper,
861         "MAX_SUSTAINED_WRITE_OPERATIONS_PER_MINUTE"));
862
863     const Config kTimedLimitConfig = {
864       // See bookmarks.json for current value.
865       bookmarks::MAX_WRITE_OPERATIONS_PER_HOUR,
866       TimeDelta::FromHours(1)
867     };
868     heuristics->push_back(new TimedLimit(
869         kTimedLimitConfig,
870         long_mapper,
871         "MAX_WRITE_OPERATIONS_PER_HOUR"));
872   }
873
874   DISALLOW_IMPLICIT_CONSTRUCTORS(BookmarksQuotaLimitFactory);
875 };
876
877 // And finally, building the individual heuristics for each function.
878 void BookmarksRemoveFunction::GetQuotaLimitHeuristics(
879     QuotaLimitHeuristics* heuristics) const {
880   BookmarksQuotaLimitFactory::BuildForRemove(heuristics, GetProfile());
881 }
882
883 void BookmarksMoveFunction::GetQuotaLimitHeuristics(
884     QuotaLimitHeuristics* heuristics) const {
885   BookmarksQuotaLimitFactory::Build<BookmarksMoveFunction>(heuristics);
886 }
887
888 void BookmarksUpdateFunction::GetQuotaLimitHeuristics(
889     QuotaLimitHeuristics* heuristics) const {
890   BookmarksQuotaLimitFactory::Build<BookmarksUpdateFunction>(heuristics);
891 };
892
893 void BookmarksCreateFunction::GetQuotaLimitHeuristics(
894     QuotaLimitHeuristics* heuristics) const {
895   BookmarksQuotaLimitFactory::BuildForCreate(heuristics, GetProfile());
896 }
897
898 BookmarksIOFunction::BookmarksIOFunction() {}
899
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();
905 }
906
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));
913     return;
914   }
915
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();
921   else
922     DCHECK(type == ui::SelectFileDialog::SELECT_OPEN_FILE);
923
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));
928 }
929
930 void BookmarksIOFunction::ShowSelectFileDialog(
931     ui::SelectFileDialog::Type type,
932     const base::FilePath& default_path) {
933   if (!dispatcher())
934     return;  // Extension was unloaded.
935
936   // Balanced in one of the three callbacks of SelectFileDialog:
937   // either FileSelectionCanceled, MultiFilesSelected, or FileSelected
938   AddRef();
939
940   WebContents* web_contents = dispatcher()->delegate()->
941       GetAssociatedWebContents();
942
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())
950           : NULL;
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();
955 #endif
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,
960                                   string16(),
961                                   default_path,
962                                   &file_type_info,
963                                   0,
964                                   base::FilePath::StringType(),
965                                   owning_window,
966                                   NULL);
967 }
968
969 void BookmarksIOFunction::FileSelectionCanceled(void* params) {
970   Release();  // Balanced in BookmarksIOFunction::SelectFile()
971 }
972
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";
977 }
978
979 bool BookmarksImportFunction::RunImpl() {
980   if (!EditBookmarksEnabled())
981     return false;
982   SelectFile(ui::SelectFileDialog::SELECT_OPEN_FILE);
983   return true;
984 }
985
986 void BookmarksImportFunction::FileSelected(const base::FilePath& path,
987                                            int index,
988                                            void* params) {
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
992   // Android.
993   // Deletes itself.
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,
999                                      GetProfile(),
1000                                      importer::FAVORITES,
1001                                      new ProfileWriter(GetProfile()));
1002
1003   importer::LogImporterUseToMetrics("BookmarksAPI",
1004                                     importer::TYPE_BOOKMARKS_FILE);
1005 #endif
1006   Release();  // Balanced in BookmarksIOFunction::SelectFile()
1007 }
1008
1009 bool BookmarksExportFunction::RunImpl() {
1010   SelectFile(ui::SelectFileDialog::SELECT_SAVEAS_FILE);
1011   return true;
1012 }
1013
1014 void BookmarksExportFunction::FileSelected(const base::FilePath& path,
1015                                            int index,
1016                                            void* params) {
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
1020   // Android.
1021   bookmark_html_writer::WriteBookmarks(GetProfile(), path, NULL);
1022 #endif
1023   Release();  // Balanced in BookmarksIOFunction::SelectFile()
1024 }
1025
1026 }  // namespace extensions