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