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