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