Home | History | Annotate | Download | only in bookmarks
      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/lazy_instance.h"
     12 #include "base/memory/scoped_ptr.h"
     13 #include "base/path_service.h"
     14 #include "base/prefs/pref_service.h"
     15 #include "base/sha1.h"
     16 #include "base/stl_util.h"
     17 #include "base/strings/string16.h"
     18 #include "base/strings/string_number_conversions.h"
     19 #include "base/strings/string_util.h"
     20 #include "base/strings/utf_string_conversions.h"
     21 #include "base/time/time.h"
     22 #include "chrome/browser/bookmarks/bookmark_html_writer.h"
     23 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
     24 #include "chrome/browser/bookmarks/chrome_bookmark_client.h"
     25 #include "chrome/browser/bookmarks/chrome_bookmark_client_factory.h"
     26 #include "chrome/browser/extensions/api/bookmarks/bookmark_api_constants.h"
     27 #include "chrome/browser/extensions/api/bookmarks/bookmark_api_helpers.h"
     28 #include "chrome/browser/importer/external_process_importer_host.h"
     29 #include "chrome/browser/importer/importer_uma.h"
     30 #include "chrome/browser/platform_util.h"
     31 #include "chrome/browser/profiles/profile.h"
     32 #include "chrome/browser/ui/chrome_select_file_policy.h"
     33 #include "chrome/browser/ui/host_desktop.h"
     34 #include "chrome/common/chrome_paths.h"
     35 #include "chrome/common/extensions/api/bookmarks.h"
     36 #include "chrome/common/importer/importer_data_types.h"
     37 #include "chrome/common/pref_names.h"
     38 #include "chrome/grit/generated_resources.h"
     39 #include "components/bookmarks/browser/bookmark_model.h"
     40 #include "components/bookmarks/browser/bookmark_utils.h"
     41 #include "components/user_prefs/user_prefs.h"
     42 #include "content/public/browser/browser_context.h"
     43 #include "content/public/browser/notification_service.h"
     44 #include "content/public/browser/web_contents.h"
     45 #include "extensions/browser/event_router.h"
     46 #include "extensions/browser/extension_function_dispatcher.h"
     47 #include "extensions/browser/notification_types.h"
     48 #include "ui/base/l10n/l10n_util.h"
     49 
     50 #if defined(OS_WIN)
     51 #include "ui/aura/remote_window_tree_host_win.h"
     52 #endif
     53 
     54 namespace extensions {
     55 
     56 namespace keys = bookmark_api_constants;
     57 namespace bookmarks = api::bookmarks;
     58 
     59 using bookmarks::BookmarkTreeNode;
     60 using bookmarks::CreateDetails;
     61 using content::BrowserContext;
     62 using content::BrowserThread;
     63 using content::WebContents;
     64 
     65 namespace {
     66 
     67 // Generates a default path (including a default filename) that will be
     68 // used for pre-populating the "Export Bookmarks" file chooser dialog box.
     69 base::FilePath GetDefaultFilepathForBookmarkExport() {
     70   base::Time time = base::Time::Now();
     71 
     72   // Concatenate a date stamp to the filename.
     73 #if defined(OS_POSIX)
     74   base::FilePath::StringType filename =
     75       l10n_util::GetStringFUTF8(IDS_EXPORT_BOOKMARKS_DEFAULT_FILENAME,
     76                                 base::TimeFormatShortDateNumeric(time));
     77 #elif defined(OS_WIN)
     78   base::FilePath::StringType filename =
     79       l10n_util::GetStringFUTF16(IDS_EXPORT_BOOKMARKS_DEFAULT_FILENAME,
     80                                  base::TimeFormatShortDateNumeric(time));
     81 #endif
     82 
     83   base::i18n::ReplaceIllegalCharactersInPath(&filename, '_');
     84 
     85   base::FilePath default_path;
     86   PathService::Get(chrome::DIR_USER_DOCUMENTS, &default_path);
     87   return default_path.Append(filename);
     88 }
     89 
     90 }  // namespace
     91 
     92 bool BookmarksFunction::RunAsync() {
     93   BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile());
     94   if (!model->loaded()) {
     95     // Bookmarks are not ready yet.  We'll wait.
     96     model->AddObserver(this);
     97     AddRef();  // Balanced in Loaded().
     98     return true;
     99   }
    100 
    101   bool success = RunOnReady();
    102   if (success) {
    103     content::NotificationService::current()->Notify(
    104         extensions::NOTIFICATION_EXTENSION_BOOKMARKS_API_INVOKED,
    105         content::Source<const Extension>(extension()),
    106         content::Details<const BookmarksFunction>(this));
    107   }
    108   SendResponse(success);
    109   return true;
    110 }
    111 
    112 BookmarkModel* BookmarksFunction::GetBookmarkModel() {
    113   return BookmarkModelFactory::GetForProfile(GetProfile());
    114 }
    115 
    116 ChromeBookmarkClient* BookmarksFunction::GetChromeBookmarkClient() {
    117   return ChromeBookmarkClientFactory::GetForProfile(GetProfile());
    118 }
    119 
    120 bool BookmarksFunction::GetBookmarkIdAsInt64(const std::string& id_string,
    121                                              int64* id) {
    122   if (base::StringToInt64(id_string, id))
    123     return true;
    124 
    125   error_ = keys::kInvalidIdError;
    126   return false;
    127 }
    128 
    129 const BookmarkNode* BookmarksFunction::GetBookmarkNodeFromId(
    130     const std::string& id_string) {
    131   int64 id;
    132   if (!GetBookmarkIdAsInt64(id_string, &id))
    133     return NULL;
    134 
    135   const BookmarkNode* node = ::bookmarks::GetBookmarkNodeByID(
    136       BookmarkModelFactory::GetForProfile(GetProfile()), id);
    137   if (!node)
    138     error_ = keys::kNoNodeError;
    139 
    140   return node;
    141 }
    142 
    143 const BookmarkNode* BookmarksFunction::CreateBookmarkNode(
    144     BookmarkModel* model,
    145     const CreateDetails& details,
    146     const BookmarkNode::MetaInfoMap* meta_info) {
    147   int64 parentId;
    148 
    149   if (!details.parent_id.get()) {
    150     // Optional, default to "other bookmarks".
    151     parentId = model->other_node()->id();
    152   } else {
    153     if (!GetBookmarkIdAsInt64(*details.parent_id, &parentId))
    154       return NULL;
    155   }
    156   const BookmarkNode* parent =
    157       ::bookmarks::GetBookmarkNodeByID(model, parentId);
    158   if (!CanBeModified(parent))
    159     return NULL;
    160 
    161   int index;
    162   if (!details.index.get()) {  // Optional (defaults to end).
    163     index = parent->child_count();
    164   } else {
    165     index = *details.index;
    166     if (index > parent->child_count() || index < 0) {
    167       error_ = keys::kInvalidIndexError;
    168       return NULL;
    169     }
    170   }
    171 
    172   base::string16 title;  // Optional.
    173   if (details.title.get())
    174     title = base::UTF8ToUTF16(*details.title.get());
    175 
    176   std::string url_string;  // Optional.
    177   if (details.url.get())
    178     url_string = *details.url.get();
    179 
    180   GURL url(url_string);
    181   if (!url_string.empty() && !url.is_valid()) {
    182     error_ = keys::kInvalidUrlError;
    183     return NULL;
    184   }
    185 
    186   const BookmarkNode* node;
    187   if (url_string.length())
    188     node = model->AddURLWithCreationTimeAndMetaInfo(
    189         parent, index, title, url, base::Time::Now(), meta_info);
    190   else
    191     node = model->AddFolderWithMetaInfo(parent, index, title, meta_info);
    192   DCHECK(node);
    193   if (!node) {
    194     error_ = keys::kNoNodeError;
    195     return NULL;
    196   }
    197 
    198   return node;
    199 }
    200 
    201 bool BookmarksFunction::EditBookmarksEnabled() {
    202   PrefService* prefs = user_prefs::UserPrefs::Get(GetProfile());
    203   if (prefs->GetBoolean(::bookmarks::prefs::kEditBookmarksEnabled))
    204     return true;
    205   error_ = keys::kEditBookmarksDisabled;
    206   return false;
    207 }
    208 
    209 bool BookmarksFunction::CanBeModified(const BookmarkNode* node) {
    210   if (!node) {
    211     error_ = keys::kNoParentError;
    212     return false;
    213   }
    214   if (node->is_root()) {
    215     error_ = keys::kModifySpecialError;
    216     return false;
    217   }
    218   ChromeBookmarkClient* client = GetChromeBookmarkClient();
    219   if (client->IsDescendantOfManagedNode(node)) {
    220     error_ = keys::kModifyManagedError;
    221     return false;
    222   }
    223   return true;
    224 }
    225 
    226 void BookmarksFunction::BookmarkModelChanged() {
    227 }
    228 
    229 void BookmarksFunction::BookmarkModelLoaded(BookmarkModel* model,
    230                                             bool ids_reassigned) {
    231   model->RemoveObserver(this);
    232   RunOnReady();
    233   Release();  // Balanced in RunOnReady().
    234 }
    235 
    236 BookmarkEventRouter::BookmarkEventRouter(Profile* profile)
    237     : browser_context_(profile),
    238       model_(BookmarkModelFactory::GetForProfile(profile)),
    239       client_(ChromeBookmarkClientFactory::GetForProfile(profile)) {
    240   model_->AddObserver(this);
    241 }
    242 
    243 BookmarkEventRouter::~BookmarkEventRouter() {
    244   if (model_) {
    245     model_->RemoveObserver(this);
    246   }
    247 }
    248 
    249 void BookmarkEventRouter::DispatchEvent(
    250     const std::string& event_name,
    251     scoped_ptr<base::ListValue> event_args) {
    252   EventRouter* event_router = EventRouter::Get(browser_context_);
    253   if (event_router) {
    254     event_router->BroadcastEvent(
    255         make_scoped_ptr(new extensions::Event(event_name, event_args.Pass())));
    256   }
    257 }
    258 
    259 void BookmarkEventRouter::BookmarkModelLoaded(BookmarkModel* model,
    260                                               bool ids_reassigned) {
    261   // TODO(erikkay): Perhaps we should send this event down to the extension
    262   // so they know when it's safe to use the API?
    263 }
    264 
    265 void BookmarkEventRouter::BookmarkModelBeingDeleted(BookmarkModel* model) {
    266   model_ = NULL;
    267 }
    268 
    269 void BookmarkEventRouter::BookmarkNodeMoved(BookmarkModel* model,
    270                                             const BookmarkNode* old_parent,
    271                                             int old_index,
    272                                             const BookmarkNode* new_parent,
    273                                             int new_index) {
    274   const BookmarkNode* node = new_parent->GetChild(new_index);
    275   bookmarks::OnMoved::MoveInfo move_info;
    276   move_info.parent_id = base::Int64ToString(new_parent->id());
    277   move_info.index = new_index;
    278   move_info.old_parent_id = base::Int64ToString(old_parent->id());
    279   move_info.old_index = old_index;
    280 
    281   DispatchEvent(
    282       bookmarks::OnMoved::kEventName,
    283       bookmarks::OnMoved::Create(base::Int64ToString(node->id()), move_info));
    284 }
    285 
    286 void BookmarkEventRouter::BookmarkNodeAdded(BookmarkModel* model,
    287                                             const BookmarkNode* parent,
    288                                             int index) {
    289   const BookmarkNode* node = parent->GetChild(index);
    290   scoped_ptr<BookmarkTreeNode> tree_node(
    291       bookmark_api_helpers::GetBookmarkTreeNode(client_, node, false, false));
    292   DispatchEvent(bookmarks::OnCreated::kEventName,
    293                 bookmarks::OnCreated::Create(base::Int64ToString(node->id()),
    294                                              *tree_node));
    295 }
    296 
    297 void BookmarkEventRouter::BookmarkNodeRemoved(
    298     BookmarkModel* model,
    299     const BookmarkNode* parent,
    300     int index,
    301     const BookmarkNode* node,
    302     const std::set<GURL>& removed_urls) {
    303   bookmarks::OnRemoved::RemoveInfo remove_info;
    304   remove_info.parent_id = base::Int64ToString(parent->id());
    305   remove_info.index = index;
    306 
    307   DispatchEvent(bookmarks::OnRemoved::kEventName,
    308                 bookmarks::OnRemoved::Create(base::Int64ToString(node->id()),
    309                                              remove_info));
    310 }
    311 
    312 void BookmarkEventRouter::BookmarkAllUserNodesRemoved(
    313     BookmarkModel* model,
    314     const std::set<GURL>& removed_urls) {
    315   NOTREACHED();
    316   // TODO(shashishekhar) Currently this notification is only used on Android,
    317   // which does not support extensions. If Desktop needs to support this, add
    318   // a new event to the extensions api.
    319 }
    320 
    321 void BookmarkEventRouter::BookmarkNodeChanged(BookmarkModel* model,
    322                                               const BookmarkNode* node) {
    323   // TODO(erikkay) The only three things that BookmarkModel sends this
    324   // notification for are title, url and favicon.  Since we're currently
    325   // ignoring favicon and since the notification doesn't say which one anyway,
    326   // for now we only include title and url.  The ideal thing would be to change
    327   // BookmarkModel to indicate what changed.
    328   bookmarks::OnChanged::ChangeInfo change_info;
    329   change_info.title = base::UTF16ToUTF8(node->GetTitle());
    330   if (node->is_url())
    331     change_info.url.reset(new std::string(node->url().spec()));
    332 
    333   DispatchEvent(bookmarks::OnChanged::kEventName,
    334                 bookmarks::OnChanged::Create(base::Int64ToString(node->id()),
    335                                              change_info));
    336 }
    337 
    338 void BookmarkEventRouter::BookmarkNodeFaviconChanged(BookmarkModel* model,
    339                                                      const BookmarkNode* node) {
    340   // TODO(erikkay) anything we should do here?
    341 }
    342 
    343 void BookmarkEventRouter::BookmarkNodeChildrenReordered(
    344     BookmarkModel* model,
    345     const BookmarkNode* node) {
    346   bookmarks::OnChildrenReordered::ReorderInfo reorder_info;
    347   int childCount = node->child_count();
    348   for (int i = 0; i < childCount; ++i) {
    349     const BookmarkNode* child = node->GetChild(i);
    350     reorder_info.child_ids.push_back(base::Int64ToString(child->id()));
    351   }
    352 
    353   DispatchEvent(bookmarks::OnChildrenReordered::kEventName,
    354                 bookmarks::OnChildrenReordered::Create(
    355                     base::Int64ToString(node->id()), reorder_info));
    356 }
    357 
    358 void BookmarkEventRouter::ExtensiveBookmarkChangesBeginning(
    359     BookmarkModel* model) {
    360   DispatchEvent(bookmarks::OnImportBegan::kEventName,
    361                 bookmarks::OnImportBegan::Create());
    362 }
    363 
    364 void BookmarkEventRouter::ExtensiveBookmarkChangesEnded(BookmarkModel* model) {
    365   DispatchEvent(bookmarks::OnImportEnded::kEventName,
    366                 bookmarks::OnImportEnded::Create());
    367 }
    368 
    369 BookmarksAPI::BookmarksAPI(BrowserContext* context)
    370     : browser_context_(context) {
    371   EventRouter* event_router = EventRouter::Get(browser_context_);
    372   event_router->RegisterObserver(this, bookmarks::OnCreated::kEventName);
    373   event_router->RegisterObserver(this, bookmarks::OnRemoved::kEventName);
    374   event_router->RegisterObserver(this, bookmarks::OnChanged::kEventName);
    375   event_router->RegisterObserver(this, bookmarks::OnMoved::kEventName);
    376   event_router->RegisterObserver(this,
    377                                  bookmarks::OnChildrenReordered::kEventName);
    378   event_router->RegisterObserver(this, bookmarks::OnImportBegan::kEventName);
    379   event_router->RegisterObserver(this, bookmarks::OnImportEnded::kEventName);
    380 }
    381 
    382 BookmarksAPI::~BookmarksAPI() {
    383 }
    384 
    385 void BookmarksAPI::Shutdown() {
    386   EventRouter::Get(browser_context_)->UnregisterObserver(this);
    387 }
    388 
    389 static base::LazyInstance<BrowserContextKeyedAPIFactory<BookmarksAPI> >
    390     g_factory = LAZY_INSTANCE_INITIALIZER;
    391 
    392 // static
    393 BrowserContextKeyedAPIFactory<BookmarksAPI>*
    394 BookmarksAPI::GetFactoryInstance() {
    395   return g_factory.Pointer();
    396 }
    397 
    398 void BookmarksAPI::OnListenerAdded(const EventListenerInfo& details) {
    399   bookmark_event_router_.reset(
    400       new BookmarkEventRouter(Profile::FromBrowserContext(browser_context_)));
    401   EventRouter::Get(browser_context_)->UnregisterObserver(this);
    402 }
    403 
    404 bool BookmarksGetFunction::RunOnReady() {
    405   scoped_ptr<bookmarks::Get::Params> params(
    406       bookmarks::Get::Params::Create(*args_));
    407   EXTENSION_FUNCTION_VALIDATE(params.get());
    408 
    409   std::vector<linked_ptr<BookmarkTreeNode> > nodes;
    410   ChromeBookmarkClient* client = GetChromeBookmarkClient();
    411   if (params->id_or_id_list.as_strings) {
    412     std::vector<std::string>& ids = *params->id_or_id_list.as_strings;
    413     size_t count = ids.size();
    414     EXTENSION_FUNCTION_VALIDATE(count > 0);
    415     for (size_t i = 0; i < count; ++i) {
    416       const BookmarkNode* node = GetBookmarkNodeFromId(ids[i]);
    417       if (!node)
    418         return false;
    419       bookmark_api_helpers::AddNode(client, node, &nodes, false);
    420     }
    421   } else {
    422     const BookmarkNode* node =
    423         GetBookmarkNodeFromId(*params->id_or_id_list.as_string);
    424     if (!node)
    425       return false;
    426     bookmark_api_helpers::AddNode(client, node, &nodes, false);
    427   }
    428 
    429   results_ = bookmarks::Get::Results::Create(nodes);
    430   return true;
    431 }
    432 
    433 bool BookmarksGetChildrenFunction::RunOnReady() {
    434   scoped_ptr<bookmarks::GetChildren::Params> params(
    435       bookmarks::GetChildren::Params::Create(*args_));
    436   EXTENSION_FUNCTION_VALIDATE(params.get());
    437 
    438   const BookmarkNode* node = GetBookmarkNodeFromId(params->id);
    439   if (!node)
    440     return false;
    441 
    442   std::vector<linked_ptr<BookmarkTreeNode> > nodes;
    443   int child_count = node->child_count();
    444   for (int i = 0; i < child_count; ++i) {
    445     const BookmarkNode* child = node->GetChild(i);
    446     bookmark_api_helpers::AddNode(
    447         GetChromeBookmarkClient(), child, &nodes, false);
    448   }
    449 
    450   results_ = bookmarks::GetChildren::Results::Create(nodes);
    451   return true;
    452 }
    453 
    454 bool BookmarksGetRecentFunction::RunOnReady() {
    455   scoped_ptr<bookmarks::GetRecent::Params> params(
    456       bookmarks::GetRecent::Params::Create(*args_));
    457   EXTENSION_FUNCTION_VALIDATE(params.get());
    458   if (params->number_of_items < 1)
    459     return false;
    460 
    461   std::vector<const BookmarkNode*> nodes;
    462   ::bookmarks::GetMostRecentlyAddedEntries(
    463       BookmarkModelFactory::GetForProfile(GetProfile()),
    464       params->number_of_items,
    465       &nodes);
    466 
    467   std::vector<linked_ptr<BookmarkTreeNode> > tree_nodes;
    468   std::vector<const BookmarkNode*>::iterator i = nodes.begin();
    469   for (; i != nodes.end(); ++i) {
    470     const BookmarkNode* node = *i;
    471     bookmark_api_helpers::AddNode(
    472         GetChromeBookmarkClient(), node, &tree_nodes, false);
    473   }
    474 
    475   results_ = bookmarks::GetRecent::Results::Create(tree_nodes);
    476   return true;
    477 }
    478 
    479 bool BookmarksGetTreeFunction::RunOnReady() {
    480   std::vector<linked_ptr<BookmarkTreeNode> > nodes;
    481   const BookmarkNode* node =
    482       BookmarkModelFactory::GetForProfile(GetProfile())->root_node();
    483   bookmark_api_helpers::AddNode(GetChromeBookmarkClient(), node, &nodes, true);
    484   results_ = bookmarks::GetTree::Results::Create(nodes);
    485   return true;
    486 }
    487 
    488 bool BookmarksGetSubTreeFunction::RunOnReady() {
    489   scoped_ptr<bookmarks::GetSubTree::Params> params(
    490       bookmarks::GetSubTree::Params::Create(*args_));
    491   EXTENSION_FUNCTION_VALIDATE(params.get());
    492 
    493   const BookmarkNode* node = GetBookmarkNodeFromId(params->id);
    494   if (!node)
    495     return false;
    496 
    497   std::vector<linked_ptr<BookmarkTreeNode> > nodes;
    498   bookmark_api_helpers::AddNode(GetChromeBookmarkClient(), node, &nodes, true);
    499   results_ = bookmarks::GetSubTree::Results::Create(nodes);
    500   return true;
    501 }
    502 
    503 bool BookmarksSearchFunction::RunOnReady() {
    504   scoped_ptr<bookmarks::Search::Params> params(
    505       bookmarks::Search::Params::Create(*args_));
    506   EXTENSION_FUNCTION_VALIDATE(params.get());
    507 
    508   PrefService* prefs = user_prefs::UserPrefs::Get(GetProfile());
    509   std::string lang = prefs->GetString(prefs::kAcceptLanguages);
    510   std::vector<const BookmarkNode*> nodes;
    511   if (params->query.as_string) {
    512     ::bookmarks::QueryFields query;
    513     query.word_phrase_query.reset(
    514         new base::string16(base::UTF8ToUTF16(*params->query.as_string)));
    515     ::bookmarks::GetBookmarksMatchingProperties(
    516         BookmarkModelFactory::GetForProfile(GetProfile()),
    517         query,
    518         std::numeric_limits<int>::max(),
    519         lang,
    520         &nodes);
    521   } else {
    522     DCHECK(params->query.as_object);
    523     const bookmarks::Search::Params::Query::Object& object =
    524         *params->query.as_object;
    525     ::bookmarks::QueryFields query;
    526     if (object.query) {
    527       query.word_phrase_query.reset(
    528           new base::string16(base::UTF8ToUTF16(*object.query)));
    529     }
    530     if (object.url)
    531       query.url.reset(new base::string16(base::UTF8ToUTF16(*object.url)));
    532     if (object.title)
    533       query.title.reset(new base::string16(base::UTF8ToUTF16(*object.title)));
    534     ::bookmarks::GetBookmarksMatchingProperties(
    535         BookmarkModelFactory::GetForProfile(GetProfile()),
    536         query,
    537         std::numeric_limits<int>::max(),
    538         lang,
    539         &nodes);
    540   }
    541 
    542   std::vector<linked_ptr<BookmarkTreeNode> > tree_nodes;
    543   ChromeBookmarkClient* client = GetChromeBookmarkClient();
    544   for (std::vector<const BookmarkNode*>::iterator node_iter = nodes.begin();
    545        node_iter != nodes.end(); ++node_iter) {
    546     bookmark_api_helpers::AddNode(client, *node_iter, &tree_nodes, false);
    547   }
    548 
    549   results_ = bookmarks::Search::Results::Create(tree_nodes);
    550   return true;
    551 }
    552 
    553 // static
    554 bool BookmarksRemoveFunction::ExtractIds(const base::ListValue* args,
    555                                          std::list<int64>* ids,
    556                                          bool* invalid_id) {
    557   std::string id_string;
    558   if (!args->GetString(0, &id_string))
    559     return false;
    560   int64 id;
    561   if (base::StringToInt64(id_string, &id))
    562     ids->push_back(id);
    563   else
    564     *invalid_id = true;
    565   return true;
    566 }
    567 
    568 bool BookmarksRemoveFunction::RunOnReady() {
    569   if (!EditBookmarksEnabled())
    570     return false;
    571 
    572   scoped_ptr<bookmarks::Remove::Params> params(
    573       bookmarks::Remove::Params::Create(*args_));
    574   EXTENSION_FUNCTION_VALIDATE(params.get());
    575 
    576   int64 id;
    577   if (!GetBookmarkIdAsInt64(params->id, &id))
    578     return false;
    579 
    580   bool recursive = false;
    581   if (name() == BookmarksRemoveTreeFunction::function_name())
    582     recursive = true;
    583 
    584   BookmarkModel* model = GetBookmarkModel();
    585   ChromeBookmarkClient* client = GetChromeBookmarkClient();
    586   if (!bookmark_api_helpers::RemoveNode(model, client, id, recursive, &error_))
    587     return false;
    588 
    589   return true;
    590 }
    591 
    592 bool BookmarksCreateFunction::RunOnReady() {
    593   if (!EditBookmarksEnabled())
    594     return false;
    595 
    596   scoped_ptr<bookmarks::Create::Params> params(
    597       bookmarks::Create::Params::Create(*args_));
    598   EXTENSION_FUNCTION_VALIDATE(params.get());
    599 
    600   BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile());
    601   const BookmarkNode* node = CreateBookmarkNode(model, params->bookmark, NULL);
    602   if (!node)
    603     return false;
    604 
    605   scoped_ptr<BookmarkTreeNode> ret(bookmark_api_helpers::GetBookmarkTreeNode(
    606       GetChromeBookmarkClient(), node, false, false));
    607   results_ = bookmarks::Create::Results::Create(*ret);
    608 
    609   return true;
    610 }
    611 
    612 // static
    613 bool BookmarksMoveFunction::ExtractIds(const base::ListValue* args,
    614                                        std::list<int64>* ids,
    615                                        bool* invalid_id) {
    616   // For now, Move accepts ID parameters in the same way as an Update.
    617   return BookmarksUpdateFunction::ExtractIds(args, ids, invalid_id);
    618 }
    619 
    620 bool BookmarksMoveFunction::RunOnReady() {
    621   if (!EditBookmarksEnabled())
    622     return false;
    623 
    624   scoped_ptr<bookmarks::Move::Params> params(
    625       bookmarks::Move::Params::Create(*args_));
    626   EXTENSION_FUNCTION_VALIDATE(params.get());
    627 
    628   const BookmarkNode* node = GetBookmarkNodeFromId(params->id);
    629   if (!node)
    630     return false;
    631 
    632   BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile());
    633   if (model->is_permanent_node(node)) {
    634     error_ = keys::kModifySpecialError;
    635     return false;
    636   }
    637 
    638   const BookmarkNode* parent = NULL;
    639   if (!params->destination.parent_id.get()) {
    640     // Optional, defaults to current parent.
    641     parent = node->parent();
    642   } else {
    643     int64 parentId;
    644     if (!GetBookmarkIdAsInt64(*params->destination.parent_id, &parentId))
    645       return false;
    646 
    647     parent = ::bookmarks::GetBookmarkNodeByID(model, parentId);
    648   }
    649   if (!CanBeModified(parent) || !CanBeModified(node))
    650     return false;
    651 
    652   int index;
    653   if (params->destination.index.get()) {  // Optional (defaults to end).
    654     index = *params->destination.index;
    655     if (index > parent->child_count() || index < 0) {
    656       error_ = keys::kInvalidIndexError;
    657       return false;
    658     }
    659   } else {
    660     index = parent->child_count();
    661   }
    662 
    663   model->Move(node, parent, index);
    664 
    665   scoped_ptr<BookmarkTreeNode> tree_node(
    666       bookmark_api_helpers::GetBookmarkTreeNode(
    667           GetChromeBookmarkClient(), node, false, false));
    668   results_ = bookmarks::Move::Results::Create(*tree_node);
    669 
    670   return true;
    671 }
    672 
    673 // static
    674 bool BookmarksUpdateFunction::ExtractIds(const base::ListValue* args,
    675                                          std::list<int64>* ids,
    676                                          bool* invalid_id) {
    677   // For now, Update accepts ID parameters in the same way as an Remove.
    678   return BookmarksRemoveFunction::ExtractIds(args, ids, invalid_id);
    679 }
    680 
    681 bool BookmarksUpdateFunction::RunOnReady() {
    682   if (!EditBookmarksEnabled())
    683     return false;
    684 
    685   scoped_ptr<bookmarks::Update::Params> params(
    686       bookmarks::Update::Params::Create(*args_));
    687   EXTENSION_FUNCTION_VALIDATE(params.get());
    688 
    689   // Optional but we need to distinguish non present from an empty title.
    690   base::string16 title;
    691   bool has_title = false;
    692   if (params->changes.title.get()) {
    693     title = base::UTF8ToUTF16(*params->changes.title);
    694     has_title = true;
    695   }
    696 
    697   // Optional.
    698   std::string url_string;
    699   if (params->changes.url.get())
    700     url_string = *params->changes.url;
    701   GURL url(url_string);
    702   if (!url_string.empty() && !url.is_valid()) {
    703     error_ = keys::kInvalidUrlError;
    704     return false;
    705   }
    706 
    707   const BookmarkNode* node = GetBookmarkNodeFromId(params->id);
    708   if (!CanBeModified(node))
    709     return false;
    710 
    711   BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile());
    712   if (model->is_permanent_node(node)) {
    713     error_ = keys::kModifySpecialError;
    714     return false;
    715   }
    716   if (has_title)
    717     model->SetTitle(node, title);
    718   if (!url.is_empty())
    719     model->SetURL(node, url);
    720 
    721   scoped_ptr<BookmarkTreeNode> tree_node(
    722       bookmark_api_helpers::GetBookmarkTreeNode(
    723           GetChromeBookmarkClient(), node, false, false));
    724   results_ = bookmarks::Update::Results::Create(*tree_node);
    725   return true;
    726 }
    727 
    728 BookmarksIOFunction::BookmarksIOFunction() {}
    729 
    730 BookmarksIOFunction::~BookmarksIOFunction() {
    731   // There may be pending file dialogs, we need to tell them that we've gone
    732   // away so they don't try and call back to us.
    733   if (select_file_dialog_.get())
    734     select_file_dialog_->ListenerDestroyed();
    735 }
    736 
    737 void BookmarksIOFunction::SelectFile(ui::SelectFileDialog::Type type) {
    738   // GetDefaultFilepathForBookmarkExport() might have to touch the filesystem
    739   // (stat or access, for example), so this requires a thread with IO allowed.
    740   if (!BrowserThread::CurrentlyOn(BrowserThread::FILE)) {
    741     BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
    742         base::Bind(&BookmarksIOFunction::SelectFile, this, type));
    743     return;
    744   }
    745 
    746   // Pre-populating the filename field in case this is a SELECT_SAVEAS_FILE
    747   // dialog. If not, there is no filename field in the dialog box.
    748   base::FilePath default_path;
    749   if (type == ui::SelectFileDialog::SELECT_SAVEAS_FILE)
    750     default_path = GetDefaultFilepathForBookmarkExport();
    751   else
    752     DCHECK(type == ui::SelectFileDialog::SELECT_OPEN_FILE);
    753 
    754   // After getting the |default_path|, ask the UI to display the file dialog.
    755   BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
    756       base::Bind(&BookmarksIOFunction::ShowSelectFileDialog, this,
    757                  type, default_path));
    758 }
    759 
    760 void BookmarksIOFunction::ShowSelectFileDialog(
    761     ui::SelectFileDialog::Type type,
    762     const base::FilePath& default_path) {
    763   if (!dispatcher())
    764     return;  // Extension was unloaded.
    765 
    766   // Balanced in one of the three callbacks of SelectFileDialog:
    767   // either FileSelectionCanceled, MultiFilesSelected, or FileSelected
    768   AddRef();
    769 
    770   WebContents* web_contents = dispatcher()->delegate()->
    771       GetAssociatedWebContents();
    772 
    773   select_file_dialog_ = ui::SelectFileDialog::Create(
    774       this, new ChromeSelectFilePolicy(web_contents));
    775   ui::SelectFileDialog::FileTypeInfo file_type_info;
    776   file_type_info.extensions.resize(1);
    777   file_type_info.extensions[0].push_back(FILE_PATH_LITERAL("html"));
    778   gfx::NativeWindow owning_window = web_contents ?
    779       platform_util::GetTopLevel(web_contents->GetNativeView())
    780           : NULL;
    781 #if defined(OS_WIN)
    782   if (!owning_window &&
    783       chrome::GetActiveDesktop() == chrome::HOST_DESKTOP_TYPE_ASH)
    784     owning_window = aura::RemoteWindowTreeHostWin::Instance()->GetAshWindow();
    785 #endif
    786   // |web_contents| can be NULL (for background pages), which is fine. In such
    787   // a case if file-selection dialogs are forbidden by policy, we will not
    788   // show an InfoBar, which is better than letting one appear out of the blue.
    789   select_file_dialog_->SelectFile(type,
    790                                   base::string16(),
    791                                   default_path,
    792                                   &file_type_info,
    793                                   0,
    794                                   base::FilePath::StringType(),
    795                                   owning_window,
    796                                   NULL);
    797 }
    798 
    799 void BookmarksIOFunction::FileSelectionCanceled(void* params) {
    800   Release();  // Balanced in BookmarksIOFunction::SelectFile()
    801 }
    802 
    803 void BookmarksIOFunction::MultiFilesSelected(
    804     const std::vector<base::FilePath>& files, void* params) {
    805   Release();  // Balanced in BookmarsIOFunction::SelectFile()
    806   NOTREACHED() << "Should not be able to select multiple files";
    807 }
    808 
    809 bool BookmarksImportFunction::RunOnReady() {
    810   if (!EditBookmarksEnabled())
    811     return false;
    812   SelectFile(ui::SelectFileDialog::SELECT_OPEN_FILE);
    813   return true;
    814 }
    815 
    816 void BookmarksImportFunction::FileSelected(const base::FilePath& path,
    817                                            int index,
    818                                            void* params) {
    819   // Deletes itself.
    820   ExternalProcessImporterHost* importer_host = new ExternalProcessImporterHost;
    821   importer::SourceProfile source_profile;
    822   source_profile.importer_type = importer::TYPE_BOOKMARKS_FILE;
    823   source_profile.source_path = path;
    824   importer_host->StartImportSettings(source_profile,
    825                                      GetProfile(),
    826                                      importer::FAVORITES,
    827                                      new ProfileWriter(GetProfile()));
    828 
    829   importer::LogImporterUseToMetrics("BookmarksAPI",
    830                                     importer::TYPE_BOOKMARKS_FILE);
    831   Release();  // Balanced in BookmarksIOFunction::SelectFile()
    832 }
    833 
    834 bool BookmarksExportFunction::RunOnReady() {
    835   SelectFile(ui::SelectFileDialog::SELECT_SAVEAS_FILE);
    836   return true;
    837 }
    838 
    839 void BookmarksExportFunction::FileSelected(const base::FilePath& path,
    840                                            int index,
    841                                            void* params) {
    842   bookmark_html_writer::WriteBookmarks(GetProfile(), path, NULL);
    843   Release();  // Balanced in BookmarksIOFunction::SelectFile()
    844 }
    845 
    846 }  // namespace extensions
    847