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