Home | History | Annotate | Download | only in extensions
      1 // Copyright (c) 2011 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/extension_bookmarks_module.h"
      6 
      7 #include "base/file_path.h"
      8 #include "base/i18n/file_util_icu.h"
      9 #include "base/i18n/time_formatting.h"
     10 #include "base/json/json_writer.h"
     11 #include "base/path_service.h"
     12 #include "base/sha1.h"
     13 #include "base/stl_util-inl.h"
     14 #include "base/string16.h"
     15 #include "base/string_number_conversions.h"
     16 #include "base/string_util.h"
     17 #include "base/time.h"
     18 #include "base/utf_string_conversions.h"
     19 #include "chrome/browser/bookmarks/bookmark_codec.h"
     20 #include "chrome/browser/bookmarks/bookmark_html_writer.h"
     21 #include "chrome/browser/bookmarks/bookmark_model.h"
     22 #include "chrome/browser/bookmarks/bookmark_utils.h"
     23 #include "chrome/browser/extensions/extension_bookmark_helpers.h"
     24 #include "chrome/browser/extensions/extension_bookmarks_module_constants.h"
     25 #include "chrome/browser/extensions/extension_event_router.h"
     26 #include "chrome/browser/extensions/extensions_quota_service.h"
     27 #include "chrome/browser/importer/importer_data_types.h"
     28 #include "chrome/browser/importer/importer_host.h"
     29 #include "chrome/browser/prefs/pref_service.h"
     30 #include "chrome/browser/profiles/profile.h"
     31 #include "chrome/browser/ui/browser_list.h"
     32 #include "chrome/common/chrome_paths.h"
     33 #include "chrome/common/pref_names.h"
     34 #include "content/common/notification_service.h"
     35 #include "grit/generated_resources.h"
     36 #include "ui/base/l10n/l10n_util.h"
     37 
     38 namespace keys = extension_bookmarks_module_constants;
     39 
     40 using base::TimeDelta;
     41 typedef QuotaLimitHeuristic::Bucket Bucket;
     42 typedef QuotaLimitHeuristic::Config Config;
     43 typedef QuotaLimitHeuristic::BucketList BucketList;
     44 typedef ExtensionsQuotaService::TimedLimit TimedLimit;
     45 typedef ExtensionsQuotaService::SustainedLimit SustainedLimit;
     46 typedef QuotaLimitHeuristic::BucketMapper BucketMapper;
     47 
     48 namespace {
     49 
     50 // Generates a default path (including a default filename) that will be
     51 // used for pre-populating the "Export Bookmarks" file chooser dialog box.
     52 FilePath GetDefaultFilepathForBookmarkExport() {
     53   base::Time time = base::Time::Now();
     54 
     55   // Concatenate a date stamp to the filename.
     56 #if defined(OS_POSIX)
     57   FilePath::StringType filename =
     58       l10n_util::GetStringFUTF8(IDS_EXPORT_BOOKMARKS_DEFAULT_FILENAME,
     59                                 base::TimeFormatShortDateNumeric(time));
     60 #elif defined(OS_WIN)
     61   FilePath::StringType filename =
     62       l10n_util::GetStringFUTF16(IDS_EXPORT_BOOKMARKS_DEFAULT_FILENAME,
     63                                  base::TimeFormatShortDateNumeric(time));
     64 #endif
     65 
     66   file_util::ReplaceIllegalCharactersInPath(&filename, '_');
     67 
     68   FilePath default_path;
     69   PathService::Get(chrome::DIR_USER_DOCUMENTS, &default_path);
     70   return default_path.Append(filename);
     71 }
     72 
     73 }  // namespace
     74 
     75 void BookmarksFunction::Run() {
     76   BookmarkModel* model = profile()->GetBookmarkModel();
     77   if (!model->IsLoaded()) {
     78     // Bookmarks are not ready yet.  We'll wait.
     79     registrar_.Add(this, NotificationType::BOOKMARK_MODEL_LOADED,
     80                    NotificationService::AllSources());
     81     AddRef();  // Balanced in Observe().
     82     return;
     83   }
     84 
     85   bool success = RunImpl();
     86   if (success) {
     87     NotificationService::current()->Notify(
     88         NotificationType::EXTENSION_BOOKMARKS_API_INVOKED,
     89         Source<const Extension>(GetExtension()),
     90         Details<const BookmarksFunction>(this));
     91   }
     92   SendResponse(success);
     93 }
     94 
     95 bool BookmarksFunction::GetBookmarkIdAsInt64(
     96     const std::string& id_string, int64* id) {
     97   if (base::StringToInt64(id_string, id))
     98     return true;
     99 
    100   error_ = keys::kInvalidIdError;
    101   return false;
    102 }
    103 
    104 bool BookmarksFunction::EditBookmarksEnabled() {
    105   if (profile_->GetPrefs()->GetBoolean(prefs::kEditBookmarksEnabled))
    106     return true;
    107   error_ = keys::kEditBookmarksDisabled;
    108   return false;
    109 }
    110 
    111 void BookmarksFunction::Observe(NotificationType type,
    112                                 const NotificationSource& source,
    113                                 const NotificationDetails& details) {
    114   DCHECK(type == NotificationType::BOOKMARK_MODEL_LOADED);
    115   DCHECK(profile()->GetBookmarkModel()->IsLoaded());
    116   Run();
    117   Release();  // Balanced in Run().
    118 }
    119 
    120 // static
    121 ExtensionBookmarkEventRouter* ExtensionBookmarkEventRouter::GetInstance() {
    122   return Singleton<ExtensionBookmarkEventRouter>::get();
    123 }
    124 
    125 ExtensionBookmarkEventRouter::ExtensionBookmarkEventRouter() {
    126 }
    127 
    128 ExtensionBookmarkEventRouter::~ExtensionBookmarkEventRouter() {
    129 }
    130 
    131 void ExtensionBookmarkEventRouter::Observe(BookmarkModel* model) {
    132   if (models_.find(model) == models_.end()) {
    133     model->AddObserver(this);
    134     models_.insert(model);
    135   }
    136 }
    137 
    138 void ExtensionBookmarkEventRouter::DispatchEvent(Profile *profile,
    139                                                  const char* event_name,
    140                                                  const std::string& json_args) {
    141   if (profile->GetExtensionEventRouter()) {
    142     profile->GetExtensionEventRouter()->DispatchEventToRenderers(
    143         event_name, json_args, NULL, GURL());
    144   }
    145 }
    146 
    147 void ExtensionBookmarkEventRouter::Loaded(BookmarkModel* model) {
    148   // TODO(erikkay): Perhaps we should send this event down to the extension
    149   // so they know when it's safe to use the API?
    150 }
    151 
    152 void ExtensionBookmarkEventRouter::BookmarkNodeMoved(
    153     BookmarkModel* model,
    154     const BookmarkNode* old_parent,
    155     int old_index,
    156     const BookmarkNode* new_parent,
    157     int new_index) {
    158   ListValue args;
    159   const BookmarkNode* node = new_parent->GetChild(new_index);
    160   args.Append(new StringValue(base::Int64ToString(node->id())));
    161   DictionaryValue* object_args = new DictionaryValue();
    162   object_args->SetString(keys::kParentIdKey,
    163                          base::Int64ToString(new_parent->id()));
    164   object_args->SetInteger(keys::kIndexKey, new_index);
    165   object_args->SetString(keys::kOldParentIdKey,
    166                          base::Int64ToString(old_parent->id()));
    167   object_args->SetInteger(keys::kOldIndexKey, old_index);
    168   args.Append(object_args);
    169 
    170   std::string json_args;
    171   base::JSONWriter::Write(&args, false, &json_args);
    172   DispatchEvent(model->profile(), keys::kOnBookmarkMoved, json_args);
    173 }
    174 
    175 void ExtensionBookmarkEventRouter::BookmarkNodeAdded(BookmarkModel* model,
    176                                                      const BookmarkNode* parent,
    177                                                      int index) {
    178   ListValue args;
    179   const BookmarkNode* node = parent->GetChild(index);
    180   args.Append(new StringValue(base::Int64ToString(node->id())));
    181   DictionaryValue* obj =
    182       extension_bookmark_helpers::GetNodeDictionary(node, false, false);
    183   args.Append(obj);
    184 
    185   std::string json_args;
    186   base::JSONWriter::Write(&args, false, &json_args);
    187   DispatchEvent(model->profile(), keys::kOnBookmarkCreated, json_args);
    188 }
    189 
    190 void ExtensionBookmarkEventRouter::BookmarkNodeRemoved(
    191     BookmarkModel* model,
    192     const BookmarkNode* parent,
    193     int index,
    194     const BookmarkNode* node) {
    195   ListValue args;
    196   args.Append(new StringValue(base::Int64ToString(node->id())));
    197   DictionaryValue* object_args = new DictionaryValue();
    198   object_args->SetString(keys::kParentIdKey,
    199                          base::Int64ToString(parent->id()));
    200   object_args->SetInteger(keys::kIndexKey, index);
    201   args.Append(object_args);
    202 
    203   std::string json_args;
    204   base::JSONWriter::Write(&args, false, &json_args);
    205   DispatchEvent(model->profile(), keys::kOnBookmarkRemoved, json_args);
    206 }
    207 
    208 void ExtensionBookmarkEventRouter::BookmarkNodeChanged(
    209     BookmarkModel* model, const BookmarkNode* node) {
    210   ListValue args;
    211   args.Append(new StringValue(base::Int64ToString(node->id())));
    212 
    213   // TODO(erikkay) The only three things that BookmarkModel sends this
    214   // notification for are title, url and favicon.  Since we're currently
    215   // ignoring favicon and since the notification doesn't say which one anyway,
    216   // for now we only include title and url.  The ideal thing would be to change
    217   // BookmarkModel to indicate what changed.
    218   DictionaryValue* object_args = new DictionaryValue();
    219   object_args->SetString(keys::kTitleKey, node->GetTitle());
    220   if (node->is_url())
    221     object_args->SetString(keys::kUrlKey, node->GetURL().spec());
    222   args.Append(object_args);
    223 
    224   std::string json_args;
    225   base::JSONWriter::Write(&args, false, &json_args);
    226   DispatchEvent(model->profile(), keys::kOnBookmarkChanged, json_args);
    227 }
    228 
    229 void ExtensionBookmarkEventRouter::BookmarkNodeFaviconLoaded(
    230     BookmarkModel* model, const BookmarkNode* node) {
    231   // TODO(erikkay) anything we should do here?
    232 }
    233 
    234 void ExtensionBookmarkEventRouter::BookmarkNodeChildrenReordered(
    235     BookmarkModel* model, const BookmarkNode* node) {
    236   ListValue args;
    237   args.Append(new StringValue(base::Int64ToString(node->id())));
    238   int childCount = node->child_count();
    239   ListValue* children = new ListValue();
    240   for (int i = 0; i < childCount; ++i) {
    241     const BookmarkNode* child = node->GetChild(i);
    242     Value* child_id = new StringValue(base::Int64ToString(child->id()));
    243     children->Append(child_id);
    244   }
    245   DictionaryValue* reorder_info = new DictionaryValue();
    246   reorder_info->Set(keys::kChildIdsKey, children);
    247   args.Append(reorder_info);
    248 
    249   std::string json_args;
    250   base::JSONWriter::Write(&args, false, &json_args);
    251   DispatchEvent(model->profile(),
    252                 keys::kOnBookmarkChildrenReordered,
    253                 json_args);
    254 }
    255 
    256 void ExtensionBookmarkEventRouter::
    257     BookmarkImportBeginning(BookmarkModel* model) {
    258   ListValue args;
    259   std::string json_args;
    260   base::JSONWriter::Write(&args, false, &json_args);
    261   DispatchEvent(model->profile(),
    262                 keys::kOnBookmarkImportBegan,
    263                 json_args);
    264 }
    265 
    266 void ExtensionBookmarkEventRouter::BookmarkImportEnding(BookmarkModel* model) {
    267   ListValue args;
    268   std::string json_args;
    269   base::JSONWriter::Write(&args, false, &json_args);
    270   DispatchEvent(model->profile(),
    271                 keys::kOnBookmarkImportEnded,
    272                 json_args);
    273 }
    274 
    275 bool GetBookmarksFunction::RunImpl() {
    276   BookmarkModel* model = profile()->GetBookmarkModel();
    277   scoped_ptr<ListValue> json(new ListValue());
    278   Value* arg0;
    279   EXTENSION_FUNCTION_VALIDATE(args_->Get(0, &arg0));
    280   if (arg0->IsType(Value::TYPE_LIST)) {
    281     const ListValue* ids = static_cast<const ListValue*>(arg0);
    282     size_t count = ids->GetSize();
    283     EXTENSION_FUNCTION_VALIDATE(count > 0);
    284     for (size_t i = 0; i < count; ++i) {
    285       int64 id;
    286       std::string id_string;
    287       EXTENSION_FUNCTION_VALIDATE(ids->GetString(i, &id_string));
    288       if (!GetBookmarkIdAsInt64(id_string, &id))
    289         return false;
    290       const BookmarkNode* node = model->GetNodeByID(id);
    291       if (!node) {
    292         error_ = keys::kNoNodeError;
    293         return false;
    294       } else {
    295         extension_bookmark_helpers::AddNode(node, json.get(), false);
    296       }
    297     }
    298   } else {
    299     int64 id;
    300     std::string id_string;
    301     EXTENSION_FUNCTION_VALIDATE(arg0->GetAsString(&id_string));
    302     if (!GetBookmarkIdAsInt64(id_string, &id))
    303       return false;
    304     const BookmarkNode* node = model->GetNodeByID(id);
    305     if (!node) {
    306       error_ = keys::kNoNodeError;
    307       return false;
    308     }
    309     extension_bookmark_helpers::AddNode(node, json.get(), false);
    310   }
    311 
    312   result_.reset(json.release());
    313   return true;
    314 }
    315 
    316 bool GetBookmarkChildrenFunction::RunImpl() {
    317   BookmarkModel* model = profile()->GetBookmarkModel();
    318   int64 id;
    319   std::string id_string;
    320   EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &id_string));
    321   if (!GetBookmarkIdAsInt64(id_string, &id))
    322     return false;
    323   scoped_ptr<ListValue> json(new ListValue());
    324   const BookmarkNode* node = model->GetNodeByID(id);
    325   if (!node) {
    326     error_ = keys::kNoNodeError;
    327     return false;
    328   }
    329   int child_count = node->child_count();
    330   for (int i = 0; i < child_count; ++i) {
    331     const BookmarkNode* child = node->GetChild(i);
    332     extension_bookmark_helpers::AddNode(child, json.get(), false);
    333   }
    334 
    335   result_.reset(json.release());
    336   return true;
    337 }
    338 
    339 bool GetBookmarkRecentFunction::RunImpl() {
    340   int number_of_items;
    341   EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &number_of_items));
    342   if (number_of_items < 1)
    343     return false;
    344 
    345   BookmarkModel* model = profile()->GetBookmarkModel();
    346   ListValue* json = new ListValue();
    347   std::vector<const BookmarkNode*> nodes;
    348   bookmark_utils::GetMostRecentlyAddedEntries(model, number_of_items, &nodes);
    349   std::vector<const BookmarkNode*>::iterator i = nodes.begin();
    350   for (; i != nodes.end(); ++i) {
    351     const BookmarkNode* node = *i;
    352     extension_bookmark_helpers::AddNode(node, json, false);
    353   }
    354   result_.reset(json);
    355   return true;
    356 }
    357 
    358 bool GetBookmarkTreeFunction::RunImpl() {
    359   BookmarkModel* model = profile()->GetBookmarkModel();
    360   scoped_ptr<ListValue> json(new ListValue());
    361   const BookmarkNode* node = model->root_node();
    362   extension_bookmark_helpers::AddNode(node, json.get(), true);
    363   result_.reset(json.release());
    364   return true;
    365 }
    366 
    367 bool SearchBookmarksFunction::RunImpl() {
    368   string16 query;
    369   EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &query));
    370 
    371   BookmarkModel* model = profile()->GetBookmarkModel();
    372   ListValue* json = new ListValue();
    373   std::string lang = profile()->GetPrefs()->GetString(prefs::kAcceptLanguages);
    374   std::vector<const BookmarkNode*> nodes;
    375   bookmark_utils::GetBookmarksContainingText(model, query,
    376                                              std::numeric_limits<int>::max(),
    377                                              lang, &nodes);
    378   std::vector<const BookmarkNode*>::iterator i = nodes.begin();
    379   for (; i != nodes.end(); ++i) {
    380     const BookmarkNode* node = *i;
    381     extension_bookmark_helpers::AddNode(node, json, false);
    382   }
    383 
    384   result_.reset(json);
    385   return true;
    386 }
    387 
    388 // static
    389 bool RemoveBookmarkFunction::ExtractIds(const ListValue* args,
    390                                         std::list<int64>* ids,
    391                                         bool* invalid_id) {
    392   std::string id_string;
    393   if (!args->GetString(0, &id_string))
    394     return false;
    395   int64 id;
    396   if (base::StringToInt64(id_string, &id))
    397     ids->push_back(id);
    398   else
    399     *invalid_id = true;
    400   return true;
    401 }
    402 
    403 bool RemoveBookmarkFunction::RunImpl() {
    404   if (!EditBookmarksEnabled())
    405     return false;
    406   std::list<int64> ids;
    407   bool invalid_id = false;
    408   EXTENSION_FUNCTION_VALIDATE(ExtractIds(args_.get(), &ids, &invalid_id));
    409   if (invalid_id) {
    410     error_ = keys::kInvalidIdError;
    411     return false;
    412   }
    413   bool recursive = false;
    414   if (name() == RemoveTreeBookmarkFunction::function_name())
    415     recursive = true;
    416 
    417   BookmarkModel* model = profile()->GetBookmarkModel();
    418   size_t count = ids.size();
    419   EXTENSION_FUNCTION_VALIDATE(count > 0);
    420   for (std::list<int64>::iterator it = ids.begin(); it != ids.end(); ++it) {
    421     if (!extension_bookmark_helpers::RemoveNode(model, *it, recursive, &error_))
    422       return false;
    423   }
    424   return true;
    425 }
    426 
    427 bool CreateBookmarkFunction::RunImpl() {
    428   if (!EditBookmarksEnabled())
    429     return false;
    430   DictionaryValue* json;
    431   EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &json));
    432   EXTENSION_FUNCTION_VALIDATE(json != NULL);
    433 
    434   BookmarkModel* model = profile()->GetBookmarkModel();
    435   int64 parentId;
    436   if (!json->HasKey(keys::kParentIdKey)) {
    437     // Optional, default to "other bookmarks".
    438     parentId = model->other_node()->id();
    439   } else {
    440     std::string parentId_string;
    441     EXTENSION_FUNCTION_VALIDATE(json->GetString(keys::kParentIdKey,
    442                                                 &parentId_string));
    443     if (!GetBookmarkIdAsInt64(parentId_string, &parentId))
    444       return false;
    445   }
    446   const BookmarkNode* parent = model->GetNodeByID(parentId);
    447   if (!parent) {
    448     error_ = keys::kNoParentError;
    449     return false;
    450   }
    451   if (parent->parent() == NULL) {  // Can't create children of the root.
    452     error_ = keys::kModifySpecialError;
    453     return false;
    454   }
    455 
    456   int index;
    457   if (!json->HasKey(keys::kIndexKey)) {  // Optional (defaults to end).
    458     index = parent->child_count();
    459   } else {
    460     EXTENSION_FUNCTION_VALIDATE(json->GetInteger(keys::kIndexKey, &index));
    461     if (index > parent->child_count() || index < 0) {
    462       error_ = keys::kInvalidIndexError;
    463       return false;
    464     }
    465   }
    466 
    467   string16 title;
    468   json->GetString(keys::kTitleKey, &title);  // Optional.
    469   std::string url_string;
    470   json->GetString(keys::kUrlKey, &url_string);  // Optional.
    471   GURL url(url_string);
    472   if (!url.is_empty() && !url.is_valid()) {
    473     error_ = keys::kInvalidUrlError;
    474     return false;
    475   }
    476 
    477   const BookmarkNode* node;
    478   if (url_string.length())
    479     node = model->AddURL(parent, index, title, url);
    480   else
    481     node = model->AddFolder(parent, index, title);
    482   DCHECK(node);
    483   if (!node) {
    484     error_ = keys::kNoNodeError;
    485     return false;
    486   }
    487 
    488   DictionaryValue* ret =
    489       extension_bookmark_helpers::GetNodeDictionary(node, false, false);
    490   result_.reset(ret);
    491 
    492   return true;
    493 }
    494 
    495 // static
    496 bool MoveBookmarkFunction::ExtractIds(const ListValue* args,
    497                                       std::list<int64>* ids,
    498                                       bool* invalid_id) {
    499   // For now, Move accepts ID parameters in the same way as an Update.
    500   return UpdateBookmarkFunction::ExtractIds(args, ids, invalid_id);
    501 }
    502 
    503 bool MoveBookmarkFunction::RunImpl() {
    504   if (!EditBookmarksEnabled())
    505     return false;
    506   std::list<int64> ids;
    507   bool invalid_id = false;
    508   EXTENSION_FUNCTION_VALIDATE(ExtractIds(args_.get(), &ids, &invalid_id));
    509   if (invalid_id) {
    510     error_ = keys::kInvalidIdError;
    511     return false;
    512   }
    513   EXTENSION_FUNCTION_VALIDATE(ids.size() == 1);
    514 
    515   DictionaryValue* destination;
    516   EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &destination));
    517 
    518   BookmarkModel* model = profile()->GetBookmarkModel();
    519   const BookmarkNode* node = model->GetNodeByID(ids.front());
    520   if (!node) {
    521     error_ = keys::kNoNodeError;
    522     return false;
    523   }
    524   if (node == model->root_node() ||
    525       node == model->other_node() ||
    526       node == model->GetBookmarkBarNode()) {
    527     error_ = keys::kModifySpecialError;
    528     return false;
    529   }
    530 
    531   const BookmarkNode* parent = NULL;
    532   if (!destination->HasKey(keys::kParentIdKey)) {
    533     // Optional, defaults to current parent.
    534     parent = node->parent();
    535   } else {
    536     std::string parentId_string;
    537     EXTENSION_FUNCTION_VALIDATE(destination->GetString(keys::kParentIdKey,
    538         &parentId_string));
    539     int64 parentId;
    540     if (!GetBookmarkIdAsInt64(parentId_string, &parentId))
    541       return false;
    542 
    543     parent = model->GetNodeByID(parentId);
    544   }
    545   if (!parent) {
    546     error_ = keys::kNoParentError;
    547     // TODO(erikkay) return an error message.
    548     return false;
    549   }
    550   if (parent == model->root_node()) {
    551     error_ = keys::kModifySpecialError;
    552     return false;
    553   }
    554 
    555   int index;
    556   if (destination->HasKey(keys::kIndexKey)) {  // Optional (defaults to end).
    557     EXTENSION_FUNCTION_VALIDATE(destination->GetInteger(keys::kIndexKey,
    558                                                         &index));
    559     if (index > parent->child_count() || index < 0) {
    560       error_ = keys::kInvalidIndexError;
    561       return false;
    562     }
    563   } else {
    564     index = parent->child_count();
    565   }
    566 
    567   model->Move(node, parent, index);
    568 
    569   DictionaryValue* ret =
    570       extension_bookmark_helpers::GetNodeDictionary(node, false, false);
    571   result_.reset(ret);
    572 
    573   return true;
    574 }
    575 
    576 // static
    577 bool UpdateBookmarkFunction::ExtractIds(const ListValue* args,
    578                                         std::list<int64>* ids,
    579                                         bool* invalid_id) {
    580   // For now, Update accepts ID parameters in the same way as an Remove.
    581   return RemoveBookmarkFunction::ExtractIds(args, ids, invalid_id);
    582 }
    583 
    584 bool UpdateBookmarkFunction::RunImpl() {
    585   if (!EditBookmarksEnabled())
    586     return false;
    587   std::list<int64> ids;
    588   bool invalid_id = false;
    589   EXTENSION_FUNCTION_VALIDATE(ExtractIds(args_.get(), &ids, &invalid_id));
    590   if (invalid_id) {
    591     error_ = keys::kInvalidIdError;
    592     return false;
    593   }
    594   EXTENSION_FUNCTION_VALIDATE(ids.size() == 1);
    595 
    596   DictionaryValue* updates;
    597   EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &updates));
    598 
    599   // Optional but we need to distinguish non present from an empty title.
    600   string16 title;
    601   const bool has_title = updates->GetString(keys::kTitleKey, &title);
    602 
    603   // Optional.
    604   std::string url_string;
    605   updates->GetString(keys::kUrlKey, &url_string);
    606   GURL url(url_string);
    607   if (!url_string.empty() && !url.is_valid()) {
    608     error_ = keys::kInvalidUrlError;
    609     return false;
    610   }
    611 
    612   BookmarkModel* model = profile()->GetBookmarkModel();
    613   const BookmarkNode* node = model->GetNodeByID(ids.front());
    614   if (!node) {
    615     error_ = keys::kNoNodeError;
    616     return false;
    617   }
    618   if (node == model->root_node() ||
    619       node == model->other_node() ||
    620       node == model->GetBookmarkBarNode()) {
    621     error_ = keys::kModifySpecialError;
    622     return false;
    623   }
    624   if (has_title)
    625     model->SetTitle(node, title);
    626   if (!url.is_empty())
    627     model->SetURL(node, url);
    628 
    629   DictionaryValue* ret =
    630       extension_bookmark_helpers::GetNodeDictionary(node, false, false);
    631   result_.reset(ret);
    632 
    633   return true;
    634 }
    635 
    636 // Mapper superclass for BookmarkFunctions.
    637 template <typename BucketIdType>
    638 class BookmarkBucketMapper : public BucketMapper {
    639  public:
    640   virtual ~BookmarkBucketMapper() { STLDeleteValues(&buckets_); }
    641  protected:
    642   Bucket* GetBucket(const BucketIdType& id) {
    643     Bucket* b = buckets_[id];
    644     if (b == NULL) {
    645       b = new Bucket();
    646       buckets_[id] = b;
    647     }
    648     return b;
    649   }
    650  private:
    651   std::map<BucketIdType, Bucket*> buckets_;
    652 };
    653 
    654 // Mapper for 'bookmarks.create'.  Maps "same input to bookmarks.create" to a
    655 // unique bucket.
    656 class CreateBookmarkBucketMapper : public BookmarkBucketMapper<std::string> {
    657  public:
    658   explicit CreateBookmarkBucketMapper(Profile* profile) : profile_(profile) {}
    659   // TODO(tim): This should share code with CreateBookmarkFunction::RunImpl,
    660   // but I can't figure out a good way to do that with all the macros.
    661   virtual void GetBucketsForArgs(const ListValue* args, BucketList* buckets) {
    662     DictionaryValue* json;
    663     if (!args->GetDictionary(0, &json))
    664       return;
    665 
    666     std::string parent_id;
    667     if (json->HasKey(keys::kParentIdKey)) {
    668       if (!json->GetString(keys::kParentIdKey, &parent_id))
    669         return;
    670     }
    671     BookmarkModel* model = profile_->GetBookmarkModel();
    672 
    673     int64 parent_id_int64;
    674     base::StringToInt64(parent_id, &parent_id_int64);
    675     const BookmarkNode* parent = model->GetNodeByID(parent_id_int64);
    676     if (!parent)
    677       return;
    678 
    679     std::string bucket_id = UTF16ToUTF8(parent->GetTitle());
    680     std::string title;
    681     json->GetString(keys::kTitleKey, &title);
    682     std::string url_string;
    683     json->GetString(keys::kUrlKey, &url_string);
    684 
    685     bucket_id += title;
    686     bucket_id += url_string;
    687     // 20 bytes (SHA1 hash length) is very likely less than most of the
    688     // |bucket_id| strings we construct here, so we hash it to save space.
    689     buckets->push_back(GetBucket(base::SHA1HashString(bucket_id)));
    690   }
    691  private:
    692   Profile* profile_;
    693 };
    694 
    695 // Mapper for 'bookmarks.remove'.
    696 class RemoveBookmarksBucketMapper : public BookmarkBucketMapper<std::string> {
    697  public:
    698   explicit RemoveBookmarksBucketMapper(Profile* profile) : profile_(profile) {}
    699   virtual void GetBucketsForArgs(const ListValue* args, BucketList* buckets) {
    700     typedef std::list<int64> IdList;
    701     IdList ids;
    702     bool invalid_id = false;
    703     if (!RemoveBookmarkFunction::ExtractIds(args, &ids, &invalid_id) ||
    704         invalid_id) {
    705       return;
    706     }
    707 
    708     for (IdList::iterator it = ids.begin(); it != ids.end(); ++it) {
    709       BookmarkModel* model = profile_->GetBookmarkModel();
    710       const BookmarkNode* node = model->GetNodeByID(*it);
    711       if (!node || !node->parent())
    712         return;
    713 
    714       std::string bucket_id;
    715       bucket_id += UTF16ToUTF8(node->parent()->GetTitle());
    716       bucket_id += UTF16ToUTF8(node->GetTitle());
    717       bucket_id += node->GetURL().spec();
    718       buckets->push_back(GetBucket(base::SHA1HashString(bucket_id)));
    719     }
    720   }
    721  private:
    722   Profile* profile_;
    723 };
    724 
    725 // Mapper for any bookmark function accepting bookmark IDs as parameters, where
    726 // a distinct ID corresponds to a single item in terms of quota limiting.  This
    727 // is inappropriate for bookmarks.remove, for example, since repeated removals
    728 // of the same item will actually have a different ID each time.
    729 template <class FunctionType>
    730 class BookmarkIdMapper : public BookmarkBucketMapper<int64> {
    731  public:
    732   typedef std::list<int64> IdList;
    733   virtual void GetBucketsForArgs(const ListValue* args, BucketList* buckets) {
    734     IdList ids;
    735     bool invalid_id = false;
    736     if (!FunctionType::ExtractIds(args, &ids, &invalid_id) || invalid_id)
    737       return;
    738     for (IdList::iterator it = ids.begin(); it != ids.end(); ++it)
    739       buckets->push_back(GetBucket(*it));
    740   }
    741 };
    742 
    743 // Builds heuristics for all BookmarkFunctions using specialized BucketMappers.
    744 class BookmarksQuotaLimitFactory {
    745  public:
    746   // For id-based bookmark functions.
    747   template <class FunctionType>
    748   static void Build(QuotaLimitHeuristics* heuristics) {
    749     BuildWithMappers(heuristics, new BookmarkIdMapper<FunctionType>(),
    750                                  new BookmarkIdMapper<FunctionType>());
    751   }
    752 
    753   // For bookmarks.create.
    754   static void BuildForCreate(QuotaLimitHeuristics* heuristics,
    755                              Profile* profile) {
    756     BuildWithMappers(heuristics, new CreateBookmarkBucketMapper(profile),
    757                                  new CreateBookmarkBucketMapper(profile));
    758   }
    759 
    760   // For bookmarks.remove.
    761   static void BuildForRemove(QuotaLimitHeuristics* heuristics,
    762                              Profile* profile) {
    763     BuildWithMappers(heuristics, new RemoveBookmarksBucketMapper(profile),
    764                                  new RemoveBookmarksBucketMapper(profile));
    765   }
    766 
    767  private:
    768   static void BuildWithMappers(QuotaLimitHeuristics* heuristics,
    769       BucketMapper* short_mapper, BucketMapper* long_mapper) {
    770     TimedLimit* timed = new TimedLimit(kLongLimitConfig, long_mapper);
    771     // A max of two operations per minute, sustained over 10 minutes.
    772     SustainedLimit* sustained = new SustainedLimit(TimeDelta::FromMinutes(10),
    773         kShortLimitConfig, short_mapper);
    774     heuristics->push_back(timed);
    775     heuristics->push_back(sustained);
    776   }
    777 
    778   // The quota configurations used for all BookmarkFunctions.
    779   static const Config kShortLimitConfig;
    780   static const Config kLongLimitConfig;
    781 
    782   DISALLOW_IMPLICIT_CONSTRUCTORS(BookmarksQuotaLimitFactory);
    783 };
    784 
    785 const Config BookmarksQuotaLimitFactory::kShortLimitConfig = {
    786   2,                         // 2 tokens per interval.
    787   TimeDelta::FromMinutes(1)  // 1 minute long refill interval.
    788 };
    789 
    790 const Config BookmarksQuotaLimitFactory::kLongLimitConfig = {
    791   100,                       // 100 tokens per interval.
    792   TimeDelta::FromHours(1)    // 1 hour long refill interval.
    793 };
    794 
    795 // And finally, building the individual heuristics for each function.
    796 void RemoveBookmarkFunction::GetQuotaLimitHeuristics(
    797     QuotaLimitHeuristics* heuristics) const {
    798   BookmarksQuotaLimitFactory::BuildForRemove(heuristics, profile());
    799 }
    800 
    801 void MoveBookmarkFunction::GetQuotaLimitHeuristics(
    802     QuotaLimitHeuristics* heuristics) const {
    803   BookmarksQuotaLimitFactory::Build<MoveBookmarkFunction>(heuristics);
    804 }
    805 
    806 void UpdateBookmarkFunction::GetQuotaLimitHeuristics(
    807     QuotaLimitHeuristics* heuristics) const {
    808   BookmarksQuotaLimitFactory::Build<UpdateBookmarkFunction>(heuristics);
    809 };
    810 
    811 void CreateBookmarkFunction::GetQuotaLimitHeuristics(
    812     QuotaLimitHeuristics* heuristics) const {
    813   BookmarksQuotaLimitFactory::BuildForCreate(heuristics, profile());
    814 }
    815 
    816 BookmarksIOFunction::BookmarksIOFunction() {}
    817 
    818 BookmarksIOFunction::~BookmarksIOFunction() {
    819   // There may be pending file dialogs, we need to tell them that we've gone
    820   // away so they don't try and call back to us.
    821   if (select_file_dialog_.get())
    822     select_file_dialog_->ListenerDestroyed();
    823 }
    824 
    825 void BookmarksIOFunction::SelectFile(SelectFileDialog::Type type) {
    826   // GetDefaultFilepathForBookmarkExport() might have to touch the filesystem
    827   // (stat or access, for example), so this requires a thread with IO allowed.
    828   if (!BrowserThread::CurrentlyOn(BrowserThread::FILE)) {
    829     BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
    830         NewRunnableMethod(this, &BookmarksIOFunction::SelectFile, type));
    831     return;
    832   }
    833 
    834   // Pre-populating the filename field in case this is a SELECT_SAVEAS_FILE
    835   // dialog. If not, there is no filename field in the dialog box.
    836   FilePath default_path;
    837   if (type == SelectFileDialog::SELECT_SAVEAS_FILE)
    838     default_path = GetDefaultFilepathForBookmarkExport();
    839   else
    840     DCHECK(type == SelectFileDialog::SELECT_OPEN_FILE);
    841 
    842   // After getting the |default_path|, ask the UI to display the file dialog.
    843   BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
    844       NewRunnableMethod(this, &BookmarksIOFunction::ShowSelectFileDialog,
    845                         type, default_path));
    846 }
    847 
    848 void BookmarksIOFunction::ShowSelectFileDialog(SelectFileDialog::Type type,
    849                                                FilePath default_path) {
    850   // Balanced in one of the three callbacks of SelectFileDialog:
    851   // either FileSelectionCanceled, MultiFilesSelected, or FileSelected
    852   AddRef();
    853   select_file_dialog_ = SelectFileDialog::Create(this);
    854   SelectFileDialog::FileTypeInfo file_type_info;
    855   file_type_info.extensions.resize(1);
    856   file_type_info.extensions[0].push_back(FILE_PATH_LITERAL("html"));
    857 
    858   TabContents* tab_contents = dispatcher()->delegate()->
    859       associated_tab_contents();
    860 
    861   // |tab_contents| can be NULL (for background pages), which is fine. In such
    862   // a case if file-selection dialogs are forbidden by policy, we will not
    863   // show an InfoBar, which is better than letting one appear out of the blue.
    864   select_file_dialog_->SelectFile(type,
    865                                   string16(),
    866                                   default_path,
    867                                   &file_type_info,
    868                                   0,
    869                                   FILE_PATH_LITERAL(""),
    870                                   tab_contents,
    871                                   NULL,
    872                                   NULL);
    873 }
    874 
    875 void BookmarksIOFunction::FileSelectionCanceled(void* params) {
    876   Release();  // Balanced in BookmarksIOFunction::SelectFile()
    877 }
    878 
    879 void BookmarksIOFunction::MultiFilesSelected(
    880     const std::vector<FilePath>& files, void* params) {
    881   Release();  // Balanced in BookmarsIOFunction::SelectFile()
    882   NOTREACHED() << "Should not be able to select multiple files";
    883 }
    884 
    885 bool ImportBookmarksFunction::RunImpl() {
    886   if (!EditBookmarksEnabled())
    887     return false;
    888   SelectFile(SelectFileDialog::SELECT_OPEN_FILE);
    889   return true;
    890 }
    891 
    892 void ImportBookmarksFunction::FileSelected(const FilePath& path,
    893                                            int index,
    894                                            void* params) {
    895   scoped_refptr<ImporterHost> importer_host(new ImporterHost);
    896   importer::SourceProfile source_profile;
    897   source_profile.importer_type = importer::BOOKMARKS_HTML;
    898   source_profile.source_path = path;
    899   importer_host->StartImportSettings(source_profile,
    900                                      profile(),
    901                                      importer::FAVORITES,
    902                                      new ProfileWriter(profile()),
    903                                      true);
    904   Release();  // Balanced in BookmarksIOFunction::SelectFile()
    905 }
    906 
    907 bool ExportBookmarksFunction::RunImpl() {
    908   SelectFile(SelectFileDialog::SELECT_SAVEAS_FILE);
    909   return true;
    910 }
    911 
    912 void ExportBookmarksFunction::FileSelected(const FilePath& path,
    913                                            int index,
    914                                            void* params) {
    915   bookmark_html_writer::WriteBookmarks(profile(), path, NULL);
    916   Release();  // Balanced in BookmarksIOFunction::SelectFile()
    917 }
    918