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 <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.
     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 
    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 
    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.
    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 
    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 
    177 BookmarkModel* BookmarksFunction::GetBookmarkModel() {
    178   return BookmarkModelFactory::GetForProfile(GetProfile());
    179 }
    180 
    181 ChromeBookmarkClient* BookmarksFunction::GetChromeBookmarkClient() {
    182   return ChromeBookmarkClientFactory::GetForProfile(GetProfile());
    183 }
    184 
    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 
    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 
    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 
    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 
    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 
    290 void BookmarksFunction::BookmarkModelChanged() {
    291 }
    292 
    293 void BookmarksFunction::BookmarkModelLoaded(BookmarkModel* model,
    294                                             bool ids_reassigned) {
    295   model->RemoveObserver(this);
    296   RunOnReady();
    297   Release();  // Balanced in RunOnReady().
    298 }
    299 
    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 
    307 BookmarkEventRouter::~BookmarkEventRouter() {
    308   if (model_) {
    309     model_->RemoveObserver(this);
    310   }
    311 }
    312 
    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 
    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 
    329 void BookmarkEventRouter::BookmarkModelBeingDeleted(BookmarkModel* model) {
    330   model_ = NULL;
    331 }
    332 
    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 
    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 
    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 
    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 
    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 
    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 
    416 void BookmarkEventRouter::BookmarkNodeFaviconChanged(BookmarkModel* model,
    417                                                      const BookmarkNode* node) {
    418   // TODO(erikkay) anything we should do here?
    419 }
    420 
    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 
    436 void BookmarkEventRouter::ExtensiveBookmarkChangesBeginning(
    437     BookmarkModel* model) {
    438   DispatchEvent(bookmarks::OnImportBegan::kEventName,
    439                 bookmarks::OnImportBegan::Create());
    440 }
    441 
    442 void BookmarkEventRouter::ExtensiveBookmarkChangesEnded(BookmarkModel* model) {
    443   DispatchEvent(bookmarks::OnImportEnded::kEventName,
    444                 bookmarks::OnImportEnded::Create());
    445 }
    446 
    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 
    460 BookmarksAPI::~BookmarksAPI() {
    461 }
    462 
    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>*
    472 BookmarksAPI::GetFactoryInstance() {
    473   return g_factory.Pointer();
    474 }
    475 
    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 
    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 
    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 
    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 
    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 
    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 
    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
    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 
    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 
    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
    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 
    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
    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 
    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:
    810   virtual ~BookmarkBucketMapper() { STLDeleteValues(&buckets_); }
    811  protected:
    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:
    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.
    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:
    871   explicit RemoveBookmarksBucketMapper(BrowserContext* context)
    872       : browser_context_(context) {}
    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;
    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>
    925   static void Build(QuotaLimitHeuristics* heuristics) {
    926     BuildWithMappers(heuristics, new BookmarkIdMapper<FunctionType>(),
    927                                  new BookmarkIdMapper<FunctionType>());
    928   }
    929 
    930   // For bookmarks.create.
    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.
    939   static void BuildForRemove(QuotaLimitHeuristics* heuristics,
    940                              BrowserContext* context) {
    941     BuildWithMappers(heuristics,
    942                      new RemoveBookmarksBucketMapper(context),
    943                      new RemoveBookmarksBucketMapper(context));
    944   }
    945 
    946  private:
    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.
    975 void BookmarksRemoveFunction::GetQuotaLimitHeuristics(
    976     QuotaLimitHeuristics* heuristics) const {
    977   BookmarksQuotaLimitFactory::BuildForRemove(heuristics, GetProfile());
    978 }
    979 
    980 void BookmarksMoveFunction::GetQuotaLimitHeuristics(
    981     QuotaLimitHeuristics* heuristics) const {
    982   BookmarksQuotaLimitFactory::Build<BookmarksMoveFunction>(heuristics);
    983 }
    984 
    985 void BookmarksUpdateFunction::GetQuotaLimitHeuristics(
    986     QuotaLimitHeuristics* heuristics) const {
    987   BookmarksQuotaLimitFactory::Build<BookmarksUpdateFunction>(heuristics);
    988 }
    989 
    990 void BookmarksCreateFunction::GetQuotaLimitHeuristics(
    991     QuotaLimitHeuristics* heuristics) const {
    992   BookmarksQuotaLimitFactory::BuildForCreate(heuristics, GetProfile());
    993 }
    994 
    995 BookmarksIOFunction::BookmarksIOFunction() {}
    996 
    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 
   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 
   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 
   1066 void BookmarksIOFunction::FileSelectionCanceled(void* params) {
   1067   Release();  // Balanced in BookmarksIOFunction::SelectFile()
   1068 }
   1069 
   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 
   1076 bool BookmarksImportFunction::RunOnReady() {
   1077   if (!EditBookmarksEnabled())
   1078     return false;
   1079   SelectFile(ui::SelectFileDialog::SELECT_OPEN_FILE);
   1080   return true;
   1081 }
   1082 
   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 
   1106 bool BookmarksExportFunction::RunOnReady() {
   1107   SelectFile(ui::SelectFileDialog::SELECT_SAVEAS_FILE);
   1108   return true;
   1109 }
   1110 
   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