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_bookmark_manager_api.h" 6 7 #include <vector> 8 9 #include "base/json/json_writer.h" 10 #include "base/string_number_conversions.h" 11 #include "base/values.h" 12 #include "chrome/browser/bookmarks/bookmark_model.h" 13 #include "chrome/browser/bookmarks/bookmark_node_data.h" 14 #include "chrome/browser/bookmarks/bookmark_utils.h" 15 #include "chrome/browser/extensions/extension_bookmark_helpers.h" 16 #include "chrome/browser/extensions/extension_bookmarks_module_constants.h" 17 #include "chrome/browser/extensions/extension_event_router.h" 18 #include "chrome/browser/extensions/extension_web_ui.h" 19 #include "chrome/browser/profiles/profile.h" 20 #include "chrome/browser/ui/webui/chrome_url_data_manager.h" 21 #include "content/browser/renderer_host/render_view_host.h" 22 #include "content/browser/tab_contents/tab_contents.h" 23 #include "grit/generated_resources.h" 24 #include "ui/base/l10n/l10n_util.h" 25 26 namespace keys = extension_bookmarks_module_constants; 27 28 namespace { 29 30 // Returns a single bookmark node from the argument ID. 31 // This returns NULL in case of failure. 32 const BookmarkNode* GetNodeFromArguments(BookmarkModel* model, 33 const ListValue* args) { 34 std::string id_string; 35 if (!args->GetString(0, &id_string)) 36 return NULL; 37 int64 id; 38 if (!base::StringToInt64(id_string, &id)) 39 return NULL; 40 return model->GetNodeByID(id); 41 } 42 43 // Gets a vector of bookmark nodes from the argument list of IDs. 44 // This returns false in the case of failure. 45 bool GetNodesFromArguments(BookmarkModel* model, const ListValue* args, 46 size_t args_index, std::vector<const BookmarkNode*>* nodes) { 47 48 ListValue* ids; 49 if (!args->GetList(args_index, &ids)) 50 return false; 51 52 size_t count = ids->GetSize(); 53 if (count == 0) 54 return false; 55 56 for (size_t i = 0; i < count; ++i) { 57 std::string id_string; 58 if (!ids->GetString(i, &id_string)) 59 return false; 60 int64 id; 61 if (!base::StringToInt64(id_string, &id)) 62 return false; 63 const BookmarkNode* node = model->GetNodeByID(id); 64 if (!node) 65 return false; 66 nodes->push_back(node); 67 } 68 69 return true; 70 } 71 72 // Recursively adds a node to a list. This is by used |BookmarkNodeDataToJSON| 73 // when the data comes from the current profile. In this case we have a 74 // BookmarkNode since we got the data from the current profile. 75 void AddNodeToList(ListValue* list, const BookmarkNode& node) { 76 DictionaryValue* dict = new DictionaryValue(); 77 78 // Add id and parentId so we can associate the data with existing nodes on the 79 // client side. 80 std::string id_string = base::Int64ToString(node.id()); 81 dict->SetString(keys::kIdKey, id_string); 82 83 std::string parent_id_string = base::Int64ToString(node.parent()->id()); 84 dict->SetString(keys::kParentIdKey, parent_id_string); 85 86 if (node.is_url()) 87 dict->SetString(keys::kUrlKey, node.GetURL().spec()); 88 89 dict->SetString(keys::kTitleKey, node.GetTitle()); 90 91 ListValue* children = new ListValue(); 92 for (int i = 0; i < node.child_count(); ++i) 93 AddNodeToList(children, *node.GetChild(i)); 94 dict->Set(keys::kChildrenKey, children); 95 96 list->Append(dict); 97 } 98 99 // Recursively adds an element to a list. This is used by 100 // |BookmarkNodeDataToJSON| when the data comes from a different profile. When 101 // the data comes from a different profile we do not have any IDs or parent IDs. 102 void AddElementToList(ListValue* list, 103 const BookmarkNodeData::Element& element) { 104 DictionaryValue* dict = new DictionaryValue(); 105 106 if (element.is_url) 107 dict->SetString(keys::kUrlKey, element.url.spec()); 108 109 dict->SetString(keys::kTitleKey, element.title); 110 111 ListValue* children = new ListValue(); 112 for (size_t i = 0; i < element.children.size(); ++i) 113 AddElementToList(children, element.children[i]); 114 dict->Set(keys::kChildrenKey, children); 115 116 list->Append(dict); 117 } 118 119 // Builds the JSON structure based on the BookmarksDragData. 120 void BookmarkNodeDataToJSON(Profile* profile, const BookmarkNodeData& data, 121 ListValue* args) { 122 bool same_profile = data.IsFromProfile(profile); 123 DictionaryValue* value = new DictionaryValue(); 124 value->SetBoolean(keys::kSameProfileKey, same_profile); 125 126 ListValue* list = new ListValue(); 127 if (same_profile) { 128 std::vector<const BookmarkNode*> nodes = data.GetNodes(profile); 129 for (size_t i = 0; i < nodes.size(); ++i) 130 AddNodeToList(list, *nodes[i]); 131 } else { 132 // We do not have an node IDs when the data comes from a different profile. 133 std::vector<BookmarkNodeData::Element> elements = data.elements; 134 for (size_t i = 0; i < elements.size(); ++i) 135 AddElementToList(list, elements[i]); 136 } 137 value->Set(keys::kElementsKey, list); 138 139 args->Append(value); 140 } 141 142 } // namespace 143 144 ExtensionBookmarkManagerEventRouter::ExtensionBookmarkManagerEventRouter( 145 Profile* profile, TabContents* tab_contents) 146 : profile_(profile), 147 tab_contents_(tab_contents) { 148 tab_contents_->SetBookmarkDragDelegate(this); 149 } 150 151 ExtensionBookmarkManagerEventRouter::~ExtensionBookmarkManagerEventRouter() { 152 if (tab_contents_->GetBookmarkDragDelegate() == this) 153 tab_contents_->SetBookmarkDragDelegate(NULL); 154 } 155 156 void ExtensionBookmarkManagerEventRouter::DispatchEvent(const char* event_name, 157 const ListValue* args) { 158 if (!profile_->GetExtensionEventRouter()) 159 return; 160 161 std::string json_args; 162 base::JSONWriter::Write(args, false, &json_args); 163 profile_->GetExtensionEventRouter()->DispatchEventToRenderers( 164 event_name, json_args, NULL, GURL()); 165 } 166 167 void ExtensionBookmarkManagerEventRouter::DispatchDragEvent( 168 const BookmarkNodeData& data, const char* event_name) { 169 if (data.size() == 0) 170 return; 171 172 ListValue args; 173 BookmarkNodeDataToJSON(profile_, data, &args); 174 DispatchEvent(event_name, &args); 175 } 176 177 void ExtensionBookmarkManagerEventRouter::OnDragEnter( 178 const BookmarkNodeData& data) { 179 DispatchDragEvent(data, keys::kOnBookmarkDragEnter); 180 } 181 182 void ExtensionBookmarkManagerEventRouter::OnDragOver( 183 const BookmarkNodeData& data) { 184 // Intentionally empty since these events happens too often and floods the 185 // message queue. We do not need this event for the bookmark manager anyway. 186 } 187 188 void ExtensionBookmarkManagerEventRouter::OnDragLeave( 189 const BookmarkNodeData& data) { 190 DispatchDragEvent(data, keys::kOnBookmarkDragLeave); 191 } 192 193 void ExtensionBookmarkManagerEventRouter::OnDrop( 194 const BookmarkNodeData& data) { 195 DispatchDragEvent(data, keys::kOnBookmarkDrop); 196 197 // Make a copy that is owned by this instance. 198 ClearBookmarkNodeData(); 199 bookmark_drag_data_ = data; 200 } 201 202 const BookmarkNodeData* 203 ExtensionBookmarkManagerEventRouter::GetBookmarkNodeData() { 204 if (bookmark_drag_data_.is_valid()) 205 return &bookmark_drag_data_; 206 return NULL; 207 } 208 209 void ExtensionBookmarkManagerEventRouter::ClearBookmarkNodeData() { 210 bookmark_drag_data_.Clear(); 211 } 212 213 bool ClipboardBookmarkManagerFunction::CopyOrCut(bool cut) { 214 BookmarkModel* model = profile()->GetBookmarkModel(); 215 std::vector<const BookmarkNode*> nodes; 216 EXTENSION_FUNCTION_VALIDATE(GetNodesFromArguments(model, args_.get(), 217 0, &nodes)); 218 bookmark_utils::CopyToClipboard(model, nodes, cut); 219 return true; 220 } 221 222 bool CopyBookmarkManagerFunction::RunImpl() { 223 return CopyOrCut(false); 224 } 225 226 bool CutBookmarkManagerFunction::RunImpl() { 227 if (!EditBookmarksEnabled()) 228 return false; 229 return CopyOrCut(true); 230 } 231 232 bool PasteBookmarkManagerFunction::RunImpl() { 233 if (!EditBookmarksEnabled()) 234 return false; 235 BookmarkModel* model = profile()->GetBookmarkModel(); 236 const BookmarkNode* parent_node = GetNodeFromArguments(model, args_.get()); 237 if (!parent_node) { 238 error_ = keys::kNoParentError; 239 return false; 240 } 241 bool can_paste = bookmark_utils::CanPasteFromClipboard(parent_node); 242 if (!can_paste) 243 return false; 244 245 // We want to use the highest index of the selected nodes as a destination. 246 std::vector<const BookmarkNode*> nodes; 247 // No need to test return value, if we got an empty list, we insert at end. 248 GetNodesFromArguments(model, args_.get(), 1, &nodes); 249 int highest_index = -1; // -1 means insert at end of list. 250 for (size_t node = 0; node < nodes.size(); ++node) { 251 // + 1 so that we insert after the selection. 252 int this_node_index = parent_node->GetIndexOf(nodes[node]) + 1; 253 if (this_node_index > highest_index) 254 highest_index = this_node_index; 255 } 256 257 bookmark_utils::PasteFromClipboard(model, parent_node, highest_index); 258 return true; 259 } 260 261 bool CanPasteBookmarkManagerFunction::RunImpl() { 262 if (!EditBookmarksEnabled()) 263 return false; 264 BookmarkModel* model = profile()->GetBookmarkModel(); 265 const BookmarkNode* parent_node = GetNodeFromArguments(model, args_.get()); 266 if (!parent_node) { 267 error_ = keys::kNoParentError; 268 return false; 269 } 270 bool can_paste = bookmark_utils::CanPasteFromClipboard(parent_node); 271 result_.reset(Value::CreateBooleanValue(can_paste)); 272 SendResponse(true); 273 return true; 274 } 275 276 bool SortChildrenBookmarkManagerFunction::RunImpl() { 277 if (!EditBookmarksEnabled()) 278 return false; 279 BookmarkModel* model = profile()->GetBookmarkModel(); 280 const BookmarkNode* parent_node = GetNodeFromArguments(model, args_.get()); 281 if (!parent_node) { 282 error_ = keys::kNoParentError; 283 return false; 284 } 285 model->SortChildren(parent_node); 286 return true; 287 } 288 289 bool BookmarkManagerGetStringsFunction::RunImpl() { 290 DictionaryValue* localized_strings = new DictionaryValue(); 291 292 localized_strings->SetString("title", 293 l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_TITLE)); 294 localized_strings->SetString("search_button", 295 l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_SEARCH_BUTTON)); 296 localized_strings->SetString("show_in_folder", 297 l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_SHOW_IN_FOLDER)); 298 localized_strings->SetString("sort", 299 l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_SORT)); 300 localized_strings->SetString("organize_menu", 301 l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_ORGANIZE_MENU)); 302 localized_strings->SetString("tools_menu", 303 l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_TOOLS_MENU)); 304 localized_strings->SetString("import_menu", 305 l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_IMPORT_MENU)); 306 localized_strings->SetString("export_menu", 307 l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_EXPORT_MENU)); 308 localized_strings->SetString("rename_folder", 309 l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_RENAME_FOLDER)); 310 localized_strings->SetString("edit", 311 l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_EDIT)); 312 localized_strings->SetString("should_open_all", 313 l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_SHOULD_OPEN_ALL)); 314 localized_strings->SetString("open_incognito", 315 l10n_util::GetStringUTF16(IDS_BOOMARK_BAR_OPEN_INCOGNITO)); 316 localized_strings->SetString("open_in_new_tab", 317 l10n_util::GetStringUTF16(IDS_BOOMARK_BAR_OPEN_IN_NEW_TAB)); 318 localized_strings->SetString("open_in_new_window", 319 l10n_util::GetStringUTF16(IDS_BOOMARK_BAR_OPEN_IN_NEW_WINDOW)); 320 localized_strings->SetString("add_new_bookmark", 321 l10n_util::GetStringUTF16(IDS_BOOMARK_BAR_ADD_NEW_BOOKMARK)); 322 localized_strings->SetString("new_folder", 323 l10n_util::GetStringUTF16(IDS_BOOMARK_BAR_NEW_FOLDER)); 324 localized_strings->SetString("open_all", 325 l10n_util::GetStringUTF16(IDS_BOOMARK_BAR_OPEN_ALL)); 326 localized_strings->SetString("open_all_new_window", 327 l10n_util::GetStringUTF16(IDS_BOOMARK_BAR_OPEN_ALL_NEW_WINDOW)); 328 localized_strings->SetString("open_all_incognito", 329 l10n_util::GetStringUTF16(IDS_BOOMARK_BAR_OPEN_ALL_INCOGNITO)); 330 localized_strings->SetString("remove", 331 l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_REMOVE)); 332 localized_strings->SetString("copy", 333 l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_COPY)); 334 localized_strings->SetString("cut", 335 l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_CUT)); 336 localized_strings->SetString("paste", 337 l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_PASTE)); 338 localized_strings->SetString("delete", 339 l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_DELETE)); 340 localized_strings->SetString("new_folder_name", 341 l10n_util::GetStringUTF16(IDS_BOOMARK_EDITOR_NEW_FOLDER_NAME)); 342 localized_strings->SetString("name_input_placeholder", 343 l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_NAME_INPUT_PLACE_HOLDER)); 344 localized_strings->SetString("url_input_placeholder", 345 l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_URL_INPUT_PLACE_HOLDER)); 346 localized_strings->SetString("invalid_url", 347 l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_INVALID_URL)); 348 localized_strings->SetString("recent", 349 l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_RECENT)); 350 localized_strings->SetString("search", 351 l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_SEARCH)); 352 353 ChromeURLDataManager::DataSource::SetFontAndTextDirection(localized_strings); 354 355 result_.reset(localized_strings); 356 SendResponse(true); 357 return true; 358 } 359 360 bool StartDragBookmarkManagerFunction::RunImpl() { 361 if (!EditBookmarksEnabled()) 362 return false; 363 BookmarkModel* model = profile()->GetBookmarkModel(); 364 std::vector<const BookmarkNode*> nodes; 365 EXTENSION_FUNCTION_VALIDATE( 366 GetNodesFromArguments(model, args_.get(), 0, &nodes)); 367 368 if (dispatcher()->render_view_host()->delegate()->GetRenderViewType() == 369 ViewType::TAB_CONTENTS) { 370 ExtensionWebUI* web_ui = 371 static_cast<ExtensionWebUI*>(dispatcher()->delegate()); 372 bookmark_utils::DragBookmarks( 373 profile(), nodes, web_ui->tab_contents()->GetNativeView()); 374 375 return true; 376 } else { 377 NOTREACHED(); 378 return false; 379 } 380 } 381 382 bool DropBookmarkManagerFunction::RunImpl() { 383 if (!EditBookmarksEnabled()) 384 return false; 385 386 BookmarkModel* model = profile()->GetBookmarkModel(); 387 388 int64 id; 389 std::string id_string; 390 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &id_string)); 391 392 if (!base::StringToInt64(id_string, &id)) { 393 error_ = keys::kInvalidIdError; 394 return false; 395 } 396 397 const BookmarkNode* drop_parent = model->GetNodeByID(id); 398 if (!drop_parent) { 399 error_ = keys::kNoParentError; 400 return false; 401 } 402 403 int drop_index; 404 if (args_->GetSize() == 2) 405 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(1, &drop_index)); 406 else 407 drop_index = drop_parent->child_count(); 408 409 if (dispatcher()->render_view_host()->delegate()->GetRenderViewType() == 410 ViewType::TAB_CONTENTS) { 411 ExtensionWebUI* web_ui = 412 static_cast<ExtensionWebUI*>(dispatcher()->delegate()); 413 ExtensionBookmarkManagerEventRouter* router = 414 web_ui->extension_bookmark_manager_event_router(); 415 416 DCHECK(router); 417 const BookmarkNodeData* drag_data = router->GetBookmarkNodeData(); 418 if (drag_data == NULL) { 419 NOTREACHED() <<"Somehow we're dropping null bookmark data"; 420 return false; 421 } 422 bookmark_utils::PerformBookmarkDrop(profile(), 423 *drag_data, 424 drop_parent, drop_index); 425 426 router->ClearBookmarkNodeData(); 427 SendResponse(true); 428 return true; 429 } else { 430 NOTREACHED(); 431 return false; 432 } 433 } 434 435 bool GetSubtreeBookmarkManagerFunction::RunImpl() { 436 BookmarkModel* model = profile()->GetBookmarkModel(); 437 const BookmarkNode* node; 438 int64 id; 439 std::string id_string; 440 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &id_string)); 441 bool folders_only; 442 EXTENSION_FUNCTION_VALIDATE(args_->GetBoolean(1, &folders_only)); 443 if (id_string == "") { 444 node = model->root_node(); 445 } else { 446 if (!base::StringToInt64(id_string, &id)) { 447 error_ = keys::kInvalidIdError; 448 return false; 449 } 450 node = model->GetNodeByID(id); 451 } 452 if (!node) { 453 error_ = keys::kNoNodeError; 454 return false; 455 } 456 scoped_ptr<ListValue> json(new ListValue()); 457 if (folders_only) { 458 extension_bookmark_helpers::AddNodeFoldersOnly(node, 459 json.get(), 460 true); 461 } else { 462 extension_bookmark_helpers::AddNode(node, json.get(), true); 463 } 464 result_.reset(json.release()); 465 return true; 466 } 467