• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 <algorithm>
6 
7 #include "chrome/browser/extensions/api/bookmarks/bookmarks_api.h"
8 
9 #include "base/bind.h"
10 #include "base/files/file_path.h"
11 #include "base/i18n/file_util_icu.h"
12 #include "base/i18n/time_formatting.h"
13 #include "base/json/json_writer.h"
14 #include "base/lazy_instance.h"
15 #include "base/memory/scoped_ptr.h"
16 #include "base/path_service.h"
17 #include "base/prefs/pref_service.h"
18 #include "base/rand_util.h"
19 #include "base/sha1.h"
20 #include "base/stl_util.h"
21 #include "base/strings/string16.h"
22 #include "base/strings/string_number_conversions.h"
23 #include "base/strings/string_util.h"
24 #include "base/strings/utf_string_conversions.h"
25 #include "base/time/time.h"
26 #include "chrome/browser/bookmarks/bookmark_html_writer.h"
27 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
28 #include "chrome/browser/bookmarks/chrome_bookmark_client.h"
29 #include "chrome/browser/bookmarks/chrome_bookmark_client_factory.h"
30 #include "chrome/browser/chrome_notification_types.h"
31 #include "chrome/browser/extensions/api/bookmarks/bookmark_api_constants.h"
32 #include "chrome/browser/extensions/api/bookmarks/bookmark_api_helpers.h"
33 #include "chrome/browser/importer/external_process_importer_host.h"
34 #include "chrome/browser/importer/importer_uma.h"
35 #include "chrome/browser/platform_util.h"
36 #include "chrome/browser/profiles/profile.h"
37 #include "chrome/browser/ui/chrome_select_file_policy.h"
38 #include "chrome/browser/ui/host_desktop.h"
39 #include "chrome/common/chrome_paths.h"
40 #include "chrome/common/extensions/api/bookmarks.h"
41 #include "chrome/common/importer/importer_data_types.h"
42 #include "chrome/common/pref_names.h"
43 #include "components/bookmarks/browser/bookmark_model.h"
44 #include "components/bookmarks/browser/bookmark_utils.h"
45 #include "components/user_prefs/user_prefs.h"
46 #include "content/public/browser/browser_context.h"
47 #include "content/public/browser/notification_service.h"
48 #include "content/public/browser/web_contents.h"
49 #include "extensions/browser/event_router.h"
50 #include "extensions/browser/extension_function_dispatcher.h"
51 #include "extensions/browser/extension_registry.h"
52 #include "extensions/browser/quota_service.h"
53 #include "extensions/common/permissions/permissions_data.h"
54 #include "grit/generated_resources.h"
55 #include "ui/base/l10n/l10n_util.h"
56 
57 #if defined(OS_WIN)
58 #include "ui/aura/remote_window_tree_host_win.h"
59 #endif
60 
61 namespace extensions {
62 
63 namespace keys = bookmark_api_constants;
64 namespace bookmarks = api::bookmarks;
65 
66 using base::TimeDelta;
67 using bookmarks::BookmarkTreeNode;
68 using bookmarks::CreateDetails;
69 using content::BrowserContext;
70 using content::BrowserThread;
71 using content::WebContents;
72 
73 typedef QuotaLimitHeuristic::Bucket Bucket;
74 typedef QuotaLimitHeuristic::Config Config;
75 typedef QuotaLimitHeuristic::BucketList BucketList;
76 typedef QuotaService::TimedLimit TimedLimit;
77 typedef QuotaService::SustainedLimit SustainedLimit;
78 typedef QuotaLimitHeuristic::BucketMapper BucketMapper;
79 
80 namespace {
81 
82 // Generates a default path (including a default filename) that will be
83 // used for pre-populating the "Export Bookmarks" file chooser dialog box.
GetDefaultFilepathForBookmarkExport()84 base::FilePath GetDefaultFilepathForBookmarkExport() {
85   base::Time time = base::Time::Now();
86 
87   // Concatenate a date stamp to the filename.
88 #if defined(OS_POSIX)
89   base::FilePath::StringType filename =
90       l10n_util::GetStringFUTF8(IDS_EXPORT_BOOKMARKS_DEFAULT_FILENAME,
91                                 base::TimeFormatShortDateNumeric(time));
92 #elif defined(OS_WIN)
93   base::FilePath::StringType filename =
94       l10n_util::GetStringFUTF16(IDS_EXPORT_BOOKMARKS_DEFAULT_FILENAME,
95                                  base::TimeFormatShortDateNumeric(time));
96 #endif
97 
98   file_util::ReplaceIllegalCharactersInPath(&filename, '_');
99 
100   base::FilePath default_path;
101   PathService::Get(chrome::DIR_USER_DOCUMENTS, &default_path);
102   return default_path.Append(filename);
103 }
104 
IsEnhancedBookmarksExtensionActive(Profile * profile)105 bool IsEnhancedBookmarksExtensionActive(Profile* profile) {
106   static const char *enhanced_extension_hashes[] = {
107     "D5736E4B5CF695CB93A2FB57E4FDC6E5AFAB6FE2",  // http://crbug.com/312900
108     "D57DE394F36DC1C3220E7604C575D29C51A6C495",  // http://crbug.com/319444
109     "3F65507A3B39259B38C8173C6FFA3D12DF64CCE9"   // http://crbug.com/371562
110   };
111   const ExtensionSet& extensions =
112       ExtensionRegistry::Get(profile)->enabled_extensions();
113   for (ExtensionSet::const_iterator it = extensions.begin();
114        it != extensions.end(); ++it) {
115     const Extension* extension = *it;
116     if (extension->permissions_data()->HasAPIPermission(
117             APIPermission::kBookmarkManagerPrivate)) {
118       std::string hash = base::SHA1HashString(extension->id());
119       hash = base::HexEncode(hash.c_str(), hash.length());
120       for (size_t i = 0; i < arraysize(enhanced_extension_hashes); i++)
121         if (hash == enhanced_extension_hashes[i])
122           return true;
123     }
124   }
125   return false;
126 }
127 
ToBase36(int64 value)128 std::string ToBase36(int64 value) {
129   DCHECK(value >= 0);
130   std::string str;
131   while (value > 0) {
132     int digit = value % 36;
133     value /= 36;
134     str += (digit < 10 ? '0' + digit : 'a' + digit - 10);
135   }
136   std::reverse(str.begin(), str.end());
137   return str;
138 }
139 
140 // Generate a metadata ID based on a the current time and a random number for
141 // enhanced bookmarks, to be assigned pre-sync.
GenerateEnhancedBookmarksID(bool is_folder)142 std::string GenerateEnhancedBookmarksID(bool is_folder) {
143   static const char bookmark_prefix[] = "cc_";
144   static const char folder_prefix[] = "cf_";
145   // Use [0..range_mid) for bookmarks, [range_mid..2*range_mid) for folders.
146   int range_mid = 36*36*36*36 / 2;
147   int rand = base::RandInt(0, range_mid - 1);
148   int64 unix_epoch_time_in_ms =
149       (base::Time::Now() - base::Time::UnixEpoch()).InMilliseconds();
150   return std::string(is_folder ? folder_prefix : bookmark_prefix) +
151       ToBase36(is_folder ? range_mid + rand : rand) +
152       ToBase36(unix_epoch_time_in_ms);
153 }
154 
155 }  // namespace
156 
RunAsync()157 bool BookmarksFunction::RunAsync() {
158   BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile());
159   if (!model->loaded()) {
160     // Bookmarks are not ready yet.  We'll wait.
161     model->AddObserver(this);
162     AddRef();  // Balanced in Loaded().
163     return true;
164   }
165 
166   bool success = RunOnReady();
167   if (success) {
168     content::NotificationService::current()->Notify(
169         chrome::NOTIFICATION_EXTENSION_BOOKMARKS_API_INVOKED,
170         content::Source<const Extension>(GetExtension()),
171         content::Details<const BookmarksFunction>(this));
172   }
173   SendResponse(success);
174   return true;
175 }
176 
GetBookmarkModel()177 BookmarkModel* BookmarksFunction::GetBookmarkModel() {
178   return BookmarkModelFactory::GetForProfile(GetProfile());
179 }
180 
GetChromeBookmarkClient()181 ChromeBookmarkClient* BookmarksFunction::GetChromeBookmarkClient() {
182   return ChromeBookmarkClientFactory::GetForProfile(GetProfile());
183 }
184 
GetBookmarkIdAsInt64(const std::string & id_string,int64 * id)185 bool BookmarksFunction::GetBookmarkIdAsInt64(const std::string& id_string,
186                                              int64* id) {
187   if (base::StringToInt64(id_string, id))
188     return true;
189 
190   error_ = keys::kInvalidIdError;
191   return false;
192 }
193 
GetBookmarkNodeFromId(const std::string & id_string)194 const BookmarkNode* BookmarksFunction::GetBookmarkNodeFromId(
195     const std::string& id_string) {
196   int64 id;
197   if (!GetBookmarkIdAsInt64(id_string, &id))
198     return NULL;
199 
200   const BookmarkNode* node = GetBookmarkNodeByID(
201       BookmarkModelFactory::GetForProfile(GetProfile()), id);
202   if (!node)
203     error_ = keys::kNoNodeError;
204 
205   return node;
206 }
207 
CreateBookmarkNode(BookmarkModel * model,const CreateDetails & details,const BookmarkNode::MetaInfoMap * meta_info)208 const BookmarkNode* BookmarksFunction::CreateBookmarkNode(
209     BookmarkModel* model,
210     const CreateDetails& details,
211     const BookmarkNode::MetaInfoMap* meta_info) {
212   int64 parentId;
213 
214   if (!details.parent_id.get()) {
215     // Optional, default to "other bookmarks".
216     parentId = model->other_node()->id();
217   } else {
218     if (!GetBookmarkIdAsInt64(*details.parent_id, &parentId))
219       return NULL;
220   }
221   const BookmarkNode* parent = GetBookmarkNodeByID(model, parentId);
222   if (!CanBeModified(parent))
223     return NULL;
224 
225   int index;
226   if (!details.index.get()) {  // Optional (defaults to end).
227     index = parent->child_count();
228   } else {
229     index = *details.index;
230     if (index > parent->child_count() || index < 0) {
231       error_ = keys::kInvalidIndexError;
232       return NULL;
233     }
234   }
235 
236   base::string16 title;  // Optional.
237   if (details.title.get())
238     title = base::UTF8ToUTF16(*details.title.get());
239 
240   std::string url_string;  // Optional.
241   if (details.url.get())
242     url_string = *details.url.get();
243 
244   GURL url(url_string);
245   if (!url_string.empty() && !url.is_valid()) {
246     error_ = keys::kInvalidUrlError;
247     return NULL;
248   }
249 
250   const BookmarkNode* node;
251   if (url_string.length())
252     node = model->AddURLWithCreationTimeAndMetaInfo(
253         parent, index, title, url, base::Time::Now(), meta_info);
254   else
255     node = model->AddFolderWithMetaInfo(parent, index, title, meta_info);
256   DCHECK(node);
257   if (!node) {
258     error_ = keys::kNoNodeError;
259     return NULL;
260   }
261 
262   return node;
263 }
264 
EditBookmarksEnabled()265 bool BookmarksFunction::EditBookmarksEnabled() {
266   PrefService* prefs = user_prefs::UserPrefs::Get(GetProfile());
267   if (prefs->GetBoolean(prefs::kEditBookmarksEnabled))
268     return true;
269   error_ = keys::kEditBookmarksDisabled;
270   return false;
271 }
272 
CanBeModified(const BookmarkNode * node)273 bool BookmarksFunction::CanBeModified(const BookmarkNode* node) {
274   if (!node) {
275     error_ = keys::kNoParentError;
276     return false;
277   }
278   if (node->is_root()) {
279     error_ = keys::kModifySpecialError;
280     return false;
281   }
282   ChromeBookmarkClient* client = GetChromeBookmarkClient();
283   if (client->IsDescendantOfManagedNode(node)) {
284     error_ = keys::kModifyManagedError;
285     return false;
286   }
287   return true;
288 }
289 
BookmarkModelChanged()290 void BookmarksFunction::BookmarkModelChanged() {
291 }
292 
BookmarkModelLoaded(BookmarkModel * model,bool ids_reassigned)293 void BookmarksFunction::BookmarkModelLoaded(BookmarkModel* model,
294                                             bool ids_reassigned) {
295   model->RemoveObserver(this);
296   RunOnReady();
297   Release();  // Balanced in RunOnReady().
298 }
299 
BookmarkEventRouter(Profile * profile)300 BookmarkEventRouter::BookmarkEventRouter(Profile* profile)
301     : browser_context_(profile),
302       model_(BookmarkModelFactory::GetForProfile(profile)),
303       client_(ChromeBookmarkClientFactory::GetForProfile(profile)) {
304   model_->AddObserver(this);
305 }
306 
~BookmarkEventRouter()307 BookmarkEventRouter::~BookmarkEventRouter() {
308   if (model_) {
309     model_->RemoveObserver(this);
310   }
311 }
312 
DispatchEvent(const std::string & event_name,scoped_ptr<base::ListValue> event_args)313 void BookmarkEventRouter::DispatchEvent(
314     const std::string& event_name,
315     scoped_ptr<base::ListValue> event_args) {
316   EventRouter* event_router = EventRouter::Get(browser_context_);
317   if (event_router) {
318     event_router->BroadcastEvent(
319         make_scoped_ptr(new extensions::Event(event_name, event_args.Pass())));
320   }
321 }
322 
BookmarkModelLoaded(BookmarkModel * model,bool ids_reassigned)323 void BookmarkEventRouter::BookmarkModelLoaded(BookmarkModel* model,
324                                               bool ids_reassigned) {
325   // TODO(erikkay): Perhaps we should send this event down to the extension
326   // so they know when it's safe to use the API?
327 }
328 
BookmarkModelBeingDeleted(BookmarkModel * model)329 void BookmarkEventRouter::BookmarkModelBeingDeleted(BookmarkModel* model) {
330   model_ = NULL;
331 }
332 
BookmarkNodeMoved(BookmarkModel * model,const BookmarkNode * old_parent,int old_index,const BookmarkNode * new_parent,int new_index)333 void BookmarkEventRouter::BookmarkNodeMoved(BookmarkModel* model,
334                                             const BookmarkNode* old_parent,
335                                             int old_index,
336                                             const BookmarkNode* new_parent,
337                                             int new_index) {
338   const BookmarkNode* node = new_parent->GetChild(new_index);
339   bookmarks::OnMoved::MoveInfo move_info;
340   move_info.parent_id = base::Int64ToString(new_parent->id());
341   move_info.index = new_index;
342   move_info.old_parent_id = base::Int64ToString(old_parent->id());
343   move_info.old_index = old_index;
344 
345   DispatchEvent(
346       bookmarks::OnMoved::kEventName,
347       bookmarks::OnMoved::Create(base::Int64ToString(node->id()), move_info));
348 }
349 
OnWillAddBookmarkNode(BookmarkModel * model,BookmarkNode * node)350 void BookmarkEventRouter::OnWillAddBookmarkNode(BookmarkModel* model,
351                                                 BookmarkNode* node) {
352   // TODO(wittman): Remove this once extension hooks are in place to allow the
353   // enhanced bookmarks extension to manage all bookmark creation code
354   // paths. See http://crbug.com/383557.
355   if (IsEnhancedBookmarksExtensionActive(Profile::FromBrowserContext(
356           browser_context_))) {
357     static const char key[] = "stars.id";
358     std::string value;
359     if (!node->GetMetaInfo(key, &value))
360       node->SetMetaInfo(key, GenerateEnhancedBookmarksID(node->is_folder()));
361   }
362 }
363 
BookmarkNodeAdded(BookmarkModel * model,const BookmarkNode * parent,int index)364 void BookmarkEventRouter::BookmarkNodeAdded(BookmarkModel* model,
365                                             const BookmarkNode* parent,
366                                             int index) {
367   const BookmarkNode* node = parent->GetChild(index);
368   scoped_ptr<BookmarkTreeNode> tree_node(
369       bookmark_api_helpers::GetBookmarkTreeNode(client_, node, false, false));
370   DispatchEvent(bookmarks::OnCreated::kEventName,
371                 bookmarks::OnCreated::Create(base::Int64ToString(node->id()),
372                                              *tree_node));
373 }
374 
BookmarkNodeRemoved(BookmarkModel * model,const BookmarkNode * parent,int index,const BookmarkNode * node,const std::set<GURL> & removed_urls)375 void BookmarkEventRouter::BookmarkNodeRemoved(
376     BookmarkModel* model,
377     const BookmarkNode* parent,
378     int index,
379     const BookmarkNode* node,
380     const std::set<GURL>& removed_urls) {
381   bookmarks::OnRemoved::RemoveInfo remove_info;
382   remove_info.parent_id = base::Int64ToString(parent->id());
383   remove_info.index = index;
384 
385   DispatchEvent(bookmarks::OnRemoved::kEventName,
386                 bookmarks::OnRemoved::Create(base::Int64ToString(node->id()),
387                                              remove_info));
388 }
389 
BookmarkAllUserNodesRemoved(BookmarkModel * model,const std::set<GURL> & removed_urls)390 void BookmarkEventRouter::BookmarkAllUserNodesRemoved(
391     BookmarkModel* model,
392     const std::set<GURL>& removed_urls) {
393   NOTREACHED();
394   // TODO(shashishekhar) Currently this notification is only used on Android,
395   // which does not support extensions. If Desktop needs to support this, add
396   // a new event to the extensions api.
397 }
398 
BookmarkNodeChanged(BookmarkModel * model,const BookmarkNode * node)399 void BookmarkEventRouter::BookmarkNodeChanged(BookmarkModel* model,
400                                               const BookmarkNode* node) {
401   // TODO(erikkay) The only three things that BookmarkModel sends this
402   // notification for are title, url and favicon.  Since we're currently
403   // ignoring favicon and since the notification doesn't say which one anyway,
404   // for now we only include title and url.  The ideal thing would be to change
405   // BookmarkModel to indicate what changed.
406   bookmarks::OnChanged::ChangeInfo change_info;
407   change_info.title = base::UTF16ToUTF8(node->GetTitle());
408   if (node->is_url())
409     change_info.url.reset(new std::string(node->url().spec()));
410 
411   DispatchEvent(bookmarks::OnChanged::kEventName,
412                 bookmarks::OnChanged::Create(base::Int64ToString(node->id()),
413                                              change_info));
414 }
415 
BookmarkNodeFaviconChanged(BookmarkModel * model,const BookmarkNode * node)416 void BookmarkEventRouter::BookmarkNodeFaviconChanged(BookmarkModel* model,
417                                                      const BookmarkNode* node) {
418   // TODO(erikkay) anything we should do here?
419 }
420 
BookmarkNodeChildrenReordered(BookmarkModel * model,const BookmarkNode * node)421 void BookmarkEventRouter::BookmarkNodeChildrenReordered(
422     BookmarkModel* model,
423     const BookmarkNode* node) {
424   bookmarks::OnChildrenReordered::ReorderInfo reorder_info;
425   int childCount = node->child_count();
426   for (int i = 0; i < childCount; ++i) {
427     const BookmarkNode* child = node->GetChild(i);
428     reorder_info.child_ids.push_back(base::Int64ToString(child->id()));
429   }
430 
431   DispatchEvent(bookmarks::OnChildrenReordered::kEventName,
432                 bookmarks::OnChildrenReordered::Create(
433                     base::Int64ToString(node->id()), reorder_info));
434 }
435 
ExtensiveBookmarkChangesBeginning(BookmarkModel * model)436 void BookmarkEventRouter::ExtensiveBookmarkChangesBeginning(
437     BookmarkModel* model) {
438   DispatchEvent(bookmarks::OnImportBegan::kEventName,
439                 bookmarks::OnImportBegan::Create());
440 }
441 
ExtensiveBookmarkChangesEnded(BookmarkModel * model)442 void BookmarkEventRouter::ExtensiveBookmarkChangesEnded(BookmarkModel* model) {
443   DispatchEvent(bookmarks::OnImportEnded::kEventName,
444                 bookmarks::OnImportEnded::Create());
445 }
446 
BookmarksAPI(BrowserContext * context)447 BookmarksAPI::BookmarksAPI(BrowserContext* context)
448     : browser_context_(context) {
449   EventRouter* event_router = EventRouter::Get(browser_context_);
450   event_router->RegisterObserver(this, bookmarks::OnCreated::kEventName);
451   event_router->RegisterObserver(this, bookmarks::OnRemoved::kEventName);
452   event_router->RegisterObserver(this, bookmarks::OnChanged::kEventName);
453   event_router->RegisterObserver(this, bookmarks::OnMoved::kEventName);
454   event_router->RegisterObserver(this,
455                                  bookmarks::OnChildrenReordered::kEventName);
456   event_router->RegisterObserver(this, bookmarks::OnImportBegan::kEventName);
457   event_router->RegisterObserver(this, bookmarks::OnImportEnded::kEventName);
458 }
459 
~BookmarksAPI()460 BookmarksAPI::~BookmarksAPI() {
461 }
462 
Shutdown()463 void BookmarksAPI::Shutdown() {
464   EventRouter::Get(browser_context_)->UnregisterObserver(this);
465 }
466 
467 static base::LazyInstance<BrowserContextKeyedAPIFactory<BookmarksAPI> >
468     g_factory = LAZY_INSTANCE_INITIALIZER;
469 
470 // static
471 BrowserContextKeyedAPIFactory<BookmarksAPI>*
GetFactoryInstance()472 BookmarksAPI::GetFactoryInstance() {
473   return g_factory.Pointer();
474 }
475 
OnListenerAdded(const EventListenerInfo & details)476 void BookmarksAPI::OnListenerAdded(const EventListenerInfo& details) {
477   bookmark_event_router_.reset(
478       new BookmarkEventRouter(Profile::FromBrowserContext(browser_context_)));
479   EventRouter::Get(browser_context_)->UnregisterObserver(this);
480 }
481 
RunOnReady()482 bool BookmarksGetFunction::RunOnReady() {
483   scoped_ptr<bookmarks::Get::Params> params(
484       bookmarks::Get::Params::Create(*args_));
485   EXTENSION_FUNCTION_VALIDATE(params.get());
486 
487   std::vector<linked_ptr<BookmarkTreeNode> > nodes;
488   ChromeBookmarkClient* client = GetChromeBookmarkClient();
489   if (params->id_or_id_list.as_strings) {
490     std::vector<std::string>& ids = *params->id_or_id_list.as_strings;
491     size_t count = ids.size();
492     EXTENSION_FUNCTION_VALIDATE(count > 0);
493     for (size_t i = 0; i < count; ++i) {
494       const BookmarkNode* node = GetBookmarkNodeFromId(ids[i]);
495       if (!node)
496         return false;
497       bookmark_api_helpers::AddNode(client, node, &nodes, false);
498     }
499   } else {
500     const BookmarkNode* node =
501         GetBookmarkNodeFromId(*params->id_or_id_list.as_string);
502     if (!node)
503       return false;
504     bookmark_api_helpers::AddNode(client, node, &nodes, false);
505   }
506 
507   results_ = bookmarks::Get::Results::Create(nodes);
508   return true;
509 }
510 
RunOnReady()511 bool BookmarksGetChildrenFunction::RunOnReady() {
512   scoped_ptr<bookmarks::GetChildren::Params> params(
513       bookmarks::GetChildren::Params::Create(*args_));
514   EXTENSION_FUNCTION_VALIDATE(params.get());
515 
516   const BookmarkNode* node = GetBookmarkNodeFromId(params->id);
517   if (!node)
518     return false;
519 
520   std::vector<linked_ptr<BookmarkTreeNode> > nodes;
521   int child_count = node->child_count();
522   for (int i = 0; i < child_count; ++i) {
523     const BookmarkNode* child = node->GetChild(i);
524     bookmark_api_helpers::AddNode(
525         GetChromeBookmarkClient(), child, &nodes, false);
526   }
527 
528   results_ = bookmarks::GetChildren::Results::Create(nodes);
529   return true;
530 }
531 
RunOnReady()532 bool BookmarksGetRecentFunction::RunOnReady() {
533   scoped_ptr<bookmarks::GetRecent::Params> params(
534       bookmarks::GetRecent::Params::Create(*args_));
535   EXTENSION_FUNCTION_VALIDATE(params.get());
536   if (params->number_of_items < 1)
537     return false;
538 
539   std::vector<const BookmarkNode*> nodes;
540   bookmark_utils::GetMostRecentlyAddedEntries(
541       BookmarkModelFactory::GetForProfile(GetProfile()),
542       params->number_of_items,
543       &nodes);
544 
545   std::vector<linked_ptr<BookmarkTreeNode> > tree_nodes;
546   std::vector<const BookmarkNode*>::iterator i = nodes.begin();
547   for (; i != nodes.end(); ++i) {
548     const BookmarkNode* node = *i;
549     bookmark_api_helpers::AddNode(
550         GetChromeBookmarkClient(), node, &tree_nodes, false);
551   }
552 
553   results_ = bookmarks::GetRecent::Results::Create(tree_nodes);
554   return true;
555 }
556 
RunOnReady()557 bool BookmarksGetTreeFunction::RunOnReady() {
558   std::vector<linked_ptr<BookmarkTreeNode> > nodes;
559   const BookmarkNode* node =
560       BookmarkModelFactory::GetForProfile(GetProfile())->root_node();
561   bookmark_api_helpers::AddNode(GetChromeBookmarkClient(), node, &nodes, true);
562   results_ = bookmarks::GetTree::Results::Create(nodes);
563   return true;
564 }
565 
RunOnReady()566 bool BookmarksGetSubTreeFunction::RunOnReady() {
567   scoped_ptr<bookmarks::GetSubTree::Params> params(
568       bookmarks::GetSubTree::Params::Create(*args_));
569   EXTENSION_FUNCTION_VALIDATE(params.get());
570 
571   const BookmarkNode* node = GetBookmarkNodeFromId(params->id);
572   if (!node)
573     return false;
574 
575   std::vector<linked_ptr<BookmarkTreeNode> > nodes;
576   bookmark_api_helpers::AddNode(GetChromeBookmarkClient(), node, &nodes, true);
577   results_ = bookmarks::GetSubTree::Results::Create(nodes);
578   return true;
579 }
580 
RunOnReady()581 bool BookmarksSearchFunction::RunOnReady() {
582   scoped_ptr<bookmarks::Search::Params> params(
583       bookmarks::Search::Params::Create(*args_));
584   EXTENSION_FUNCTION_VALIDATE(params.get());
585 
586   PrefService* prefs = user_prefs::UserPrefs::Get(GetProfile());
587   std::string lang = prefs->GetString(prefs::kAcceptLanguages);
588   std::vector<const BookmarkNode*> nodes;
589   if (params->query.as_string) {
590     bookmark_utils::QueryFields query;
591     query.word_phrase_query.reset(
592         new base::string16(base::UTF8ToUTF16(*params->query.as_string)));
593     bookmark_utils::GetBookmarksMatchingProperties(
594         BookmarkModelFactory::GetForProfile(GetProfile()),
595         query,
596         std::numeric_limits<int>::max(),
597         lang,
598         &nodes);
599   } else {
600     DCHECK(params->query.as_object);
601     const bookmarks::Search::Params::Query::Object& object =
602         *params->query.as_object;
603     bookmark_utils::QueryFields query;
604     if (object.query) {
605       query.word_phrase_query.reset(
606           new base::string16(base::UTF8ToUTF16(*object.query)));
607     }
608     if (object.url)
609       query.url.reset(new base::string16(base::UTF8ToUTF16(*object.url)));
610     if (object.title)
611       query.title.reset(new base::string16(base::UTF8ToUTF16(*object.title)));
612     bookmark_utils::GetBookmarksMatchingProperties(
613         BookmarkModelFactory::GetForProfile(GetProfile()),
614         query,
615         std::numeric_limits<int>::max(),
616         lang,
617         &nodes);
618   }
619 
620   std::vector<linked_ptr<BookmarkTreeNode> > tree_nodes;
621   ChromeBookmarkClient* client = GetChromeBookmarkClient();
622   for (std::vector<const BookmarkNode*>::iterator node_iter = nodes.begin();
623        node_iter != nodes.end(); ++node_iter) {
624     bookmark_api_helpers::AddNode(client, *node_iter, &tree_nodes, false);
625   }
626 
627   results_ = bookmarks::Search::Results::Create(tree_nodes);
628   return true;
629 }
630 
631 // static
ExtractIds(const base::ListValue * args,std::list<int64> * ids,bool * invalid_id)632 bool BookmarksRemoveFunction::ExtractIds(const base::ListValue* args,
633                                          std::list<int64>* ids,
634                                          bool* invalid_id) {
635   std::string id_string;
636   if (!args->GetString(0, &id_string))
637     return false;
638   int64 id;
639   if (base::StringToInt64(id_string, &id))
640     ids->push_back(id);
641   else
642     *invalid_id = true;
643   return true;
644 }
645 
RunOnReady()646 bool BookmarksRemoveFunction::RunOnReady() {
647   if (!EditBookmarksEnabled())
648     return false;
649 
650   scoped_ptr<bookmarks::Remove::Params> params(
651       bookmarks::Remove::Params::Create(*args_));
652   EXTENSION_FUNCTION_VALIDATE(params.get());
653 
654   int64 id;
655   if (!GetBookmarkIdAsInt64(params->id, &id))
656     return false;
657 
658   bool recursive = false;
659   if (name() == BookmarksRemoveTreeFunction::function_name())
660     recursive = true;
661 
662   BookmarkModel* model = GetBookmarkModel();
663   ChromeBookmarkClient* client = GetChromeBookmarkClient();
664   if (!bookmark_api_helpers::RemoveNode(model, client, id, recursive, &error_))
665     return false;
666 
667   return true;
668 }
669 
RunOnReady()670 bool BookmarksCreateFunction::RunOnReady() {
671   if (!EditBookmarksEnabled())
672     return false;
673 
674   scoped_ptr<bookmarks::Create::Params> params(
675       bookmarks::Create::Params::Create(*args_));
676   EXTENSION_FUNCTION_VALIDATE(params.get());
677 
678   BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile());
679   const BookmarkNode* node = CreateBookmarkNode(model, params->bookmark, NULL);
680   if (!node)
681     return false;
682 
683   scoped_ptr<BookmarkTreeNode> ret(bookmark_api_helpers::GetBookmarkTreeNode(
684       GetChromeBookmarkClient(), node, false, false));
685   results_ = bookmarks::Create::Results::Create(*ret);
686 
687   return true;
688 }
689 
690 // static
ExtractIds(const base::ListValue * args,std::list<int64> * ids,bool * invalid_id)691 bool BookmarksMoveFunction::ExtractIds(const base::ListValue* args,
692                                        std::list<int64>* ids,
693                                        bool* invalid_id) {
694   // For now, Move accepts ID parameters in the same way as an Update.
695   return BookmarksUpdateFunction::ExtractIds(args, ids, invalid_id);
696 }
697 
RunOnReady()698 bool BookmarksMoveFunction::RunOnReady() {
699   if (!EditBookmarksEnabled())
700     return false;
701 
702   scoped_ptr<bookmarks::Move::Params> params(
703       bookmarks::Move::Params::Create(*args_));
704   EXTENSION_FUNCTION_VALIDATE(params.get());
705 
706   const BookmarkNode* node = GetBookmarkNodeFromId(params->id);
707   if (!node)
708     return false;
709 
710   BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile());
711   if (model->is_permanent_node(node)) {
712     error_ = keys::kModifySpecialError;
713     return false;
714   }
715 
716   const BookmarkNode* parent = NULL;
717   if (!params->destination.parent_id.get()) {
718     // Optional, defaults to current parent.
719     parent = node->parent();
720   } else {
721     int64 parentId;
722     if (!GetBookmarkIdAsInt64(*params->destination.parent_id, &parentId))
723       return false;
724 
725     parent = GetBookmarkNodeByID(model, parentId);
726   }
727   if (!CanBeModified(parent) || !CanBeModified(node))
728     return false;
729 
730   int index;
731   if (params->destination.index.get()) {  // Optional (defaults to end).
732     index = *params->destination.index;
733     if (index > parent->child_count() || index < 0) {
734       error_ = keys::kInvalidIndexError;
735       return false;
736     }
737   } else {
738     index = parent->child_count();
739   }
740 
741   model->Move(node, parent, index);
742 
743   scoped_ptr<BookmarkTreeNode> tree_node(
744       bookmark_api_helpers::GetBookmarkTreeNode(
745           GetChromeBookmarkClient(), node, false, false));
746   results_ = bookmarks::Move::Results::Create(*tree_node);
747 
748   return true;
749 }
750 
751 // static
ExtractIds(const base::ListValue * args,std::list<int64> * ids,bool * invalid_id)752 bool BookmarksUpdateFunction::ExtractIds(const base::ListValue* args,
753                                          std::list<int64>* ids,
754                                          bool* invalid_id) {
755   // For now, Update accepts ID parameters in the same way as an Remove.
756   return BookmarksRemoveFunction::ExtractIds(args, ids, invalid_id);
757 }
758 
RunOnReady()759 bool BookmarksUpdateFunction::RunOnReady() {
760   if (!EditBookmarksEnabled())
761     return false;
762 
763   scoped_ptr<bookmarks::Update::Params> params(
764       bookmarks::Update::Params::Create(*args_));
765   EXTENSION_FUNCTION_VALIDATE(params.get());
766 
767   // Optional but we need to distinguish non present from an empty title.
768   base::string16 title;
769   bool has_title = false;
770   if (params->changes.title.get()) {
771     title = base::UTF8ToUTF16(*params->changes.title);
772     has_title = true;
773   }
774 
775   // Optional.
776   std::string url_string;
777   if (params->changes.url.get())
778     url_string = *params->changes.url;
779   GURL url(url_string);
780   if (!url_string.empty() && !url.is_valid()) {
781     error_ = keys::kInvalidUrlError;
782     return false;
783   }
784 
785   const BookmarkNode* node = GetBookmarkNodeFromId(params->id);
786   if (!CanBeModified(node))
787     return false;
788 
789   BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile());
790   if (model->is_permanent_node(node)) {
791     error_ = keys::kModifySpecialError;
792     return false;
793   }
794   if (has_title)
795     model->SetTitle(node, title);
796   if (!url.is_empty())
797     model->SetURL(node, url);
798 
799   scoped_ptr<BookmarkTreeNode> tree_node(
800       bookmark_api_helpers::GetBookmarkTreeNode(
801           GetChromeBookmarkClient(), node, false, false));
802   results_ = bookmarks::Update::Results::Create(*tree_node);
803   return true;
804 }
805 
806 // Mapper superclass for BookmarkFunctions.
807 template <typename BucketIdType>
808 class BookmarkBucketMapper : public BucketMapper {
809  public:
~BookmarkBucketMapper()810   virtual ~BookmarkBucketMapper() { STLDeleteValues(&buckets_); }
811  protected:
GetBucket(const BucketIdType & id)812   Bucket* GetBucket(const BucketIdType& id) {
813     Bucket* b = buckets_[id];
814     if (b == NULL) {
815       b = new Bucket();
816       buckets_[id] = b;
817     }
818     return b;
819   }
820  private:
821   std::map<BucketIdType, Bucket*> buckets_;
822 };
823 
824 // Mapper for 'bookmarks.create'.  Maps "same input to bookmarks.create" to a
825 // unique bucket.
826 class CreateBookmarkBucketMapper : public BookmarkBucketMapper<std::string> {
827  public:
CreateBookmarkBucketMapper(BrowserContext * context)828   explicit CreateBookmarkBucketMapper(BrowserContext* context)
829       : browser_context_(context) {}
830   // TODO(tim): This should share code with BookmarksCreateFunction::RunOnReady,
831   // but I can't figure out a good way to do that with all the macros.
GetBucketsForArgs(const base::ListValue * args,BucketList * buckets)832   virtual void GetBucketsForArgs(const base::ListValue* args,
833                                  BucketList* buckets) OVERRIDE {
834     const base::DictionaryValue* json;
835     if (!args->GetDictionary(0, &json))
836       return;
837 
838     std::string parent_id;
839     if (json->HasKey(keys::kParentIdKey)) {
840       if (!json->GetString(keys::kParentIdKey, &parent_id))
841         return;
842     }
843     BookmarkModel* model = BookmarkModelFactory::GetForProfile(
844         Profile::FromBrowserContext(browser_context_));
845 
846     int64 parent_id_int64;
847     base::StringToInt64(parent_id, &parent_id_int64);
848     const BookmarkNode* parent = GetBookmarkNodeByID(model, parent_id_int64);
849     if (!parent)
850       return;
851 
852     std::string bucket_id = base::UTF16ToUTF8(parent->GetTitle());
853     std::string title;
854     json->GetString(keys::kTitleKey, &title);
855     std::string url_string;
856     json->GetString(keys::kUrlKey, &url_string);
857 
858     bucket_id += title;
859     bucket_id += url_string;
860     // 20 bytes (SHA1 hash length) is very likely less than most of the
861     // |bucket_id| strings we construct here, so we hash it to save space.
862     buckets->push_back(GetBucket(base::SHA1HashString(bucket_id)));
863   }
864  private:
865   BrowserContext* browser_context_;
866 };
867 
868 // Mapper for 'bookmarks.remove'.
869 class RemoveBookmarksBucketMapper : public BookmarkBucketMapper<std::string> {
870  public:
RemoveBookmarksBucketMapper(BrowserContext * context)871   explicit RemoveBookmarksBucketMapper(BrowserContext* context)
872       : browser_context_(context) {}
GetBucketsForArgs(const base::ListValue * args,BucketList * buckets)873   virtual void GetBucketsForArgs(const base::ListValue* args,
874                                  BucketList* buckets) OVERRIDE {
875     typedef std::list<int64> IdList;
876     IdList ids;
877     bool invalid_id = false;
878     if (!BookmarksRemoveFunction::ExtractIds(args, &ids, &invalid_id) ||
879         invalid_id) {
880       return;
881     }
882 
883     for (IdList::iterator it = ids.begin(); it != ids.end(); ++it) {
884       BookmarkModel* model = BookmarkModelFactory::GetForProfile(
885           Profile::FromBrowserContext(browser_context_));
886       const BookmarkNode* node = GetBookmarkNodeByID(model, *it);
887       if (!node || node->is_root())
888         return;
889 
890       std::string bucket_id;
891       bucket_id += base::UTF16ToUTF8(node->parent()->GetTitle());
892       bucket_id += base::UTF16ToUTF8(node->GetTitle());
893       bucket_id += node->url().spec();
894       buckets->push_back(GetBucket(base::SHA1HashString(bucket_id)));
895     }
896   }
897  private:
898   BrowserContext* browser_context_;
899 };
900 
901 // Mapper for any bookmark function accepting bookmark IDs as parameters, where
902 // a distinct ID corresponds to a single item in terms of quota limiting.  This
903 // is inappropriate for bookmarks.remove, for example, since repeated removals
904 // of the same item will actually have a different ID each time.
905 template <class FunctionType>
906 class BookmarkIdMapper : public BookmarkBucketMapper<int64> {
907  public:
908   typedef std::list<int64> IdList;
GetBucketsForArgs(const base::ListValue * args,BucketList * buckets)909   virtual void GetBucketsForArgs(const base::ListValue* args,
910                                  BucketList* buckets) {
911     IdList ids;
912     bool invalid_id = false;
913     if (!FunctionType::ExtractIds(args, &ids, &invalid_id) || invalid_id)
914       return;
915     for (IdList::iterator it = ids.begin(); it != ids.end(); ++it)
916       buckets->push_back(GetBucket(*it));
917   }
918 };
919 
920 // Builds heuristics for all BookmarkFunctions using specialized BucketMappers.
921 class BookmarksQuotaLimitFactory {
922  public:
923   // For id-based bookmark functions.
924   template <class FunctionType>
Build(QuotaLimitHeuristics * heuristics)925   static void Build(QuotaLimitHeuristics* heuristics) {
926     BuildWithMappers(heuristics, new BookmarkIdMapper<FunctionType>(),
927                                  new BookmarkIdMapper<FunctionType>());
928   }
929 
930   // For bookmarks.create.
BuildForCreate(QuotaLimitHeuristics * heuristics,BrowserContext * context)931   static void BuildForCreate(QuotaLimitHeuristics* heuristics,
932                              BrowserContext* context) {
933     BuildWithMappers(heuristics,
934                      new CreateBookmarkBucketMapper(context),
935                      new CreateBookmarkBucketMapper(context));
936   }
937 
938   // For bookmarks.remove.
BuildForRemove(QuotaLimitHeuristics * heuristics,BrowserContext * context)939   static void BuildForRemove(QuotaLimitHeuristics* heuristics,
940                              BrowserContext* context) {
941     BuildWithMappers(heuristics,
942                      new RemoveBookmarksBucketMapper(context),
943                      new RemoveBookmarksBucketMapper(context));
944   }
945 
946  private:
BuildWithMappers(QuotaLimitHeuristics * heuristics,BucketMapper * short_mapper,BucketMapper * long_mapper)947   static void BuildWithMappers(QuotaLimitHeuristics* heuristics,
948       BucketMapper* short_mapper, BucketMapper* long_mapper) {
949     const Config kSustainedLimitConfig = {
950       // See bookmarks.json for current value.
951       bookmarks::MAX_SUSTAINED_WRITE_OPERATIONS_PER_MINUTE,
952       TimeDelta::FromMinutes(1)
953     };
954     heuristics->push_back(new SustainedLimit(
955         TimeDelta::FromMinutes(10),
956         kSustainedLimitConfig,
957         short_mapper,
958         "MAX_SUSTAINED_WRITE_OPERATIONS_PER_MINUTE"));
959 
960     const Config kTimedLimitConfig = {
961       // See bookmarks.json for current value.
962       bookmarks::MAX_WRITE_OPERATIONS_PER_HOUR,
963       TimeDelta::FromHours(1)
964     };
965     heuristics->push_back(new TimedLimit(
966         kTimedLimitConfig,
967         long_mapper,
968         "MAX_WRITE_OPERATIONS_PER_HOUR"));
969   }
970 
971   DISALLOW_IMPLICIT_CONSTRUCTORS(BookmarksQuotaLimitFactory);
972 };
973 
974 // And finally, building the individual heuristics for each function.
GetQuotaLimitHeuristics(QuotaLimitHeuristics * heuristics) const975 void BookmarksRemoveFunction::GetQuotaLimitHeuristics(
976     QuotaLimitHeuristics* heuristics) const {
977   BookmarksQuotaLimitFactory::BuildForRemove(heuristics, GetProfile());
978 }
979 
GetQuotaLimitHeuristics(QuotaLimitHeuristics * heuristics) const980 void BookmarksMoveFunction::GetQuotaLimitHeuristics(
981     QuotaLimitHeuristics* heuristics) const {
982   BookmarksQuotaLimitFactory::Build<BookmarksMoveFunction>(heuristics);
983 }
984 
GetQuotaLimitHeuristics(QuotaLimitHeuristics * heuristics) const985 void BookmarksUpdateFunction::GetQuotaLimitHeuristics(
986     QuotaLimitHeuristics* heuristics) const {
987   BookmarksQuotaLimitFactory::Build<BookmarksUpdateFunction>(heuristics);
988 }
989 
GetQuotaLimitHeuristics(QuotaLimitHeuristics * heuristics) const990 void BookmarksCreateFunction::GetQuotaLimitHeuristics(
991     QuotaLimitHeuristics* heuristics) const {
992   BookmarksQuotaLimitFactory::BuildForCreate(heuristics, GetProfile());
993 }
994 
BookmarksIOFunction()995 BookmarksIOFunction::BookmarksIOFunction() {}
996 
~BookmarksIOFunction()997 BookmarksIOFunction::~BookmarksIOFunction() {
998   // There may be pending file dialogs, we need to tell them that we've gone
999   // away so they don't try and call back to us.
1000   if (select_file_dialog_.get())
1001     select_file_dialog_->ListenerDestroyed();
1002 }
1003 
SelectFile(ui::SelectFileDialog::Type type)1004 void BookmarksIOFunction::SelectFile(ui::SelectFileDialog::Type type) {
1005   // GetDefaultFilepathForBookmarkExport() might have to touch the filesystem
1006   // (stat or access, for example), so this requires a thread with IO allowed.
1007   if (!BrowserThread::CurrentlyOn(BrowserThread::FILE)) {
1008     BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
1009         base::Bind(&BookmarksIOFunction::SelectFile, this, type));
1010     return;
1011   }
1012 
1013   // Pre-populating the filename field in case this is a SELECT_SAVEAS_FILE
1014   // dialog. If not, there is no filename field in the dialog box.
1015   base::FilePath default_path;
1016   if (type == ui::SelectFileDialog::SELECT_SAVEAS_FILE)
1017     default_path = GetDefaultFilepathForBookmarkExport();
1018   else
1019     DCHECK(type == ui::SelectFileDialog::SELECT_OPEN_FILE);
1020 
1021   // After getting the |default_path|, ask the UI to display the file dialog.
1022   BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
1023       base::Bind(&BookmarksIOFunction::ShowSelectFileDialog, this,
1024                  type, default_path));
1025 }
1026 
ShowSelectFileDialog(ui::SelectFileDialog::Type type,const base::FilePath & default_path)1027 void BookmarksIOFunction::ShowSelectFileDialog(
1028     ui::SelectFileDialog::Type type,
1029     const base::FilePath& default_path) {
1030   if (!dispatcher())
1031     return;  // Extension was unloaded.
1032 
1033   // Balanced in one of the three callbacks of SelectFileDialog:
1034   // either FileSelectionCanceled, MultiFilesSelected, or FileSelected
1035   AddRef();
1036 
1037   WebContents* web_contents = dispatcher()->delegate()->
1038       GetAssociatedWebContents();
1039 
1040   select_file_dialog_ = ui::SelectFileDialog::Create(
1041       this, new ChromeSelectFilePolicy(web_contents));
1042   ui::SelectFileDialog::FileTypeInfo file_type_info;
1043   file_type_info.extensions.resize(1);
1044   file_type_info.extensions[0].push_back(FILE_PATH_LITERAL("html"));
1045   gfx::NativeWindow owning_window = web_contents ?
1046       platform_util::GetTopLevel(web_contents->GetNativeView())
1047           : NULL;
1048 #if defined(OS_WIN)
1049   if (!owning_window &&
1050       chrome::GetActiveDesktop() == chrome::HOST_DESKTOP_TYPE_ASH)
1051     owning_window = aura::RemoteWindowTreeHostWin::Instance()->GetAshWindow();
1052 #endif
1053   // |web_contents| can be NULL (for background pages), which is fine. In such
1054   // a case if file-selection dialogs are forbidden by policy, we will not
1055   // show an InfoBar, which is better than letting one appear out of the blue.
1056   select_file_dialog_->SelectFile(type,
1057                                   base::string16(),
1058                                   default_path,
1059                                   &file_type_info,
1060                                   0,
1061                                   base::FilePath::StringType(),
1062                                   owning_window,
1063                                   NULL);
1064 }
1065 
FileSelectionCanceled(void * params)1066 void BookmarksIOFunction::FileSelectionCanceled(void* params) {
1067   Release();  // Balanced in BookmarksIOFunction::SelectFile()
1068 }
1069 
MultiFilesSelected(const std::vector<base::FilePath> & files,void * params)1070 void BookmarksIOFunction::MultiFilesSelected(
1071     const std::vector<base::FilePath>& files, void* params) {
1072   Release();  // Balanced in BookmarsIOFunction::SelectFile()
1073   NOTREACHED() << "Should not be able to select multiple files";
1074 }
1075 
RunOnReady()1076 bool BookmarksImportFunction::RunOnReady() {
1077   if (!EditBookmarksEnabled())
1078     return false;
1079   SelectFile(ui::SelectFileDialog::SELECT_OPEN_FILE);
1080   return true;
1081 }
1082 
FileSelected(const base::FilePath & path,int index,void * params)1083 void BookmarksImportFunction::FileSelected(const base::FilePath& path,
1084                                            int index,
1085                                            void* params) {
1086 #if !defined(OS_ANDROID)
1087   // Android does not have support for the standard importers.
1088   // TODO(jgreenwald): remove ifdef once extensions are no longer built on
1089   // Android.
1090   // Deletes itself.
1091   ExternalProcessImporterHost* importer_host = new ExternalProcessImporterHost;
1092   importer::SourceProfile source_profile;
1093   source_profile.importer_type = importer::TYPE_BOOKMARKS_FILE;
1094   source_profile.source_path = path;
1095   importer_host->StartImportSettings(source_profile,
1096                                      GetProfile(),
1097                                      importer::FAVORITES,
1098                                      new ProfileWriter(GetProfile()));
1099 
1100   importer::LogImporterUseToMetrics("BookmarksAPI",
1101                                     importer::TYPE_BOOKMARKS_FILE);
1102 #endif
1103   Release();  // Balanced in BookmarksIOFunction::SelectFile()
1104 }
1105 
RunOnReady()1106 bool BookmarksExportFunction::RunOnReady() {
1107   SelectFile(ui::SelectFileDialog::SELECT_SAVEAS_FILE);
1108   return true;
1109 }
1110 
FileSelected(const base::FilePath & path,int index,void * params)1111 void BookmarksExportFunction::FileSelected(const base::FilePath& path,
1112                                            int index,
1113                                            void* params) {
1114 #if !defined(OS_ANDROID)
1115   // Android does not have support for the standard exporter.
1116   // TODO(jgreenwald): remove ifdef once extensions are no longer built on
1117   // Android.
1118   bookmark_html_writer::WriteBookmarks(GetProfile(), path, NULL);
1119 #endif
1120   Release();  // Balanced in BookmarksIOFunction::SelectFile()
1121 }
1122 
1123 }  // namespace extensions
1124