bookmark_manager_private_api.cc revision 1e9bf3e0803691d0a228da41fc608347b6db4340
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/bookmark_manager_private/bookmark_manager_private_api.h" 6 7#include <vector> 8 9#include "base/json/json_writer.h" 10#include "base/prefs/pref_service.h" 11#include "base/strings/string_number_conversions.h" 12#include "base/values.h" 13#include "chrome/browser/bookmarks/bookmark_model.h" 14#include "chrome/browser/bookmarks/bookmark_model_factory.h" 15#include "chrome/browser/bookmarks/bookmark_node_data.h" 16#include "chrome/browser/bookmarks/bookmark_stats.h" 17#include "chrome/browser/bookmarks/bookmark_utils.h" 18#include "chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api_constants.h" 19#include "chrome/browser/extensions/api/bookmarks/bookmark_api_constants.h" 20#include "chrome/browser/extensions/api/bookmarks/bookmark_api_helpers.h" 21#include "chrome/browser/extensions/event_router.h" 22#include "chrome/browser/extensions/extension_function_dispatcher.h" 23#include "chrome/browser/extensions/extension_system.h" 24#include "chrome/browser/extensions/extension_web_ui.h" 25#include "chrome/browser/profiles/profile.h" 26#include "chrome/browser/ui/bookmarks/bookmark_drag_drop.h" 27#include "chrome/common/extensions/api/bookmark_manager_private.h" 28#include "chrome/common/pref_names.h" 29#include "components/user_prefs/user_prefs.h" 30#include "content/public/browser/render_view_host.h" 31#include "content/public/browser/web_contents.h" 32#include "content/public/browser/web_contents_view.h" 33#include "content/public/browser/web_ui.h" 34#include "extensions/browser/view_type_utils.h" 35#include "grit/generated_resources.h" 36#include "ui/base/l10n/l10n_util.h" 37#include "ui/base/webui/web_ui_util.h" 38 39#if defined(OS_WIN) 40#include "win8/util/win8_util.h" 41#endif // OS_WIN 42 43namespace extensions { 44 45namespace bookmark_keys = bookmark_api_constants; 46namespace bookmark_manager_private = api::bookmark_manager_private; 47namespace CanPaste = api::bookmark_manager_private::CanPaste; 48namespace Copy = api::bookmark_manager_private::Copy; 49namespace Cut = api::bookmark_manager_private::Cut; 50namespace Drop = api::bookmark_manager_private::Drop; 51namespace GetSubtree = api::bookmark_manager_private::GetSubtree; 52namespace manager_keys = bookmark_manager_api_constants; 53namespace Paste = api::bookmark_manager_private::Paste; 54namespace RemoveTrees = api::bookmark_manager_private::RemoveTrees; 55namespace SortChildren = api::bookmark_manager_private::SortChildren; 56namespace StartDrag = api::bookmark_manager_private::StartDrag; 57 58using content::WebContents; 59 60namespace { 61 62// Returns a single bookmark node from the argument ID. 63// This returns NULL in case of failure. 64const BookmarkNode* GetNodeFromString( 65 BookmarkModel* model, const std::string& id_string) { 66 int64 id; 67 if (!base::StringToInt64(id_string, &id)) 68 return NULL; 69 return model->GetNodeByID(id); 70} 71 72// Gets a vector of bookmark nodes from the argument list of IDs. 73// This returns false in the case of failure. 74bool GetNodesFromVector(BookmarkModel* model, 75 const std::vector<std::string>& id_strings, 76 std::vector<const BookmarkNode*>* nodes) { 77 78 if (id_strings.empty()) 79 return false; 80 81 for (size_t i = 0; i < id_strings.size(); ++i) { 82 const BookmarkNode* node = GetNodeFromString(model, id_strings[i]); 83 if (!node) 84 return false; 85 nodes->push_back(node); 86 } 87 88 return true; 89} 90 91// Recursively adds a node to a list. This is by used |BookmarkNodeDataToJSON| 92// when the data comes from the current profile. In this case we have a 93// BookmarkNode since we got the data from the current profile. 94void AddNodeToList(base::ListValue* list, const BookmarkNode& node) { 95 base::DictionaryValue* dict = new base::DictionaryValue(); 96 97 // Add id and parentId so we can associate the data with existing nodes on the 98 // client side. 99 std::string id_string = base::Int64ToString(node.id()); 100 dict->SetString(bookmark_keys::kIdKey, id_string); 101 102 std::string parent_id_string = base::Int64ToString(node.parent()->id()); 103 dict->SetString(bookmark_keys::kParentIdKey, parent_id_string); 104 105 if (node.is_url()) 106 dict->SetString(bookmark_keys::kUrlKey, node.url().spec()); 107 108 dict->SetString(bookmark_keys::kTitleKey, node.GetTitle()); 109 110 base::ListValue* children = new base::ListValue(); 111 for (int i = 0; i < node.child_count(); ++i) 112 AddNodeToList(children, *node.GetChild(i)); 113 dict->Set(bookmark_keys::kChildrenKey, children); 114 115 list->Append(dict); 116} 117 118// Recursively adds an element to a list. This is used by 119// |BookmarkNodeDataToJSON| when the data comes from a different profile. When 120// the data comes from a different profile we do not have any IDs or parent IDs. 121void AddElementToList(base::ListValue* list, 122 const BookmarkNodeData::Element& element) { 123 base::DictionaryValue* dict = new base::DictionaryValue(); 124 125 if (element.is_url) 126 dict->SetString(bookmark_keys::kUrlKey, element.url.spec()); 127 128 dict->SetString(bookmark_keys::kTitleKey, element.title); 129 130 base::ListValue* children = new base::ListValue(); 131 for (size_t i = 0; i < element.children.size(); ++i) 132 AddElementToList(children, element.children[i]); 133 dict->Set(bookmark_keys::kChildrenKey, children); 134 135 list->Append(dict); 136} 137 138// Builds the JSON structure based on the BookmarksDragData. 139void BookmarkNodeDataToJSON(Profile* profile, const BookmarkNodeData& data, 140 base::ListValue* args) { 141 bool same_profile = data.IsFromProfile(profile); 142 base::DictionaryValue* value = new base::DictionaryValue(); 143 value->SetBoolean(manager_keys::kSameProfileKey, same_profile); 144 145 base::ListValue* list = new base::ListValue(); 146 if (same_profile) { 147 std::vector<const BookmarkNode*> nodes = data.GetNodes(profile); 148 for (size_t i = 0; i < nodes.size(); ++i) 149 AddNodeToList(list, *nodes[i]); 150 } else { 151 // We do not have an node IDs when the data comes from a different profile. 152 std::vector<BookmarkNodeData::Element> elements = data.elements; 153 for (size_t i = 0; i < elements.size(); ++i) 154 AddElementToList(list, elements[i]); 155 } 156 value->Set(manager_keys::kElementsKey, list); 157 158 args->Append(value); 159} 160 161} // namespace 162 163BookmarkManagerPrivateEventRouter::BookmarkManagerPrivateEventRouter( 164 Profile* profile, 165 content::WebContents* web_contents) 166 : profile_(profile), 167 web_contents_(web_contents) { 168 BookmarkTabHelper* bookmark_tab_helper = 169 BookmarkTabHelper::FromWebContents(web_contents_); 170 bookmark_tab_helper->set_bookmark_drag_delegate(this); 171} 172 173BookmarkManagerPrivateEventRouter::~BookmarkManagerPrivateEventRouter() { 174 BookmarkTabHelper* bookmark_tab_helper = 175 BookmarkTabHelper::FromWebContents(web_contents_); 176 if (bookmark_tab_helper->bookmark_drag_delegate() == this) 177 bookmark_tab_helper->set_bookmark_drag_delegate(NULL); 178} 179 180void BookmarkManagerPrivateEventRouter::DispatchEvent( 181 const std::string& event_name, 182 scoped_ptr<base::ListValue> args) { 183 if (!ExtensionSystem::Get(profile_)->event_router()) 184 return; 185 186 scoped_ptr<Event> event(new Event(event_name, args.Pass())); 187 ExtensionSystem::Get(profile_)->event_router()->BroadcastEvent(event.Pass()); 188} 189 190void BookmarkManagerPrivateEventRouter::DispatchDragEvent( 191 const BookmarkNodeData& data, 192 const std::string& event_name) { 193 if (data.size() == 0) 194 return; 195 196 scoped_ptr<base::ListValue> args(new base::ListValue()); 197 BookmarkNodeDataToJSON(profile_, data, args.get()); 198 DispatchEvent(event_name, args.Pass()); 199} 200 201void BookmarkManagerPrivateEventRouter::OnDragEnter( 202 const BookmarkNodeData& data) { 203 DispatchDragEvent(data, bookmark_manager_private::OnDragEnter::kEventName); 204} 205 206void BookmarkManagerPrivateEventRouter::OnDragOver( 207 const BookmarkNodeData& data) { 208 // Intentionally empty since these events happens too often and floods the 209 // message queue. We do not need this event for the bookmark manager anyway. 210} 211 212void BookmarkManagerPrivateEventRouter::OnDragLeave( 213 const BookmarkNodeData& data) { 214 DispatchDragEvent(data, bookmark_manager_private::OnDragLeave::kEventName); 215} 216 217void BookmarkManagerPrivateEventRouter::OnDrop(const BookmarkNodeData& data) { 218 DispatchDragEvent(data, bookmark_manager_private::OnDrop::kEventName); 219 220 // Make a copy that is owned by this instance. 221 ClearBookmarkNodeData(); 222 bookmark_drag_data_ = data; 223} 224 225const BookmarkNodeData* 226BookmarkManagerPrivateEventRouter::GetBookmarkNodeData() { 227 if (bookmark_drag_data_.is_valid()) 228 return &bookmark_drag_data_; 229 return NULL; 230} 231 232void BookmarkManagerPrivateEventRouter::ClearBookmarkNodeData() { 233 bookmark_drag_data_.Clear(); 234} 235 236bool ClipboardBookmarkManagerFunction::CopyOrCut(bool cut, 237 const std::vector<std::string>& id_list) { 238 BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile()); 239 std::vector<const BookmarkNode*> nodes; 240 EXTENSION_FUNCTION_VALIDATE(GetNodesFromVector(model, id_list, &nodes)); 241 bookmark_utils::CopyToClipboard(model, nodes, cut); 242 return true; 243} 244 245bool BookmarkManagerPrivateCopyFunction::RunImpl() { 246 scoped_ptr<Copy::Params> params(Copy::Params::Create(*args_)); 247 EXTENSION_FUNCTION_VALIDATE(params); 248 return CopyOrCut(false, params->id_list); 249} 250 251bool BookmarkManagerPrivateCutFunction::RunImpl() { 252 if (!EditBookmarksEnabled()) 253 return false; 254 255 scoped_ptr<Cut::Params> params(Cut::Params::Create(*args_)); 256 EXTENSION_FUNCTION_VALIDATE(params); 257 return CopyOrCut(true, params->id_list); 258} 259 260bool BookmarkManagerPrivatePasteFunction::RunImpl() { 261 if (!EditBookmarksEnabled()) 262 return false; 263 264 scoped_ptr<Paste::Params> params(Paste::Params::Create(*args_)); 265 EXTENSION_FUNCTION_VALIDATE(params); 266 BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile()); 267 const BookmarkNode* parent_node = GetNodeFromString(model, params->parent_id); 268 if (!parent_node) { 269 error_ = bookmark_keys::kNoParentError; 270 return false; 271 } 272 bool can_paste = bookmark_utils::CanPasteFromClipboard(parent_node); 273 if (!can_paste) 274 return false; 275 276 // We want to use the highest index of the selected nodes as a destination. 277 std::vector<const BookmarkNode*> nodes; 278 // No need to test return value, if we got an empty list, we insert at end. 279 if (params->selected_id_list) 280 GetNodesFromVector(model, *params->selected_id_list, &nodes); 281 int highest_index = -1; // -1 means insert at end of list. 282 for (size_t i = 0; i < nodes.size(); ++i) { 283 // + 1 so that we insert after the selection. 284 int index = parent_node->GetIndexOf(nodes[i]) + 1; 285 if (index > highest_index) 286 highest_index = index; 287 } 288 289 bookmark_utils::PasteFromClipboard(model, parent_node, highest_index); 290 return true; 291} 292 293bool BookmarkManagerPrivateCanPasteFunction::RunImpl() { 294 if (!EditBookmarksEnabled()) 295 return false; 296 297 scoped_ptr<CanPaste::Params> params(CanPaste::Params::Create(*args_)); 298 EXTENSION_FUNCTION_VALIDATE(params); 299 300 BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile()); 301 const BookmarkNode* parent_node = GetNodeFromString(model, params->parent_id); 302 if (!parent_node) { 303 error_ = bookmark_keys::kNoParentError; 304 return false; 305 } 306 bool can_paste = bookmark_utils::CanPasteFromClipboard(parent_node); 307 SetResult(new base::FundamentalValue(can_paste)); 308 return true; 309} 310 311bool BookmarkManagerPrivateSortChildrenFunction::RunImpl() { 312 if (!EditBookmarksEnabled()) 313 return false; 314 315 scoped_ptr<SortChildren::Params> params(SortChildren::Params::Create(*args_)); 316 EXTENSION_FUNCTION_VALIDATE(params); 317 318 BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile()); 319 const BookmarkNode* parent_node = GetNodeFromString(model, params->parent_id); 320 if (!parent_node) { 321 error_ = bookmark_keys::kNoParentError; 322 return false; 323 } 324 model->SortChildren(parent_node); 325 return true; 326} 327 328bool BookmarkManagerPrivateGetStringsFunction::RunImpl() { 329 base::DictionaryValue* localized_strings = new base::DictionaryValue(); 330 331 localized_strings->SetString("title", 332 l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_TITLE)); 333 localized_strings->SetString("search_button", 334 l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_SEARCH_BUTTON)); 335 localized_strings->SetString("organize_menu", 336 l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_ORGANIZE_MENU)); 337 localized_strings->SetString("show_in_folder", 338 l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_SHOW_IN_FOLDER)); 339 localized_strings->SetString("sort", 340 l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_SORT)); 341 localized_strings->SetString("import_menu", 342 l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_IMPORT_MENU)); 343 localized_strings->SetString("export_menu", 344 l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_EXPORT_MENU)); 345 localized_strings->SetString("rename_folder", 346 l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_RENAME_FOLDER)); 347 localized_strings->SetString("edit", 348 l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_EDIT)); 349 localized_strings->SetString("should_open_all", 350 l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_SHOULD_OPEN_ALL)); 351 localized_strings->SetString("open_incognito", 352 l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_OPEN_INCOGNITO)); 353 localized_strings->SetString("open_in_new_tab", 354 l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_OPEN_IN_NEW_TAB)); 355 localized_strings->SetString("open_in_new_window", 356 l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_OPEN_IN_NEW_WINDOW)); 357 localized_strings->SetString("add_new_bookmark", 358 l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_ADD_NEW_BOOKMARK)); 359 localized_strings->SetString("new_folder", 360 l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_NEW_FOLDER)); 361 localized_strings->SetString("open_all", 362 l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_OPEN_ALL)); 363 localized_strings->SetString("open_all_new_window", 364 l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW)); 365 localized_strings->SetString("open_all_incognito", 366 l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_OPEN_ALL_INCOGNITO)); 367 localized_strings->SetString("remove", 368 l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_REMOVE)); 369 localized_strings->SetString("copy", 370 l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_COPY)); 371 localized_strings->SetString("cut", 372 l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_CUT)); 373 localized_strings->SetString("paste", 374 l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_PASTE)); 375 localized_strings->SetString("delete", 376 l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_DELETE)); 377 localized_strings->SetString("undo_delete", 378 l10n_util::GetStringUTF16(IDS_UNDO_DELETE)); 379 localized_strings->SetString("new_folder_name", 380 l10n_util::GetStringUTF16(IDS_BOOKMARK_EDITOR_NEW_FOLDER_NAME)); 381 localized_strings->SetString("name_input_placeholder", 382 l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_NAME_INPUT_PLACE_HOLDER)); 383 localized_strings->SetString("url_input_placeholder", 384 l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_URL_INPUT_PLACE_HOLDER)); 385 localized_strings->SetString("invalid_url", 386 l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_INVALID_URL)); 387 localized_strings->SetString("recent", 388 l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_RECENT)); 389 localized_strings->SetString("search", 390 l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_SEARCH)); 391 localized_strings->SetString("save", 392 l10n_util::GetStringUTF16(IDS_SAVE)); 393 localized_strings->SetString("cancel", 394 l10n_util::GetStringUTF16(IDS_CANCEL)); 395 396 webui::SetFontAndTextDirection(localized_strings); 397 398 SetResult(localized_strings); 399 400 // This is needed because unlike the rest of these functions, this class 401 // inherits from AsyncFunction directly, rather than BookmarkFunction. 402 SendResponse(true); 403 404 return true; 405} 406 407bool BookmarkManagerPrivateStartDragFunction::RunImpl() { 408 if (!EditBookmarksEnabled()) 409 return false; 410 411 scoped_ptr<StartDrag::Params> params(StartDrag::Params::Create(*args_)); 412 EXTENSION_FUNCTION_VALIDATE(params); 413 414 BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile()); 415 std::vector<const BookmarkNode*> nodes; 416 EXTENSION_FUNCTION_VALIDATE( 417 GetNodesFromVector(model, params->id_list, &nodes)); 418 419 WebContents* web_contents = 420 WebContents::FromRenderViewHost(render_view_host_); 421 if (GetViewType(web_contents) == VIEW_TYPE_TAB_CONTENTS) { 422 WebContents* web_contents = 423 dispatcher()->delegate()->GetAssociatedWebContents(); 424 CHECK(web_contents); 425 chrome::DragBookmarks( 426 GetProfile(), nodes, web_contents->GetView()->GetNativeView()); 427 428 return true; 429 } else { 430 NOTREACHED(); 431 return false; 432 } 433} 434 435bool BookmarkManagerPrivateDropFunction::RunImpl() { 436 if (!EditBookmarksEnabled()) 437 return false; 438 439 scoped_ptr<Drop::Params> params(Drop::Params::Create(*args_)); 440 EXTENSION_FUNCTION_VALIDATE(params); 441 442 BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile()); 443 444 const BookmarkNode* drop_parent = GetNodeFromString(model, params->parent_id); 445 if (!drop_parent) { 446 error_ = bookmark_keys::kNoParentError; 447 return false; 448 } 449 450 int drop_index; 451 if (params->index) 452 drop_index = *params->index; 453 else 454 drop_index = drop_parent->child_count(); 455 456 WebContents* web_contents = 457 WebContents::FromRenderViewHost(render_view_host_); 458 if (GetViewType(web_contents) == VIEW_TYPE_TAB_CONTENTS) { 459 WebContents* web_contents = 460 dispatcher()->delegate()->GetAssociatedWebContents(); 461 CHECK(web_contents); 462 ExtensionWebUI* web_ui = 463 static_cast<ExtensionWebUI*>(web_contents->GetWebUI()->GetController()); 464 CHECK(web_ui); 465 BookmarkManagerPrivateEventRouter* router = 466 web_ui->bookmark_manager_private_event_router(); 467 468 DCHECK(router); 469 const BookmarkNodeData* drag_data = router->GetBookmarkNodeData(); 470 if (drag_data == NULL) { 471 NOTREACHED() <<"Somehow we're dropping null bookmark data"; 472 return false; 473 } 474 chrome::DropBookmarks(GetProfile(), *drag_data, drop_parent, drop_index); 475 476 router->ClearBookmarkNodeData(); 477 return true; 478 } else { 479 NOTREACHED(); 480 return false; 481 } 482} 483 484bool BookmarkManagerPrivateGetSubtreeFunction::RunImpl() { 485 scoped_ptr<GetSubtree::Params> params(GetSubtree::Params::Create(*args_)); 486 EXTENSION_FUNCTION_VALIDATE(params); 487 488 BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile()); 489 const BookmarkNode* node = NULL; 490 491 if (params->id == "") { 492 node = model->root_node(); 493 } else { 494 int64 id; 495 if (!base::StringToInt64(params->id, &id)) { 496 error_ = bookmark_keys::kInvalidIdError; 497 return false; 498 } 499 node = model->GetNodeByID(id); 500 } 501 502 if (!node) { 503 error_ = bookmark_keys::kNoNodeError; 504 return false; 505 } 506 507 scoped_ptr<base::ListValue> json(new base::ListValue()); 508 if (params->folders_only) 509 bookmark_api_helpers::AddNodeFoldersOnly(node, json.get(), true); 510 else 511 bookmark_api_helpers::AddNode(node, json.get(), true); 512 SetResult(json.release()); 513 return true; 514} 515 516bool BookmarkManagerPrivateCanEditFunction::RunImpl() { 517 PrefService* prefs = user_prefs::UserPrefs::Get(GetProfile()); 518 SetResult(new base::FundamentalValue( 519 prefs->GetBoolean(prefs::kEditBookmarksEnabled))); 520 return true; 521} 522 523bool BookmarkManagerPrivateRecordLaunchFunction::RunImpl() { 524 RecordBookmarkLaunch(BOOKMARK_LAUNCH_LOCATION_MANAGER); 525 return true; 526} 527 528bool BookmarkManagerPrivateCanOpenNewWindowsFunction::RunImpl() { 529 bool can_open_new_windows = true; 530 531#if defined(OS_WIN) 532 if (win8::IsSingleWindowMetroMode()) 533 can_open_new_windows = false; 534#endif // OS_WIN 535 536 SetResult(new base::FundamentalValue(can_open_new_windows)); 537 return true; 538} 539 540bool BookmarkManagerPrivateRemoveTreesFunction::RunImpl() { 541 scoped_ptr<RemoveTrees::Params> params(RemoveTrees::Params::Create(*args_)); 542 EXTENSION_FUNCTION_VALIDATE(params); 543 544 BookmarkModel* model = BookmarkModelFactory::GetForProfile(GetProfile()); 545 int64 id; 546 for (size_t i = 0; i < params->id_list.size(); ++i) { 547 if (!base::StringToInt64(params->id_list[i], &id)) { 548 error_ = bookmark_api_constants::kInvalidIdError; 549 return false; 550 } 551 if (!bookmark_api_helpers::RemoveNode(model, id, true, &error_)) 552 return false; 553 } 554 555 return true; 556} 557 558} // namespace extensions 559