Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / extensions / api / bookmarks / bookmarks_api.cc
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/extensions/api/bookmarks/bookmarks_api.h"
6
7 #include "base/bind.h"
8 #include "base/files/file_path.h"
9 #include "base/i18n/file_util_icu.h"
10 #include "base/i18n/time_formatting.h"
11 #include "base/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"
50
51 #if defined(OS_WIN)
52 #include "ui/aura/remote_window_tree_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 QuotaService::TimedLimit TimedLimit;
69 typedef QuotaService::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 const BookmarkNode* BookmarksFunction::GetBookmarkNodeFromId(
128     const std::string& id_string) {
129   int64 id;
130   if (!GetBookmarkIdAsInt64(id_string, &id))
131     return NULL;
132
133   BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile());
134   const BookmarkNode* node = model->GetNodeByID(id);
135   if (!node)
136     error_ = keys::kNoNodeError;
137
138   return node;
139 }
140
141 bool BookmarksFunction::EditBookmarksEnabled() {
142   PrefService* prefs = user_prefs::UserPrefs::Get(GetProfile());
143   if (prefs->GetBoolean(prefs::kEditBookmarksEnabled))
144     return true;
145   error_ = keys::kEditBookmarksDisabled;
146   return false;
147 }
148
149 void BookmarksFunction::BookmarkModelChanged() {
150 }
151
152 void BookmarksFunction::BookmarkModelLoaded(BookmarkModel* model,
153                                             bool ids_reassigned) {
154   model->RemoveObserver(this);
155   Run();
156   Release();  // Balanced in Run().
157 }
158
159 BookmarkEventRouter::BookmarkEventRouter(Profile* profile, BookmarkModel* model)
160     : profile_(profile),
161       model_(model) {
162   model_->AddObserver(this);
163 }
164
165 BookmarkEventRouter::~BookmarkEventRouter() {
166   if (model_) {
167     model_->RemoveObserver(this);
168   }
169 }
170
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())));
177   }
178 }
179
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?
184 }
185
186 void BookmarkEventRouter::BookmarkModelBeingDeleted(BookmarkModel* model) {
187   model_ = NULL;
188 }
189
190 void BookmarkEventRouter::BookmarkNodeMoved(BookmarkModel* model,
191                                             const BookmarkNode* old_parent,
192                                             int old_index,
193                                             const BookmarkNode* new_parent,
194                                             int new_index) {
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);
206
207   DispatchEvent(bookmarks::OnMoved::kEventName, args.Pass());
208 }
209
210 void BookmarkEventRouter::BookmarkNodeAdded(BookmarkModel* model,
211                                             const BookmarkNode* parent,
212                                             int index) {
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());
219
220   DispatchEvent(bookmarks::OnCreated::kEventName, args.Pass());
221 }
222
223 void BookmarkEventRouter::BookmarkNodeRemoved(BookmarkModel* model,
224                                               const BookmarkNode* parent,
225                                               int index,
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);
234
235   DispatchEvent(bookmarks::OnRemoved::kEventName, args.Pass());
236 }
237
238 void BookmarkEventRouter::BookmarkAllNodesRemoved(BookmarkModel* model) {
239   NOTREACHED();
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.
243 }
244
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())));
249
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());
257   if (node->is_url())
258     object_args->SetString(keys::kUrlKey, node->url().spec());
259   args->Append(object_args);
260
261   DispatchEvent(bookmarks::OnChanged::kEventName, args.Pass());
262 }
263
264 void BookmarkEventRouter::BookmarkNodeFaviconChanged(BookmarkModel* model,
265                                                      const BookmarkNode* node) {
266   // TODO(erikkay) anything we should do here?
267 }
268
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);
281   }
282   base::DictionaryValue* reorder_info = new base::DictionaryValue();
283   reorder_info->Set(keys::kChildIdsKey, children);
284   args->Append(reorder_info);
285
286   DispatchEvent(bookmarks::OnChildrenReordered::kEventName, args.Pass());
287 }
288
289 void BookmarkEventRouter::ExtensiveBookmarkChangesBeginning(
290     BookmarkModel* model) {
291   scoped_ptr<base::ListValue> args(new base::ListValue());
292   DispatchEvent(bookmarks::OnImportBegan::kEventName, args.Pass());
293 }
294
295 void BookmarkEventRouter::ExtensiveBookmarkChangesEnded(BookmarkModel* model) {
296   scoped_ptr<base::ListValue> args(new base::ListValue());
297   DispatchEvent(bookmarks::OnImportEnded::kEventName, args.Pass());
298 }
299
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);
315 }
316
317 BookmarksAPI::~BookmarksAPI() {
318 }
319
320 void BookmarksAPI::Shutdown() {
321   ExtensionSystem::Get(profile_)->event_router()->UnregisterObserver(this);
322 }
323
324 static base::LazyInstance<ProfileKeyedAPIFactory<BookmarksAPI> >
325 g_factory = LAZY_INSTANCE_INITIALIZER;
326
327 // static
328 ProfileKeyedAPIFactory<BookmarksAPI>* BookmarksAPI::GetFactoryInstance() {
329   return g_factory.Pointer();
330 }
331
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);
336 }
337
338 bool BookmarksGetFunction::RunImpl() {
339   scoped_ptr<bookmarks::Get::Params> params(
340       bookmarks::Get::Params::Create(*args_));
341   EXTENSION_FUNCTION_VALIDATE(params.get());
342
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]);
350       if (!node)
351         return false;
352       bookmark_api_helpers::AddNode(node, &nodes, false);
353     }
354   } else {
355     const BookmarkNode* node =
356         GetBookmarkNodeFromId(*params->id_or_id_list.as_string);
357     if (!node)
358       return false;
359     bookmark_api_helpers::AddNode(node, &nodes, false);
360   }
361
362   results_ = bookmarks::Get::Results::Create(nodes);
363   return true;
364 }
365
366 bool BookmarksGetChildrenFunction::RunImpl() {
367   scoped_ptr<bookmarks::GetChildren::Params> params(
368       bookmarks::GetChildren::Params::Create(*args_));
369   EXTENSION_FUNCTION_VALIDATE(params.get());
370
371   const BookmarkNode* node = GetBookmarkNodeFromId(params->id);
372   if (!node)
373     return false;
374
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);
380   }
381
382   results_ = bookmarks::GetChildren::Results::Create(nodes);
383   return true;
384 }
385
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)
391     return false;
392
393   std::vector<const BookmarkNode*> nodes;
394   bookmark_utils::GetMostRecentlyAddedEntries(
395       BookmarkModelFactory::GetForProfile(GetProfile()),
396       params->number_of_items,
397       &nodes);
398
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);
404   }
405
406   results_ = bookmarks::GetRecent::Results::Create(tree_nodes);
407   return true;
408 }
409
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);
416   return true;
417 }
418
419 bool BookmarksGetSubTreeFunction::RunImpl() {
420   scoped_ptr<bookmarks::GetSubTree::Params> params(
421       bookmarks::GetSubTree::Params::Create(*args_));
422   EXTENSION_FUNCTION_VALIDATE(params.get());
423
424   const BookmarkNode* node = GetBookmarkNodeFromId(params->id);
425   if (!node)
426     return false;
427
428   std::vector<linked_ptr<BookmarkTreeNode> > nodes;
429   bookmark_api_helpers::AddNode(node, &nodes, true);
430   results_ = bookmarks::GetSubTree::Results::Create(nodes);
431   return true;
432 }
433
434 bool BookmarksSearchFunction::RunImpl() {
435   scoped_ptr<bookmarks::Search::Params> params(
436       bookmarks::Search::Params::Create(*args_));
437   EXTENSION_FUNCTION_VALIDATE(params.get());
438
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()),
448         query,
449         std::numeric_limits<int>::max(),
450         lang,
451         &nodes);
452   } else {
453     DCHECK(params->query.as_object);
454     const bookmarks::Search::Params::Query::Object& object =
455         *params->query.as_object;
456     bookmark_utils::QueryFields query;
457     if (object.query) {
458       query.word_phrase_query.reset(
459           new base::string16(base::UTF8ToUTF16(*object.query)));
460     }
461     if (object.url)
462       query.url.reset(new base::string16(base::UTF8ToUTF16(*object.url)));
463     if (object.title)
464       query.title.reset(new base::string16(base::UTF8ToUTF16(*object.title)));
465     bookmark_utils::GetBookmarksMatchingProperties(
466         BookmarkModelFactory::GetForProfile(GetProfile()),
467         query,
468         std::numeric_limits<int>::max(),
469         lang,
470         &nodes);
471   }
472
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);
477   }
478
479   results_ = bookmarks::Search::Results::Create(tree_nodes);
480   return true;
481 }
482
483 // static
484 bool BookmarksRemoveFunction::ExtractIds(const base::ListValue* args,
485                                          std::list<int64>* ids,
486                                          bool* invalid_id) {
487   std::string id_string;
488   if (!args->GetString(0, &id_string))
489     return false;
490   int64 id;
491   if (base::StringToInt64(id_string, &id))
492     ids->push_back(id);
493   else
494     *invalid_id = true;
495   return true;
496 }
497
498 bool BookmarksRemoveFunction::RunImpl() {
499   if (!EditBookmarksEnabled())
500     return false;
501
502   scoped_ptr<bookmarks::Remove::Params> params(
503       bookmarks::Remove::Params::Create(*args_));
504   EXTENSION_FUNCTION_VALIDATE(params.get());
505
506   int64 id;
507   if (!GetBookmarkIdAsInt64(params->id, &id))
508     return false;
509
510   bool recursive = false;
511   if (name() == BookmarksRemoveTreeFunction::function_name())
512     recursive = true;
513
514   BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile());
515   if (!bookmark_api_helpers::RemoveNode(model, id, recursive, &error_))
516     return false;
517
518   return true;
519 }
520
521 bool BookmarksCreateFunction::RunImpl() {
522   if (!EditBookmarksEnabled())
523     return false;
524
525   scoped_ptr<bookmarks::Create::Params> params(
526       bookmarks::Create::Params::Create(*args_));
527   EXTENSION_FUNCTION_VALIDATE(params.get());
528
529   BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile());
530   int64 parentId;
531
532   if (!params->bookmark.parent_id.get()) {
533     // Optional, default to "other bookmarks".
534     parentId = model->other_node()->id();
535   } else {
536     if (!GetBookmarkIdAsInt64(*params->bookmark.parent_id, &parentId))
537       return false;
538   }
539   const BookmarkNode* parent = model->GetNodeByID(parentId);
540   if (!parent) {
541     error_ = keys::kNoParentError;
542     return false;
543   }
544   if (parent->is_root()) {  // Can't create children of the root.
545     error_ = keys::kModifySpecialError;
546     return false;
547   }
548
549   int index;
550   if (!params->bookmark.index.get()) {  // Optional (defaults to end).
551     index = parent->child_count();
552   } else {
553     index = *params->bookmark.index;
554     if (index > parent->child_count() || index < 0) {
555       error_ = keys::kInvalidIndexError;
556       return false;
557     }
558   }
559
560   base::string16 title;  // Optional.
561   if (params->bookmark.title.get())
562     title = base::UTF8ToUTF16(*params->bookmark.title.get());
563
564   std::string url_string;  // Optional.
565   if (params->bookmark.url.get())
566     url_string = *params->bookmark.url.get();
567
568   GURL url(url_string);
569   if (!url_string.empty() && !url.is_valid()) {
570     error_ = keys::kInvalidUrlError;
571     return false;
572   }
573
574   const BookmarkNode* node;
575   if (url_string.length())
576     node = model->AddURL(parent, index, title, url);
577   else
578     node = model->AddFolder(parent, index, title);
579   DCHECK(node);
580   if (!node) {
581     error_ = keys::kNoNodeError;
582     return false;
583   }
584
585   scoped_ptr<BookmarkTreeNode> ret(
586       bookmark_api_helpers::GetBookmarkTreeNode(node, false, false));
587   results_ = bookmarks::Create::Results::Create(*ret);
588
589   return true;
590 }
591
592 // static
593 bool BookmarksMoveFunction::ExtractIds(const base::ListValue* args,
594                                        std::list<int64>* ids,
595                                        bool* invalid_id) {
596   // For now, Move accepts ID parameters in the same way as an Update.
597   return BookmarksUpdateFunction::ExtractIds(args, ids, invalid_id);
598 }
599
600 bool BookmarksMoveFunction::RunImpl() {
601   if (!EditBookmarksEnabled())
602     return false;
603
604   scoped_ptr<bookmarks::Move::Params> params(
605       bookmarks::Move::Params::Create(*args_));
606   EXTENSION_FUNCTION_VALIDATE(params.get());
607
608   const BookmarkNode* node = GetBookmarkNodeFromId(params->id);
609   if (!node)
610     return false;
611
612   BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile());
613   if (model->is_permanent_node(node)) {
614     error_ = keys::kModifySpecialError;
615     return false;
616   }
617
618   const BookmarkNode* parent = NULL;
619   if (!params->destination.parent_id.get()) {
620     // Optional, defaults to current parent.
621     parent = node->parent();
622   } else {
623     int64 parentId;
624     if (!GetBookmarkIdAsInt64(*params->destination.parent_id, &parentId))
625       return false;
626
627     parent = model->GetNodeByID(parentId);
628   }
629   if (!parent) {
630     error_ = keys::kNoParentError;
631     // TODO(erikkay) return an error message.
632     return false;
633   }
634   if (parent == model->root_node()) {
635     error_ = keys::kModifySpecialError;
636     return false;
637   }
638
639   int index;
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;
644       return false;
645     }
646   } else {
647     index = parent->child_count();
648   }
649
650   model->Move(node, parent, index);
651
652   scoped_ptr<BookmarkTreeNode> tree_node(
653       bookmark_api_helpers::GetBookmarkTreeNode(node, false, false));
654   results_ = bookmarks::Move::Results::Create(*tree_node);
655
656   return true;
657 }
658
659 // static
660 bool BookmarksUpdateFunction::ExtractIds(const base::ListValue* args,
661                                          std::list<int64>* ids,
662                                          bool* invalid_id) {
663   // For now, Update accepts ID parameters in the same way as an Remove.
664   return BookmarksRemoveFunction::ExtractIds(args, ids, invalid_id);
665 }
666
667 bool BookmarksUpdateFunction::RunImpl() {
668   if (!EditBookmarksEnabled())
669     return false;
670
671   scoped_ptr<bookmarks::Update::Params> params(
672       bookmarks::Update::Params::Create(*args_));
673   EXTENSION_FUNCTION_VALIDATE(params.get());
674
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);
680     has_title = true;
681   }
682
683   // Optional.
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;
690     return false;
691   }
692
693   const BookmarkNode* node = GetBookmarkNodeFromId(params->id);
694   if (!node)
695     return false;
696
697   BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile());
698   if (model->is_permanent_node(node)) {
699     error_ = keys::kModifySpecialError;
700     return false;
701   }
702   if (has_title)
703     model->SetTitle(node, title);
704   if (!url.is_empty())
705     model->SetURL(node, url);
706
707   scoped_ptr<BookmarkTreeNode> tree_node(
708       bookmark_api_helpers::GetBookmarkTreeNode(node, false, false));
709   results_ = bookmarks::Update::Results::Create(*tree_node);
710   return true;
711 }
712
713 // Mapper superclass for BookmarkFunctions.
714 template <typename BucketIdType>
715 class BookmarkBucketMapper : public BucketMapper {
716  public:
717   virtual ~BookmarkBucketMapper() { STLDeleteValues(&buckets_); }
718  protected:
719   Bucket* GetBucket(const BucketIdType& id) {
720     Bucket* b = buckets_[id];
721     if (b == NULL) {
722       b = new Bucket();
723       buckets_[id] = b;
724     }
725     return b;
726   }
727  private:
728   std::map<BucketIdType, Bucket*> buckets_;
729 };
730
731 // Mapper for 'bookmarks.create'.  Maps "same input to bookmarks.create" to a
732 // unique bucket.
733 class CreateBookmarkBucketMapper : public BookmarkBucketMapper<std::string> {
734  public:
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))
742       return;
743
744     std::string parent_id;
745     if (json->HasKey(keys::kParentIdKey)) {
746       if (!json->GetString(keys::kParentIdKey, &parent_id))
747         return;
748     }
749     BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile_);
750
751     int64 parent_id_int64;
752     base::StringToInt64(parent_id, &parent_id_int64);
753     const BookmarkNode* parent = model->GetNodeByID(parent_id_int64);
754     if (!parent)
755       return;
756
757     std::string bucket_id = base::UTF16ToUTF8(parent->GetTitle());
758     std::string title;
759     json->GetString(keys::kTitleKey, &title);
760     std::string url_string;
761     json->GetString(keys::kUrlKey, &url_string);
762
763     bucket_id += title;
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)));
768   }
769  private:
770   Profile* profile_;
771 };
772
773 // Mapper for 'bookmarks.remove'.
774 class RemoveBookmarksBucketMapper : public BookmarkBucketMapper<std::string> {
775  public:
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;
780     IdList ids;
781     bool invalid_id = false;
782     if (!BookmarksRemoveFunction::ExtractIds(args, &ids, &invalid_id) ||
783         invalid_id) {
784       return;
785     }
786
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())
791         return;
792
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)));
798     }
799   }
800  private:
801   Profile* profile_;
802 };
803
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> {
810  public:
811   typedef std::list<int64> IdList;
812   virtual void GetBucketsForArgs(const base::ListValue* args,
813                                  BucketList* buckets) {
814     IdList ids;
815     bool invalid_id = false;
816     if (!FunctionType::ExtractIds(args, &ids, &invalid_id) || invalid_id)
817       return;
818     for (IdList::iterator it = ids.begin(); it != ids.end(); ++it)
819       buckets->push_back(GetBucket(*it));
820   }
821 };
822
823 // Builds heuristics for all BookmarkFunctions using specialized BucketMappers.
824 class BookmarksQuotaLimitFactory {
825  public:
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>());
831   }
832
833   // For bookmarks.create.
834   static void BuildForCreate(QuotaLimitHeuristics* heuristics,
835                              Profile* profile) {
836     BuildWithMappers(heuristics, new CreateBookmarkBucketMapper(profile),
837                                  new CreateBookmarkBucketMapper(profile));
838   }
839
840   // For bookmarks.remove.
841   static void BuildForRemove(QuotaLimitHeuristics* heuristics,
842                              Profile* profile) {
843     BuildWithMappers(heuristics, new RemoveBookmarksBucketMapper(profile),
844                                  new RemoveBookmarksBucketMapper(profile));
845   }
846
847  private:
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)
854     };
855     heuristics->push_back(new SustainedLimit(
856         TimeDelta::FromMinutes(10),
857         kSustainedLimitConfig,
858         short_mapper,
859         "MAX_SUSTAINED_WRITE_OPERATIONS_PER_MINUTE"));
860
861     const Config kTimedLimitConfig = {
862       // See bookmarks.json for current value.
863       bookmarks::MAX_WRITE_OPERATIONS_PER_HOUR,
864       TimeDelta::FromHours(1)
865     };
866     heuristics->push_back(new TimedLimit(
867         kTimedLimitConfig,
868         long_mapper,
869         "MAX_WRITE_OPERATIONS_PER_HOUR"));
870   }
871
872   DISALLOW_IMPLICIT_CONSTRUCTORS(BookmarksQuotaLimitFactory);
873 };
874
875 // And finally, building the individual heuristics for each function.
876 void BookmarksRemoveFunction::GetQuotaLimitHeuristics(
877     QuotaLimitHeuristics* heuristics) const {
878   BookmarksQuotaLimitFactory::BuildForRemove(heuristics, GetProfile());
879 }
880
881 void BookmarksMoveFunction::GetQuotaLimitHeuristics(
882     QuotaLimitHeuristics* heuristics) const {
883   BookmarksQuotaLimitFactory::Build<BookmarksMoveFunction>(heuristics);
884 }
885
886 void BookmarksUpdateFunction::GetQuotaLimitHeuristics(
887     QuotaLimitHeuristics* heuristics) const {
888   BookmarksQuotaLimitFactory::Build<BookmarksUpdateFunction>(heuristics);
889 };
890
891 void BookmarksCreateFunction::GetQuotaLimitHeuristics(
892     QuotaLimitHeuristics* heuristics) const {
893   BookmarksQuotaLimitFactory::BuildForCreate(heuristics, GetProfile());
894 }
895
896 BookmarksIOFunction::BookmarksIOFunction() {}
897
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();
903 }
904
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));
911     return;
912   }
913
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();
919   else
920     DCHECK(type == ui::SelectFileDialog::SELECT_OPEN_FILE);
921
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));
926 }
927
928 void BookmarksIOFunction::ShowSelectFileDialog(
929     ui::SelectFileDialog::Type type,
930     const base::FilePath& default_path) {
931   if (!dispatcher())
932     return;  // Extension was unloaded.
933
934   // Balanced in one of the three callbacks of SelectFileDialog:
935   // either FileSelectionCanceled, MultiFilesSelected, or FileSelected
936   AddRef();
937
938   WebContents* web_contents = dispatcher()->delegate()->
939       GetAssociatedWebContents();
940
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())
948           : NULL;
949 #if defined(OS_WIN)
950   if (!owning_window &&
951       chrome::GetActiveDesktop() == chrome::HOST_DESKTOP_TYPE_ASH)
952     owning_window = aura::RemoteWindowTreeHostWin::Instance()->GetAshWindow();
953 #endif
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,
958                                   base::string16(),
959                                   default_path,
960                                   &file_type_info,
961                                   0,
962                                   base::FilePath::StringType(),
963                                   owning_window,
964                                   NULL);
965 }
966
967 void BookmarksIOFunction::FileSelectionCanceled(void* params) {
968   Release();  // Balanced in BookmarksIOFunction::SelectFile()
969 }
970
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";
975 }
976
977 bool BookmarksImportFunction::RunImpl() {
978   if (!EditBookmarksEnabled())
979     return false;
980   SelectFile(ui::SelectFileDialog::SELECT_OPEN_FILE);
981   return true;
982 }
983
984 void BookmarksImportFunction::FileSelected(const base::FilePath& path,
985                                            int index,
986                                            void* params) {
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
990   // Android.
991   // Deletes itself.
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,
997                                      GetProfile(),
998                                      importer::FAVORITES,
999                                      new ProfileWriter(GetProfile()));
1000
1001   importer::LogImporterUseToMetrics("BookmarksAPI",
1002                                     importer::TYPE_BOOKMARKS_FILE);
1003 #endif
1004   Release();  // Balanced in BookmarksIOFunction::SelectFile()
1005 }
1006
1007 bool BookmarksExportFunction::RunImpl() {
1008   SelectFile(ui::SelectFileDialog::SELECT_SAVEAS_FILE);
1009   return true;
1010 }
1011
1012 void BookmarksExportFunction::FileSelected(const base::FilePath& path,
1013                                            int index,
1014                                            void* params) {
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
1018   // Android.
1019   bookmark_html_writer::WriteBookmarks(GetProfile(), path, NULL);
1020 #endif
1021   Release();  // Balanced in BookmarksIOFunction::SelectFile()
1022 }
1023
1024 }  // namespace extensions