toolkit/components/places/tests/unit/test_preventive_maintenance.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim:set ts=2 sw=2 sts=2 et: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 /**
michael@0 8 * Test preventive maintenance
michael@0 9 * For every maintenance query create an uncoherent db and check that we take
michael@0 10 * correct fix steps, without polluting valid data.
michael@0 11 */
michael@0 12
michael@0 13 // Include PlacesDBUtils module
michael@0 14 Components.utils.import("resource://gre/modules/PlacesDBUtils.jsm");
michael@0 15
michael@0 16 const FINISHED_MAINTENANCE_NOTIFICATION_TOPIC = "places-maintenance-finished";
michael@0 17
michael@0 18 // Get services and database connection
michael@0 19 let hs = PlacesUtils.history;
michael@0 20 let bs = PlacesUtils.bookmarks;
michael@0 21 let ts = PlacesUtils.tagging;
michael@0 22 let as = PlacesUtils.annotations;
michael@0 23 let fs = PlacesUtils.favicons;
michael@0 24
michael@0 25 let mDBConn = hs.QueryInterface(Ci.nsPIPlacesDatabase).DBConnection;
michael@0 26
michael@0 27 //------------------------------------------------------------------------------
michael@0 28 // Helpers
michael@0 29
michael@0 30 let defaultBookmarksMaxId = 0;
michael@0 31 function cleanDatabase() {
michael@0 32 mDBConn.executeSimpleSQL("DELETE FROM moz_places");
michael@0 33 mDBConn.executeSimpleSQL("DELETE FROM moz_historyvisits");
michael@0 34 mDBConn.executeSimpleSQL("DELETE FROM moz_anno_attributes");
michael@0 35 mDBConn.executeSimpleSQL("DELETE FROM moz_annos");
michael@0 36 mDBConn.executeSimpleSQL("DELETE FROM moz_items_annos");
michael@0 37 mDBConn.executeSimpleSQL("DELETE FROM moz_inputhistory");
michael@0 38 mDBConn.executeSimpleSQL("DELETE FROM moz_keywords");
michael@0 39 mDBConn.executeSimpleSQL("DELETE FROM moz_favicons");
michael@0 40 mDBConn.executeSimpleSQL("DELETE FROM moz_bookmarks WHERE id > " + defaultBookmarksMaxId);
michael@0 41 }
michael@0 42
michael@0 43 function addPlace(aUrl, aFavicon) {
michael@0 44 let stmt = mDBConn.createStatement(
michael@0 45 "INSERT INTO moz_places (url, favicon_id) VALUES (:url, :favicon)");
michael@0 46 stmt.params["url"] = aUrl || "http://www.mozilla.org";
michael@0 47 stmt.params["favicon"] = aFavicon || null;
michael@0 48 stmt.execute();
michael@0 49 stmt.finalize();
michael@0 50 return mDBConn.lastInsertRowID;
michael@0 51 }
michael@0 52
michael@0 53 function addBookmark(aPlaceId, aType, aParent, aKeywordId, aFolderType, aTitle) {
michael@0 54 let stmt = mDBConn.createStatement(
michael@0 55 "INSERT INTO moz_bookmarks (fk, type, parent, keyword_id, folder_type, "
michael@0 56 + "title, guid) "
michael@0 57 + "VALUES (:place_id, :type, :parent, :keyword_id, :folder_type, :title, "
michael@0 58 + "GENERATE_GUID())");
michael@0 59 stmt.params["place_id"] = aPlaceId || null;
michael@0 60 stmt.params["type"] = aType || bs.TYPE_BOOKMARK;
michael@0 61 stmt.params["parent"] = aParent || bs.unfiledBookmarksFolder;
michael@0 62 stmt.params["keyword_id"] = aKeywordId || null;
michael@0 63 stmt.params["folder_type"] = aFolderType || null;
michael@0 64 stmt.params["title"] = typeof(aTitle) == "string" ? aTitle : null;
michael@0 65 stmt.execute();
michael@0 66 stmt.finalize();
michael@0 67 return mDBConn.lastInsertRowID;
michael@0 68 }
michael@0 69
michael@0 70 //------------------------------------------------------------------------------
michael@0 71 // Tests
michael@0 72
michael@0 73 let tests = [];
michael@0 74
michael@0 75 //------------------------------------------------------------------------------
michael@0 76
michael@0 77 tests.push({
michael@0 78 name: "A.1",
michael@0 79 desc: "Remove obsolete annotations from moz_annos",
michael@0 80
michael@0 81 _obsoleteWeaveAttribute: "weave/test",
michael@0 82 _placeId: null,
michael@0 83
michael@0 84 setup: function() {
michael@0 85 // Add a place to ensure place_id = 1 is valid.
michael@0 86 this._placeId = addPlace();
michael@0 87 // Add an obsolete attribute.
michael@0 88 let stmt = mDBConn.createStatement(
michael@0 89 "INSERT INTO moz_anno_attributes (name) VALUES (:anno)"
michael@0 90 );
michael@0 91 stmt.params['anno'] = this._obsoleteWeaveAttribute;
michael@0 92 stmt.execute();
michael@0 93 stmt.finalize();
michael@0 94 stmt = mDBConn.createStatement(
michael@0 95 "INSERT INTO moz_annos (place_id, anno_attribute_id) "
michael@0 96 + "VALUES (:place_id, "
michael@0 97 + "(SELECT id FROM moz_anno_attributes WHERE name = :anno)"
michael@0 98 + ")"
michael@0 99 );
michael@0 100 stmt.params['place_id'] = this._placeId;
michael@0 101 stmt.params['anno'] = this._obsoleteWeaveAttribute;
michael@0 102 stmt.execute();
michael@0 103 stmt.finalize();
michael@0 104 },
michael@0 105
michael@0 106 check: function() {
michael@0 107 // Check that the obsolete annotation has been removed.
michael@0 108 let stmt = mDBConn.createStatement(
michael@0 109 "SELECT id FROM moz_anno_attributes WHERE name = :anno"
michael@0 110 );
michael@0 111 stmt.params['anno'] = this._obsoleteWeaveAttribute;
michael@0 112 do_check_false(stmt.executeStep());
michael@0 113 stmt.finalize();
michael@0 114 }
michael@0 115 });
michael@0 116
michael@0 117 tests.push({
michael@0 118 name: "A.2",
michael@0 119 desc: "Remove obsolete annotations from moz_items_annos",
michael@0 120
michael@0 121 _obsoleteSyncAttribute: "sync/children",
michael@0 122 _obsoleteGuidAttribute: "placesInternal/GUID",
michael@0 123 _obsoleteWeaveAttribute: "weave/test",
michael@0 124 _placeId: null,
michael@0 125 _bookmarkId: null,
michael@0 126
michael@0 127 setup: function() {
michael@0 128 // Add a place to ensure place_id = 1 is valid.
michael@0 129 this._placeId = addPlace();
michael@0 130 // Add a bookmark.
michael@0 131 this._bookmarkId = addBookmark(this._placeId);
michael@0 132 // Add an obsolete attribute.
michael@0 133 let stmt = mDBConn.createStatement(
michael@0 134 "INSERT INTO moz_anno_attributes (name) "
michael@0 135 + "VALUES (:anno1), (:anno2), (:anno3)"
michael@0 136 );
michael@0 137 stmt.params['anno1'] = this._obsoleteSyncAttribute;
michael@0 138 stmt.params['anno2'] = this._obsoleteGuidAttribute;
michael@0 139 stmt.params['anno3'] = this._obsoleteWeaveAttribute;
michael@0 140 stmt.execute();
michael@0 141 stmt.finalize();
michael@0 142 stmt = mDBConn.createStatement(
michael@0 143 "INSERT INTO moz_items_annos (item_id, anno_attribute_id) "
michael@0 144 + "SELECT :item_id, id "
michael@0 145 + "FROM moz_anno_attributes "
michael@0 146 + "WHERE name IN (:anno1, :anno2, :anno3)"
michael@0 147 );
michael@0 148 stmt.params['item_id'] = this._bookmarkId;
michael@0 149 stmt.params['anno1'] = this._obsoleteSyncAttribute;
michael@0 150 stmt.params['anno2'] = this._obsoleteGuidAttribute;
michael@0 151 stmt.params['anno3'] = this._obsoleteWeaveAttribute;
michael@0 152 stmt.execute();
michael@0 153 stmt.finalize();
michael@0 154 },
michael@0 155
michael@0 156 check: function() {
michael@0 157 // Check that the obsolete annotations have been removed.
michael@0 158 let stmt = mDBConn.createStatement(
michael@0 159 "SELECT id FROM moz_anno_attributes "
michael@0 160 + "WHERE name IN (:anno1, :anno2, :anno3)"
michael@0 161 );
michael@0 162 stmt.params['anno1'] = this._obsoleteSyncAttribute;
michael@0 163 stmt.params['anno2'] = this._obsoleteGuidAttribute;
michael@0 164 stmt.params['anno3'] = this._obsoleteWeaveAttribute;
michael@0 165 do_check_false(stmt.executeStep());
michael@0 166 stmt.finalize();
michael@0 167 }
michael@0 168 });
michael@0 169
michael@0 170 tests.push({
michael@0 171 name: "A.3",
michael@0 172 desc: "Remove unused attributes",
michael@0 173
michael@0 174 _usedPageAttribute: "usedPage",
michael@0 175 _usedItemAttribute: "usedItem",
michael@0 176 _unusedAttribute: "unused",
michael@0 177 _placeId: null,
michael@0 178 _bookmarkId: null,
michael@0 179
michael@0 180 setup: function() {
michael@0 181 // Add a place to ensure place_id = 1 is valid
michael@0 182 this._placeId = addPlace();
michael@0 183 // add a bookmark
michael@0 184 this._bookmarkId = addBookmark(this._placeId);
michael@0 185 // Add a used attribute and an unused one.
michael@0 186 let stmt = mDBConn.createStatement("INSERT INTO moz_anno_attributes (name) VALUES (:anno)");
michael@0 187 stmt.params['anno'] = this._usedPageAttribute;
michael@0 188 stmt.execute();
michael@0 189 stmt.reset();
michael@0 190 stmt.params['anno'] = this._usedItemAttribute;
michael@0 191 stmt.execute();
michael@0 192 stmt.reset();
michael@0 193 stmt.params['anno'] = this._unusedAttribute;
michael@0 194 stmt.execute();
michael@0 195 stmt.finalize();
michael@0 196
michael@0 197 stmt = mDBConn.createStatement("INSERT INTO moz_annos (place_id, anno_attribute_id) VALUES(:place_id, (SELECT id FROM moz_anno_attributes WHERE name = :anno))");
michael@0 198 stmt.params['place_id'] = this._placeId;
michael@0 199 stmt.params['anno'] = this._usedPageAttribute;
michael@0 200 stmt.execute();
michael@0 201 stmt.finalize();
michael@0 202 stmt = mDBConn.createStatement("INSERT INTO moz_items_annos (item_id, anno_attribute_id) VALUES(:item_id, (SELECT id FROM moz_anno_attributes WHERE name = :anno))");
michael@0 203 stmt.params['item_id'] = this._bookmarkId;
michael@0 204 stmt.params['anno'] = this._usedItemAttribute;
michael@0 205 stmt.execute();
michael@0 206 stmt.finalize();
michael@0 207 },
michael@0 208
michael@0 209 check: function() {
michael@0 210 // Check that used attributes are still there
michael@0 211 let stmt = mDBConn.createStatement("SELECT id FROM moz_anno_attributes WHERE name = :anno");
michael@0 212 stmt.params['anno'] = this._usedPageAttribute;
michael@0 213 do_check_true(stmt.executeStep());
michael@0 214 stmt.reset();
michael@0 215 stmt.params['anno'] = this._usedItemAttribute;
michael@0 216 do_check_true(stmt.executeStep());
michael@0 217 stmt.reset();
michael@0 218 // Check that unused attribute has been removed
michael@0 219 stmt.params['anno'] = this._unusedAttribute;
michael@0 220 do_check_false(stmt.executeStep());
michael@0 221 stmt.finalize();
michael@0 222 }
michael@0 223 });
michael@0 224
michael@0 225 //------------------------------------------------------------------------------
michael@0 226
michael@0 227 tests.push({
michael@0 228 name: "B.1",
michael@0 229 desc: "Remove annotations with an invalid attribute",
michael@0 230
michael@0 231 _usedPageAttribute: "usedPage",
michael@0 232 _placeId: null,
michael@0 233
michael@0 234 setup: function() {
michael@0 235 // Add a place to ensure place_id = 1 is valid
michael@0 236 this._placeId = addPlace();
michael@0 237 // Add a used attribute.
michael@0 238 let stmt = mDBConn.createStatement("INSERT INTO moz_anno_attributes (name) VALUES (:anno)");
michael@0 239 stmt.params['anno'] = this._usedPageAttribute;
michael@0 240 stmt.execute();
michael@0 241 stmt.finalize();
michael@0 242 stmt = mDBConn.createStatement("INSERT INTO moz_annos (place_id, anno_attribute_id) VALUES(:place_id, (SELECT id FROM moz_anno_attributes WHERE name = :anno))");
michael@0 243 stmt.params['place_id'] = this._placeId;
michael@0 244 stmt.params['anno'] = this._usedPageAttribute;
michael@0 245 stmt.execute();
michael@0 246 stmt.finalize();
michael@0 247 // Add an annotation with a nonexistent attribute
michael@0 248 stmt = mDBConn.createStatement("INSERT INTO moz_annos (place_id, anno_attribute_id) VALUES(:place_id, 1337)");
michael@0 249 stmt.params['place_id'] = this._placeId;
michael@0 250 stmt.execute();
michael@0 251 stmt.finalize();
michael@0 252 },
michael@0 253
michael@0 254 check: function() {
michael@0 255 // Check that used attribute is still there
michael@0 256 let stmt = mDBConn.createStatement("SELECT id FROM moz_anno_attributes WHERE name = :anno");
michael@0 257 stmt.params['anno'] = this._usedPageAttribute;
michael@0 258 do_check_true(stmt.executeStep());
michael@0 259 stmt.finalize();
michael@0 260 // check that annotation with valid attribute is still there
michael@0 261 stmt = mDBConn.createStatement("SELECT id FROM moz_annos WHERE anno_attribute_id = (SELECT id FROM moz_anno_attributes WHERE name = :anno)");
michael@0 262 stmt.params['anno'] = this._usedPageAttribute;
michael@0 263 do_check_true(stmt.executeStep());
michael@0 264 stmt.finalize();
michael@0 265 // Check that annotation with bogus attribute has been removed
michael@0 266 stmt = mDBConn.createStatement("SELECT id FROM moz_annos WHERE anno_attribute_id = 1337");
michael@0 267 do_check_false(stmt.executeStep());
michael@0 268 stmt.finalize();
michael@0 269 }
michael@0 270 });
michael@0 271
michael@0 272 //------------------------------------------------------------------------------
michael@0 273
michael@0 274 tests.push({
michael@0 275 name: "B.2",
michael@0 276 desc: "Remove orphan page annotations",
michael@0 277
michael@0 278 _usedPageAttribute: "usedPage",
michael@0 279 _placeId: null,
michael@0 280
michael@0 281 setup: function() {
michael@0 282 // Add a place to ensure place_id = 1 is valid
michael@0 283 this._placeId = addPlace();
michael@0 284 // Add a used attribute.
michael@0 285 let stmt = mDBConn.createStatement("INSERT INTO moz_anno_attributes (name) VALUES (:anno)");
michael@0 286 stmt.params['anno'] = this._usedPageAttribute;
michael@0 287 stmt.execute();
michael@0 288 stmt.finalize();
michael@0 289 stmt = mDBConn.createStatement("INSERT INTO moz_annos (place_id, anno_attribute_id) VALUES(:place_id, (SELECT id FROM moz_anno_attributes WHERE name = :anno))");
michael@0 290 stmt.params['place_id'] = this._placeId;
michael@0 291 stmt.params['anno'] = this._usedPageAttribute;
michael@0 292 stmt.execute();
michael@0 293 stmt.reset();
michael@0 294 // Add an annotation to a nonexistent page
michael@0 295 stmt.params['place_id'] = 1337;
michael@0 296 stmt.params['anno'] = this._usedPageAttribute;
michael@0 297 stmt.execute();
michael@0 298 stmt.finalize();
michael@0 299 },
michael@0 300
michael@0 301 check: function() {
michael@0 302 // Check that used attribute is still there
michael@0 303 let stmt = mDBConn.createStatement("SELECT id FROM moz_anno_attributes WHERE name = :anno");
michael@0 304 stmt.params['anno'] = this._usedPageAttribute;
michael@0 305 do_check_true(stmt.executeStep());
michael@0 306 stmt.finalize();
michael@0 307 // check that annotation with valid attribute is still there
michael@0 308 stmt = mDBConn.createStatement("SELECT id FROM moz_annos WHERE anno_attribute_id = (SELECT id FROM moz_anno_attributes WHERE name = :anno)");
michael@0 309 stmt.params['anno'] = this._usedPageAttribute;
michael@0 310 do_check_true(stmt.executeStep());
michael@0 311 stmt.finalize();
michael@0 312 // Check that an annotation to a nonexistent page has been removed
michael@0 313 stmt = mDBConn.createStatement("SELECT id FROM moz_annos WHERE place_id = 1337");
michael@0 314 do_check_false(stmt.executeStep());
michael@0 315 stmt.finalize();
michael@0 316 }
michael@0 317 });
michael@0 318
michael@0 319 //------------------------------------------------------------------------------
michael@0 320 tests.push({
michael@0 321 name: "C.1",
michael@0 322 desc: "fix missing Places root",
michael@0 323
michael@0 324 setup: function() {
michael@0 325 // Sanity check: ensure that roots are intact.
michael@0 326 do_check_eq(bs.getFolderIdForItem(bs.placesRoot), 0);
michael@0 327 do_check_eq(bs.getFolderIdForItem(bs.bookmarksMenuFolder), bs.placesRoot);
michael@0 328 do_check_eq(bs.getFolderIdForItem(bs.tagsFolder), bs.placesRoot);
michael@0 329 do_check_eq(bs.getFolderIdForItem(bs.unfiledBookmarksFolder), bs.placesRoot);
michael@0 330 do_check_eq(bs.getFolderIdForItem(bs.toolbarFolder), bs.placesRoot);
michael@0 331
michael@0 332 // Remove the root.
michael@0 333 mDBConn.executeSimpleSQL("DELETE FROM moz_bookmarks WHERE parent = 0");
michael@0 334 let stmt = mDBConn.createStatement("SELECT id FROM moz_bookmarks WHERE parent = 0");
michael@0 335 do_check_false(stmt.executeStep());
michael@0 336 stmt.finalize();
michael@0 337 },
michael@0 338
michael@0 339 check: function() {
michael@0 340 // Ensure the roots have been correctly restored.
michael@0 341 do_check_eq(bs.getFolderIdForItem(bs.placesRoot), 0);
michael@0 342 do_check_eq(bs.getFolderIdForItem(bs.bookmarksMenuFolder), bs.placesRoot);
michael@0 343 do_check_eq(bs.getFolderIdForItem(bs.tagsFolder), bs.placesRoot);
michael@0 344 do_check_eq(bs.getFolderIdForItem(bs.unfiledBookmarksFolder), bs.placesRoot);
michael@0 345 do_check_eq(bs.getFolderIdForItem(bs.toolbarFolder), bs.placesRoot);
michael@0 346 }
michael@0 347 });
michael@0 348
michael@0 349 //------------------------------------------------------------------------------
michael@0 350 tests.push({
michael@0 351 name: "C.2",
michael@0 352 desc: "Fix roots titles",
michael@0 353
michael@0 354 setup: function() {
michael@0 355 // Sanity check: ensure that roots titles are correct. We can use our check.
michael@0 356 this.check();
michael@0 357 // Change some roots' titles.
michael@0 358 bs.setItemTitle(bs.placesRoot, "bad title");
michael@0 359 do_check_eq(bs.getItemTitle(bs.placesRoot), "bad title");
michael@0 360 bs.setItemTitle(bs.unfiledBookmarksFolder, "bad title");
michael@0 361 do_check_eq(bs.getItemTitle(bs.unfiledBookmarksFolder), "bad title");
michael@0 362 },
michael@0 363
michael@0 364 check: function() {
michael@0 365 // Ensure all roots titles are correct.
michael@0 366 do_check_eq(bs.getItemTitle(bs.placesRoot), "");
michael@0 367 do_check_eq(bs.getItemTitle(bs.bookmarksMenuFolder),
michael@0 368 PlacesUtils.getString("BookmarksMenuFolderTitle"));
michael@0 369 do_check_eq(bs.getItemTitle(bs.tagsFolder),
michael@0 370 PlacesUtils.getString("TagsFolderTitle"));
michael@0 371 do_check_eq(bs.getItemTitle(bs.unfiledBookmarksFolder),
michael@0 372 PlacesUtils.getString("UnsortedBookmarksFolderTitle"));
michael@0 373 do_check_eq(bs.getItemTitle(bs.toolbarFolder),
michael@0 374 PlacesUtils.getString("BookmarksToolbarFolderTitle"));
michael@0 375 }
michael@0 376 });
michael@0 377
michael@0 378 //------------------------------------------------------------------------------
michael@0 379
michael@0 380 tests.push({
michael@0 381 name: "D.1",
michael@0 382 desc: "Remove items without a valid place",
michael@0 383
michael@0 384 _validItemId: null,
michael@0 385 _invalidItemId: null,
michael@0 386 _placeId: null,
michael@0 387
michael@0 388 setup: function() {
michael@0 389 // Add a place to ensure place_id = 1 is valid
michael@0 390 this.placeId = addPlace();
michael@0 391 // Insert a valid bookmark
michael@0 392 this._validItemId = addBookmark(this.placeId);
michael@0 393 // Insert a bookmark with an invalid place
michael@0 394 this._invalidItemId = addBookmark(1337);
michael@0 395 },
michael@0 396
michael@0 397 check: function() {
michael@0 398 // Check that valid bookmark is still there
michael@0 399 let stmt = mDBConn.createStatement("SELECT id FROM moz_bookmarks WHERE id = :item_id");
michael@0 400 stmt.params["item_id"] = this._validItemId;
michael@0 401 do_check_true(stmt.executeStep());
michael@0 402 stmt.reset();
michael@0 403 // Check that invalid bookmark has been removed
michael@0 404 stmt.params["item_id"] = this._invalidItemId;
michael@0 405 do_check_false(stmt.executeStep());
michael@0 406 stmt.finalize();
michael@0 407 }
michael@0 408 });
michael@0 409
michael@0 410 //------------------------------------------------------------------------------
michael@0 411
michael@0 412 tests.push({
michael@0 413 name: "D.2",
michael@0 414 desc: "Remove items that are not uri bookmarks from tag containers",
michael@0 415
michael@0 416 _tagId: null,
michael@0 417 _bookmarkId: null,
michael@0 418 _separatorId: null,
michael@0 419 _folderId: null,
michael@0 420 _placeId: null,
michael@0 421
michael@0 422 setup: function() {
michael@0 423 // Add a place to ensure place_id = 1 is valid
michael@0 424 this._placeId = addPlace();
michael@0 425 // Create a tag
michael@0 426 this._tagId = addBookmark(null, bs.TYPE_FOLDER, bs.tagsFolder);
michael@0 427 // Insert a bookmark in the tag
michael@0 428 this._bookmarkId = addBookmark(this._placeId, bs.TYPE_BOOKMARK, this._tagId);
michael@0 429 // Insert a separator in the tag
michael@0 430 this._separatorId = addBookmark(null, bs.TYPE_SEPARATOR, this._tagId);
michael@0 431 // Insert a folder in the tag
michael@0 432 this._folderId = addBookmark(null, bs.TYPE_FOLDER, this._tagId);
michael@0 433 },
michael@0 434
michael@0 435 check: function() {
michael@0 436 // Check that valid bookmark is still there
michael@0 437 let stmt = mDBConn.createStatement("SELECT id FROM moz_bookmarks WHERE type = :type AND parent = :parent");
michael@0 438 stmt.params["type"] = bs.TYPE_BOOKMARK;
michael@0 439 stmt.params["parent"] = this._tagId;
michael@0 440 do_check_true(stmt.executeStep());
michael@0 441 stmt.reset();
michael@0 442 // Check that separator is no more there
michael@0 443 stmt.params["type"] = bs.TYPE_SEPARATOR;
michael@0 444 stmt.params["parent"] = this._tagId;
michael@0 445 do_check_false(stmt.executeStep());
michael@0 446 stmt.reset();
michael@0 447 // Check that folder is no more there
michael@0 448 stmt.params["type"] = bs.TYPE_FOLDER;
michael@0 449 stmt.params["parent"] = this._tagId;
michael@0 450 do_check_false(stmt.executeStep());
michael@0 451 stmt.finalize();
michael@0 452 }
michael@0 453 });
michael@0 454
michael@0 455 //------------------------------------------------------------------------------
michael@0 456
michael@0 457 tests.push({
michael@0 458 name: "D.3",
michael@0 459 desc: "Remove empty tags",
michael@0 460
michael@0 461 _tagId: null,
michael@0 462 _bookmarkId: null,
michael@0 463 _emptyTagId: null,
michael@0 464 _placeId: null,
michael@0 465
michael@0 466 setup: function() {
michael@0 467 // Add a place to ensure place_id = 1 is valid
michael@0 468 this._placeId = addPlace();
michael@0 469 // Create a tag
michael@0 470 this._tagId = addBookmark(null, bs.TYPE_FOLDER, bs.tagsFolder);
michael@0 471 // Insert a bookmark in the tag
michael@0 472 this._bookmarkId = addBookmark(this._placeId, bs.TYPE_BOOKMARK, this._tagId);
michael@0 473 // Create another tag (empty)
michael@0 474 this._emptyTagId = addBookmark(null, bs.TYPE_FOLDER, bs.tagsFolder);
michael@0 475 },
michael@0 476
michael@0 477 check: function() {
michael@0 478 // Check that valid bookmark is still there
michael@0 479 let stmt = mDBConn.createStatement("SELECT id FROM moz_bookmarks WHERE id = :id AND type = :type AND parent = :parent");
michael@0 480 stmt.params["id"] = this._bookmarkId;
michael@0 481 stmt.params["type"] = bs.TYPE_BOOKMARK;
michael@0 482 stmt.params["parent"] = this._tagId;
michael@0 483 do_check_true(stmt.executeStep());
michael@0 484 stmt.reset();
michael@0 485 stmt.params["id"] = this._tagId;
michael@0 486 stmt.params["type"] = bs.TYPE_FOLDER;
michael@0 487 stmt.params["parent"] = bs.tagsFolder;
michael@0 488 do_check_true(stmt.executeStep());
michael@0 489 stmt.reset();
michael@0 490 stmt.params["id"] = this._emptyTagId;
michael@0 491 stmt.params["type"] = bs.TYPE_FOLDER;
michael@0 492 stmt.params["parent"] = bs.tagsFolder;
michael@0 493 do_check_false(stmt.executeStep());
michael@0 494 stmt.finalize();
michael@0 495 }
michael@0 496 });
michael@0 497
michael@0 498 //------------------------------------------------------------------------------
michael@0 499
michael@0 500 tests.push({
michael@0 501 name: "D.4",
michael@0 502 desc: "Move orphan items to unsorted folder",
michael@0 503
michael@0 504 _orphanBookmarkId: null,
michael@0 505 _orphanSeparatorId: null,
michael@0 506 _orphanFolderId: null,
michael@0 507 _bookmarkId: null,
michael@0 508 _placeId: null,
michael@0 509
michael@0 510 setup: function() {
michael@0 511 // Add a place to ensure place_id = 1 is valid
michael@0 512 this._placeId = addPlace();
michael@0 513 // Insert an orphan bookmark
michael@0 514 this._orphanBookmarkId = addBookmark(this._placeId, bs.TYPE_BOOKMARK, 8888);
michael@0 515 // Insert an orphan separator
michael@0 516 this._orphanSeparatorId = addBookmark(null, bs.TYPE_SEPARATOR, 8888);
michael@0 517 // Insert a orphan folder
michael@0 518 this._orphanFolderId = addBookmark(null, bs.TYPE_FOLDER, 8888);
michael@0 519 // Create a child of the last created folder
michael@0 520 this._bookmarkId = addBookmark(this._placeId, bs.TYPE_BOOKMARK, this._orphanFolderId);
michael@0 521 },
michael@0 522
michael@0 523 check: function() {
michael@0 524 // Check that bookmarks are now children of a real folder (unsorted)
michael@0 525 let stmt = mDBConn.createStatement("SELECT id FROM moz_bookmarks WHERE id = :item_id AND parent = :parent");
michael@0 526 stmt.params["item_id"] = this._orphanBookmarkId;
michael@0 527 stmt.params["parent"] = bs.unfiledBookmarksFolder;
michael@0 528 do_check_true(stmt.executeStep());
michael@0 529 stmt.reset();
michael@0 530 stmt.params["item_id"] = this._orphanSeparatorId;
michael@0 531 stmt.params["parent"] = bs.unfiledBookmarksFolder;
michael@0 532 do_check_true(stmt.executeStep());
michael@0 533 stmt.reset();
michael@0 534 stmt.params["item_id"] = this._orphanFolderId;
michael@0 535 stmt.params["parent"] = bs.unfiledBookmarksFolder;
michael@0 536 do_check_true(stmt.executeStep());
michael@0 537 stmt.reset();
michael@0 538 stmt.params["item_id"] = this._bookmarkId;
michael@0 539 stmt.params["parent"] = this._orphanFolderId;
michael@0 540 do_check_true(stmt.executeStep());
michael@0 541 stmt.finalize();
michael@0 542 }
michael@0 543 });
michael@0 544
michael@0 545 //------------------------------------------------------------------------------
michael@0 546
michael@0 547 tests.push({
michael@0 548 name: "D.5",
michael@0 549 desc: "Fix wrong keywords",
michael@0 550
michael@0 551 _validKeywordItemId: null,
michael@0 552 _invalidKeywordItemId: null,
michael@0 553 _validKeywordId: 1,
michael@0 554 _invalidKeywordId: 8888,
michael@0 555 _placeId: null,
michael@0 556
michael@0 557 setup: function() {
michael@0 558 // Insert a keyword
michael@0 559 let stmt = mDBConn.createStatement("INSERT INTO moz_keywords (id, keyword) VALUES(:id, :keyword)");
michael@0 560 stmt.params["id"] = this._validKeywordId;
michael@0 561 stmt.params["keyword"] = "used";
michael@0 562 stmt.execute();
michael@0 563 stmt.finalize();
michael@0 564 // Add a place to ensure place_id = 1 is valid
michael@0 565 this._placeId = addPlace();
michael@0 566 // Add a bookmark using the keyword
michael@0 567 this._validKeywordItemId = addBookmark(this._placeId, bs.TYPE_BOOKMARK, bs.unfiledBookmarksFolder, this._validKeywordId);
michael@0 568 // Add a bookmark using a nonexistent keyword
michael@0 569 this._invalidKeywordItemId = addBookmark(this._placeId, bs.TYPE_BOOKMARK, bs.unfiledBookmarksFolder, this._invalidKeywordId);
michael@0 570 },
michael@0 571
michael@0 572 check: function() {
michael@0 573 // Check that item with valid keyword is there
michael@0 574 let stmt = mDBConn.createStatement("SELECT id FROM moz_bookmarks WHERE id = :item_id AND keyword_id = :keyword");
michael@0 575 stmt.params["item_id"] = this._validKeywordItemId;
michael@0 576 stmt.params["keyword"] = this._validKeywordId;
michael@0 577 do_check_true(stmt.executeStep());
michael@0 578 stmt.reset();
michael@0 579 // Check that item with invalid keyword has been corrected
michael@0 580 stmt.params["item_id"] = this._invalidKeywordItemId;
michael@0 581 stmt.params["keyword"] = this._invalidKeywordId;
michael@0 582 do_check_false(stmt.executeStep());
michael@0 583 stmt.finalize();
michael@0 584 // Check that item with invalid keyword has not been removed
michael@0 585 stmt = mDBConn.createStatement("SELECT id FROM moz_bookmarks WHERE id = :item_id");
michael@0 586 stmt.params["item_id"] = this._invalidKeywordItemId;
michael@0 587 do_check_true(stmt.executeStep());
michael@0 588 stmt.finalize();
michael@0 589 }
michael@0 590 });
michael@0 591
michael@0 592 //------------------------------------------------------------------------------
michael@0 593
michael@0 594 tests.push({
michael@0 595 name: "D.6",
michael@0 596 desc: "Fix wrong item types | bookmarks",
michael@0 597
michael@0 598 _separatorId: null,
michael@0 599 _folderId: null,
michael@0 600 _placeId: null,
michael@0 601
michael@0 602 setup: function() {
michael@0 603 // Add a place to ensure place_id = 1 is valid
michael@0 604 this._placeId = addPlace();
michael@0 605 // Add a separator with a fk
michael@0 606 this._separatorId = addBookmark(this._placeId, bs.TYPE_SEPARATOR);
michael@0 607 // Add a folder with a fk
michael@0 608 this._folderId = addBookmark(this._placeId, bs.TYPE_FOLDER);
michael@0 609 },
michael@0 610
michael@0 611 check: function() {
michael@0 612 // Check that items with an fk have been converted to bookmarks
michael@0 613 let stmt = mDBConn.createStatement("SELECT id FROM moz_bookmarks WHERE id = :item_id AND type = :type");
michael@0 614 stmt.params["item_id"] = this._separatorId;
michael@0 615 stmt.params["type"] = bs.TYPE_BOOKMARK;
michael@0 616 do_check_true(stmt.executeStep());
michael@0 617 stmt.reset();
michael@0 618 stmt.params["item_id"] = this._folderId;
michael@0 619 stmt.params["type"] = bs.TYPE_BOOKMARK;
michael@0 620 do_check_true(stmt.executeStep());
michael@0 621 stmt.finalize();
michael@0 622 }
michael@0 623 });
michael@0 624
michael@0 625 //------------------------------------------------------------------------------
michael@0 626
michael@0 627 tests.push({
michael@0 628 name: "D.7",
michael@0 629 desc: "Fix wrong item types | bookmarks",
michael@0 630
michael@0 631 _validBookmarkId: null,
michael@0 632 _invalidBookmarkId: null,
michael@0 633 _placeId: null,
michael@0 634
michael@0 635 setup: function() {
michael@0 636 // Add a place to ensure place_id = 1 is valid
michael@0 637 this._placeId = addPlace();
michael@0 638 // Add a bookmark with a valid place id
michael@0 639 this._validBookmarkId = addBookmark(this._placeId, bs.TYPE_BOOKMARK);
michael@0 640 // Add a bookmark with a null place id
michael@0 641 this._invalidBookmarkId = addBookmark(null, bs.TYPE_BOOKMARK);
michael@0 642 },
michael@0 643
michael@0 644 check: function() {
michael@0 645 // Check valid bookmark
michael@0 646 let stmt = mDBConn.createStatement("SELECT id FROM moz_bookmarks WHERE id = :item_id AND type = :type");
michael@0 647 stmt.params["item_id"] = this._validBookmarkId;
michael@0 648 stmt.params["type"] = bs.TYPE_BOOKMARK;
michael@0 649 do_check_true(stmt.executeStep());
michael@0 650 stmt.reset();
michael@0 651 // Check invalid bookmark has been converted to a folder
michael@0 652 stmt.params["item_id"] = this._invalidBookmarkId;
michael@0 653 stmt.params["type"] = bs.TYPE_FOLDER;
michael@0 654 do_check_true(stmt.executeStep());
michael@0 655 stmt.finalize();
michael@0 656 }
michael@0 657 });
michael@0 658
michael@0 659 //------------------------------------------------------------------------------
michael@0 660
michael@0 661 tests.push({
michael@0 662 name: "D.9",
michael@0 663 desc: "Fix wrong parents",
michael@0 664
michael@0 665 _bookmarkId: null,
michael@0 666 _separatorId: null,
michael@0 667 _bookmarkId1: null,
michael@0 668 _bookmarkId2: null,
michael@0 669 _placeId: null,
michael@0 670
michael@0 671 setup: function() {
michael@0 672 // Add a place to ensure place_id = 1 is valid
michael@0 673 this._placeId = addPlace();
michael@0 674 // Insert a bookmark
michael@0 675 this._bookmarkId = addBookmark(this._placeId, bs.TYPE_BOOKMARK);
michael@0 676 // Insert a separator
michael@0 677 this._separatorId = addBookmark(null, bs.TYPE_SEPARATOR);
michael@0 678 // Create 3 children of these items
michael@0 679 this._bookmarkId1 = addBookmark(this._placeId, bs.TYPE_BOOKMARK, this._bookmarkId);
michael@0 680 this._bookmarkId2 = addBookmark(this._placeId, bs.TYPE_BOOKMARK, this._separatorId);
michael@0 681 },
michael@0 682
michael@0 683 check: function() {
michael@0 684 // Check that bookmarks are now children of a real folder (unsorted)
michael@0 685 let stmt = mDBConn.createStatement("SELECT id FROM moz_bookmarks WHERE id = :item_id AND parent = :parent");
michael@0 686 stmt.params["item_id"] = this._bookmarkId1;
michael@0 687 stmt.params["parent"] = bs.unfiledBookmarksFolder;
michael@0 688 do_check_true(stmt.executeStep());
michael@0 689 stmt.reset();
michael@0 690 stmt.params["item_id"] = this._bookmarkId2;
michael@0 691 stmt.params["parent"] = bs.unfiledBookmarksFolder;
michael@0 692 do_check_true(stmt.executeStep());
michael@0 693 stmt.finalize();
michael@0 694 }
michael@0 695 });
michael@0 696
michael@0 697 //------------------------------------------------------------------------------
michael@0 698
michael@0 699 tests.push({
michael@0 700 name: "D.10",
michael@0 701 desc: "Recalculate positions",
michael@0 702
michael@0 703 _unfiledBookmarks: [],
michael@0 704 _toolbarBookmarks: [],
michael@0 705
michael@0 706 setup: function() {
michael@0 707 const NUM_BOOKMARKS = 20;
michael@0 708 bs.runInBatchMode({
michael@0 709 runBatched: function (aUserData) {
michael@0 710 // Add bookmarks to two folders to better perturbate the table.
michael@0 711 for (let i = 0; i < NUM_BOOKMARKS; i++) {
michael@0 712 bs.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
michael@0 713 NetUtil.newURI("http://example.com/"),
michael@0 714 bs.DEFAULT_INDEX, "testbookmark");
michael@0 715 }
michael@0 716 for (let i = 0; i < NUM_BOOKMARKS; i++) {
michael@0 717 bs.insertBookmark(PlacesUtils.toolbarFolderId,
michael@0 718 NetUtil.newURI("http://example.com/"),
michael@0 719 bs.DEFAULT_INDEX, "testbookmark");
michael@0 720 }
michael@0 721 }
michael@0 722 }, null);
michael@0 723
michael@0 724 function randomize_positions(aParent, aResultArray) {
michael@0 725 let stmt = mDBConn.createStatement(
michael@0 726 "UPDATE moz_bookmarks SET position = :rand " +
michael@0 727 "WHERE id IN ( " +
michael@0 728 "SELECT id FROM moz_bookmarks WHERE parent = :parent " +
michael@0 729 "ORDER BY RANDOM() LIMIT 1 " +
michael@0 730 ") "
michael@0 731 );
michael@0 732 for (let i = 0; i < (NUM_BOOKMARKS / 2); i++) {
michael@0 733 stmt.params["parent"] = aParent;
michael@0 734 stmt.params["rand"] = Math.round(Math.random() * (NUM_BOOKMARKS - 1));
michael@0 735 stmt.execute();
michael@0 736 stmt.reset();
michael@0 737 }
michael@0 738 stmt.finalize();
michael@0 739
michael@0 740 // Build the expected ordered list of bookmarks.
michael@0 741 stmt = mDBConn.createStatement(
michael@0 742 "SELECT id, position " +
michael@0 743 "FROM moz_bookmarks WHERE parent = :parent " +
michael@0 744 "ORDER BY position ASC, ROWID ASC "
michael@0 745 );
michael@0 746 stmt.params["parent"] = aParent;
michael@0 747 while (stmt.executeStep()) {
michael@0 748 aResultArray.push(stmt.row.id);
michael@0 749 print(stmt.row.id + "\t" + stmt.row.position + "\t" +
michael@0 750 (aResultArray.length - 1));
michael@0 751 }
michael@0 752 stmt.finalize();
michael@0 753 }
michael@0 754
michael@0 755 // Set random positions for the added bookmarks.
michael@0 756 randomize_positions(PlacesUtils.unfiledBookmarksFolderId,
michael@0 757 this._unfiledBookmarks);
michael@0 758 randomize_positions(PlacesUtils.toolbarFolderId, this._toolbarBookmarks);
michael@0 759 },
michael@0 760
michael@0 761 check: function() {
michael@0 762 function check_order(aParent, aResultArray) {
michael@0 763 // Build the expected ordered list of bookmarks.
michael@0 764 let stmt = mDBConn.createStatement(
michael@0 765 "SELECT id, position FROM moz_bookmarks WHERE parent = :parent " +
michael@0 766 "ORDER BY position ASC"
michael@0 767 );
michael@0 768 stmt.params["parent"] = aParent;
michael@0 769 let pass = true;
michael@0 770 while (stmt.executeStep()) {
michael@0 771 print(stmt.row.id + "\t" + stmt.row.position);
michael@0 772 if (aResultArray.indexOf(stmt.row.id) != stmt.row.position) {
michael@0 773 pass = false;
michael@0 774 }
michael@0 775 }
michael@0 776 stmt.finalize();
michael@0 777 if (!pass) {
michael@0 778 dump_table("moz_bookmarks");
michael@0 779 do_throw("Unexpected unfiled bookmarks order.");
michael@0 780 }
michael@0 781 }
michael@0 782
michael@0 783 check_order(PlacesUtils.unfiledBookmarksFolderId, this._unfiledBookmarks);
michael@0 784 check_order(PlacesUtils.toolbarFolderId, this._toolbarBookmarks);
michael@0 785 }
michael@0 786 });
michael@0 787
michael@0 788 //------------------------------------------------------------------------------
michael@0 789
michael@0 790 tests.push({
michael@0 791 name: "D.12",
michael@0 792 desc: "Fix empty-named tags",
michael@0 793
michael@0 794 setup: function() {
michael@0 795 // Add a place to ensure place_id = 1 is valid
michael@0 796 let placeId = addPlace();
michael@0 797 // Create a empty-named tag.
michael@0 798 this._untitledTagId = addBookmark(null, bs.TYPE_FOLDER, bs.tagsFolder, null, null, "");
michael@0 799 // Insert a bookmark in the tag, otherwise it will be removed.
michael@0 800 addBookmark(placeId, bs.TYPE_BOOKMARK, this._untitledTagId);
michael@0 801 // Create a empty-named folder.
michael@0 802 this._untitledFolderId = addBookmark(null, bs.TYPE_FOLDER, bs.toolbarFolder, null, null, "");
michael@0 803 // Create a titled tag.
michael@0 804 this._titledTagId = addBookmark(null, bs.TYPE_FOLDER, bs.tagsFolder, null, null, "titledTag");
michael@0 805 // Insert a bookmark in the tag, otherwise it will be removed.
michael@0 806 addBookmark(placeId, bs.TYPE_BOOKMARK, this._titledTagId);
michael@0 807 // Create a titled folder.
michael@0 808 this._titledFolderId = addBookmark(null, bs.TYPE_FOLDER, bs.toolbarFolder, null, null, "titledFolder");
michael@0 809 },
michael@0 810
michael@0 811 check: function() {
michael@0 812 // Check that valid bookmark is still there
michael@0 813 let stmt = mDBConn.createStatement(
michael@0 814 "SELECT title FROM moz_bookmarks WHERE id = :id"
michael@0 815 );
michael@0 816 stmt.params["id"] = this._untitledTagId;
michael@0 817 do_check_true(stmt.executeStep());
michael@0 818 do_check_eq(stmt.row.title, "(notitle)");
michael@0 819 stmt.reset();
michael@0 820 stmt.params["id"] = this._untitledFolderId;
michael@0 821 do_check_true(stmt.executeStep());
michael@0 822 do_check_eq(stmt.row.title, "");
michael@0 823 stmt.reset();
michael@0 824 stmt.params["id"] = this._titledTagId;
michael@0 825 do_check_true(stmt.executeStep());
michael@0 826 do_check_eq(stmt.row.title, "titledTag");
michael@0 827 stmt.reset();
michael@0 828 stmt.params["id"] = this._titledFolderId;
michael@0 829 do_check_true(stmt.executeStep());
michael@0 830 do_check_eq(stmt.row.title, "titledFolder");
michael@0 831 stmt.finalize();
michael@0 832 }
michael@0 833 });
michael@0 834
michael@0 835 //------------------------------------------------------------------------------
michael@0 836
michael@0 837 tests.push({
michael@0 838 name: "E.1",
michael@0 839 desc: "Remove orphan icons",
michael@0 840
michael@0 841 _placeId: null,
michael@0 842
michael@0 843 setup: function() {
michael@0 844 // Insert favicon entries
michael@0 845 let stmt = mDBConn.createStatement("INSERT INTO moz_favicons (id, url) VALUES(:favicon_id, :url)");
michael@0 846 stmt.params["favicon_id"] = 1;
michael@0 847 stmt.params["url"] = "http://www1.mozilla.org/favicon.ico";
michael@0 848 stmt.execute();
michael@0 849 stmt.reset();
michael@0 850 stmt.params["favicon_id"] = 2;
michael@0 851 stmt.params["url"] = "http://www2.mozilla.org/favicon.ico";
michael@0 852 stmt.execute();
michael@0 853 stmt.finalize();
michael@0 854 // Insert a place using the existing favicon entry
michael@0 855 this._placeId = addPlace("http://www.mozilla.org", 1);
michael@0 856 },
michael@0 857
michael@0 858 check: function() {
michael@0 859 // Check that used icon is still there
michael@0 860 let stmt = mDBConn.createStatement("SELECT id FROM moz_favicons WHERE id = :favicon_id");
michael@0 861 stmt.params["favicon_id"] = 1;
michael@0 862 do_check_true(stmt.executeStep());
michael@0 863 stmt.reset();
michael@0 864 // Check that unused icon has been removed
michael@0 865 stmt.params["favicon_id"] = 2;
michael@0 866 do_check_false(stmt.executeStep());
michael@0 867 stmt.finalize();
michael@0 868 }
michael@0 869 });
michael@0 870
michael@0 871 //------------------------------------------------------------------------------
michael@0 872
michael@0 873 tests.push({
michael@0 874 name: "F.1",
michael@0 875 desc: "Remove orphan visits",
michael@0 876
michael@0 877 _placeId: null,
michael@0 878 _invalidPlaceId: 1337,
michael@0 879
michael@0 880 setup: function() {
michael@0 881 // Add a place to ensure place_id = 1 is valid
michael@0 882 this._placeId = addPlace();
michael@0 883 // Add a valid visit and an invalid one
michael@0 884 stmt = mDBConn.createStatement("INSERT INTO moz_historyvisits(place_id) VALUES (:place_id)");
michael@0 885 stmt.params["place_id"] = this._placeId;
michael@0 886 stmt.execute();
michael@0 887 stmt.reset();
michael@0 888 stmt.params["place_id"] = this._invalidPlaceId;
michael@0 889 stmt.execute();
michael@0 890 stmt.finalize();
michael@0 891 },
michael@0 892
michael@0 893 check: function() {
michael@0 894 // Check that valid visit is still there
michael@0 895 let stmt = mDBConn.createStatement("SELECT id FROM moz_historyvisits WHERE place_id = :place_id");
michael@0 896 stmt.params["place_id"] = this._placeId;
michael@0 897 do_check_true(stmt.executeStep());
michael@0 898 stmt.reset();
michael@0 899 // Check that invalid visit has been removed
michael@0 900 stmt.params["place_id"] = this._invalidPlaceId;
michael@0 901 do_check_false(stmt.executeStep());
michael@0 902 stmt.finalize();
michael@0 903 }
michael@0 904 });
michael@0 905
michael@0 906 //------------------------------------------------------------------------------
michael@0 907
michael@0 908 tests.push({
michael@0 909 name: "G.1",
michael@0 910 desc: "Remove orphan input history",
michael@0 911
michael@0 912 _placeId: null,
michael@0 913 _invalidPlaceId: 1337,
michael@0 914
michael@0 915 setup: function() {
michael@0 916 // Add a place to ensure place_id = 1 is valid
michael@0 917 this._placeId = addPlace();
michael@0 918 // Add input history entries
michael@0 919 let stmt = mDBConn.createStatement("INSERT INTO moz_inputhistory (place_id, input) VALUES (:place_id, :input)");
michael@0 920 stmt.params["place_id"] = this._placeId;
michael@0 921 stmt.params["input"] = "moz";
michael@0 922 stmt.execute();
michael@0 923 stmt.reset();
michael@0 924 stmt.params["place_id"] = this._invalidPlaceId;
michael@0 925 stmt.params["input"] = "moz";
michael@0 926 stmt.execute();
michael@0 927 stmt.finalize();
michael@0 928 },
michael@0 929
michael@0 930 check: function() {
michael@0 931 // Check that inputhistory on valid place is still there
michael@0 932 let stmt = mDBConn.createStatement("SELECT place_id FROM moz_inputhistory WHERE place_id = :place_id");
michael@0 933 stmt.params["place_id"] = this._placeId;
michael@0 934 do_check_true(stmt.executeStep());
michael@0 935 stmt.reset();
michael@0 936 // Check that inputhistory on invalid place has gone
michael@0 937 stmt.params["place_id"] = this._invalidPlaceId;
michael@0 938 do_check_false(stmt.executeStep());
michael@0 939 stmt.finalize();
michael@0 940 }
michael@0 941 });
michael@0 942
michael@0 943 //------------------------------------------------------------------------------
michael@0 944
michael@0 945 tests.push({
michael@0 946 name: "H.1",
michael@0 947 desc: "Remove item annos with an invalid attribute",
michael@0 948
michael@0 949 _usedItemAttribute: "usedItem",
michael@0 950 _bookmarkId: null,
michael@0 951 _placeId: null,
michael@0 952
michael@0 953 setup: function() {
michael@0 954 // Add a place to ensure place_id = 1 is valid
michael@0 955 this._placeId = addPlace();
michael@0 956 // Insert a bookmark
michael@0 957 this._bookmarkId = addBookmark(this._placeId);
michael@0 958 // Add a used attribute.
michael@0 959 let stmt = mDBConn.createStatement("INSERT INTO moz_anno_attributes (name) VALUES (:anno)");
michael@0 960 stmt.params['anno'] = this._usedItemAttribute;
michael@0 961 stmt.execute();
michael@0 962 stmt.finalize();
michael@0 963 stmt = mDBConn.createStatement("INSERT INTO moz_items_annos (item_id, anno_attribute_id) VALUES(:item_id, (SELECT id FROM moz_anno_attributes WHERE name = :anno))");
michael@0 964 stmt.params['item_id'] = this._bookmarkId;
michael@0 965 stmt.params['anno'] = this._usedItemAttribute;
michael@0 966 stmt.execute();
michael@0 967 stmt.finalize();
michael@0 968 // Add an annotation with a nonexistent attribute
michael@0 969 stmt = mDBConn.createStatement("INSERT INTO moz_items_annos (item_id, anno_attribute_id) VALUES(:item_id, 1337)");
michael@0 970 stmt.params['item_id'] = this._bookmarkId;
michael@0 971 stmt.execute();
michael@0 972 stmt.finalize();
michael@0 973 },
michael@0 974
michael@0 975 check: function() {
michael@0 976 // Check that used attribute is still there
michael@0 977 let stmt = mDBConn.createStatement("SELECT id FROM moz_anno_attributes WHERE name = :anno");
michael@0 978 stmt.params['anno'] = this._usedItemAttribute;
michael@0 979 do_check_true(stmt.executeStep());
michael@0 980 stmt.finalize();
michael@0 981 // check that annotation with valid attribute is still there
michael@0 982 stmt = mDBConn.createStatement("SELECT id FROM moz_items_annos WHERE anno_attribute_id = (SELECT id FROM moz_anno_attributes WHERE name = :anno)");
michael@0 983 stmt.params['anno'] = this._usedItemAttribute;
michael@0 984 do_check_true(stmt.executeStep());
michael@0 985 stmt.finalize();
michael@0 986 // Check that annotation with bogus attribute has been removed
michael@0 987 stmt = mDBConn.createStatement("SELECT id FROM moz_items_annos WHERE anno_attribute_id = 1337");
michael@0 988 do_check_false(stmt.executeStep());
michael@0 989 stmt.finalize();
michael@0 990 }
michael@0 991 });
michael@0 992
michael@0 993 //------------------------------------------------------------------------------
michael@0 994
michael@0 995 tests.push({
michael@0 996 name: "H.2",
michael@0 997 desc: "Remove orphan item annotations",
michael@0 998
michael@0 999 _usedItemAttribute: "usedItem",
michael@0 1000 _bookmarkId: null,
michael@0 1001 _invalidBookmarkId: 8888,
michael@0 1002 _placeId: null,
michael@0 1003
michael@0 1004 setup: function() {
michael@0 1005 // Add a place to ensure place_id = 1 is valid
michael@0 1006 this._placeId = addPlace();
michael@0 1007 // Insert a bookmark
michael@0 1008 this._bookmarkId = addBookmark(this._placeId);
michael@0 1009 // Add a used attribute.
michael@0 1010 stmt = mDBConn.createStatement("INSERT INTO moz_anno_attributes (name) VALUES (:anno)");
michael@0 1011 stmt.params['anno'] = this._usedItemAttribute;
michael@0 1012 stmt.execute();
michael@0 1013 stmt.finalize();
michael@0 1014 stmt = mDBConn.createStatement("INSERT INTO moz_items_annos (item_id, anno_attribute_id) VALUES (:item_id, (SELECT id FROM moz_anno_attributes WHERE name = :anno))");
michael@0 1015 stmt.params["item_id"] = this._bookmarkId;
michael@0 1016 stmt.params["anno"] = this._usedItemAttribute;
michael@0 1017 stmt.execute();
michael@0 1018 stmt.reset();
michael@0 1019 // Add an annotation to a nonexistent item
michael@0 1020 stmt.params["item_id"] = this._invalidBookmarkId;
michael@0 1021 stmt.params["anno"] = this._usedItemAttribute;
michael@0 1022 stmt.execute();
michael@0 1023 stmt.finalize();
michael@0 1024 },
michael@0 1025
michael@0 1026 check: function() {
michael@0 1027 // Check that used attribute is still there
michael@0 1028 let stmt = mDBConn.createStatement("SELECT id FROM moz_anno_attributes WHERE name = :anno");
michael@0 1029 stmt.params['anno'] = this._usedItemAttribute;
michael@0 1030 do_check_true(stmt.executeStep());
michael@0 1031 stmt.finalize();
michael@0 1032 // check that annotation with valid attribute is still there
michael@0 1033 stmt = mDBConn.createStatement("SELECT id FROM moz_items_annos WHERE anno_attribute_id = (SELECT id FROM moz_anno_attributes WHERE name = :anno)");
michael@0 1034 stmt.params['anno'] = this._usedItemAttribute;
michael@0 1035 do_check_true(stmt.executeStep());
michael@0 1036 stmt.finalize();
michael@0 1037 // Check that an annotation to a nonexistent page has been removed
michael@0 1038 stmt = mDBConn.createStatement("SELECT id FROM moz_items_annos WHERE item_id = 8888");
michael@0 1039 do_check_false(stmt.executeStep());
michael@0 1040 stmt.finalize();
michael@0 1041 }
michael@0 1042 });
michael@0 1043
michael@0 1044
michael@0 1045 //------------------------------------------------------------------------------
michael@0 1046
michael@0 1047 tests.push({
michael@0 1048 name: "I.1",
michael@0 1049 desc: "Remove unused keywords",
michael@0 1050
michael@0 1051 _bookmarkId: null,
michael@0 1052 _placeId: null,
michael@0 1053
michael@0 1054 setup: function() {
michael@0 1055 // Insert 2 keywords
michael@0 1056 let stmt = mDBConn.createStatement("INSERT INTO moz_keywords (id, keyword) VALUES(:id, :keyword)");
michael@0 1057 stmt.params["id"] = 1;
michael@0 1058 stmt.params["keyword"] = "used";
michael@0 1059 stmt.execute();
michael@0 1060 stmt.reset();
michael@0 1061 stmt.params["id"] = 2;
michael@0 1062 stmt.params["keyword"] = "unused";
michael@0 1063 stmt.execute();
michael@0 1064 stmt.finalize();
michael@0 1065 // Add a place to ensure place_id = 1 is valid
michael@0 1066 this._placeId = addPlace();
michael@0 1067 // Insert a bookmark using the "used" keyword
michael@0 1068 this._bookmarkId = addBookmark(this._placeId, bs.TYPE_BOOKMARK, bs.unfiledBookmarksFolder, 1);
michael@0 1069 },
michael@0 1070
michael@0 1071 check: function() {
michael@0 1072 // Check that "used" keyword is still there
michael@0 1073 let stmt = mDBConn.createStatement("SELECT id FROM moz_keywords WHERE keyword = :keyword");
michael@0 1074 stmt.params["keyword"] = "used";
michael@0 1075 do_check_true(stmt.executeStep());
michael@0 1076 stmt.reset();
michael@0 1077 // Check that "unused" keyword has gone
michael@0 1078 stmt.params["keyword"] = "unused";
michael@0 1079 do_check_false(stmt.executeStep());
michael@0 1080 stmt.finalize();
michael@0 1081 }
michael@0 1082 });
michael@0 1083
michael@0 1084
michael@0 1085 //------------------------------------------------------------------------------
michael@0 1086
michael@0 1087 tests.push({
michael@0 1088 name: "L.1",
michael@0 1089 desc: "Fix wrong favicon ids",
michael@0 1090
michael@0 1091 _validIconPlaceId: null,
michael@0 1092 _invalidIconPlaceId: null,
michael@0 1093
michael@0 1094 setup: function() {
michael@0 1095 // Insert a favicon entry
michael@0 1096 let stmt = mDBConn.createStatement("INSERT INTO moz_favicons (id, url) VALUES(1, :url)");
michael@0 1097 stmt.params["url"] = "http://www.mozilla.org/favicon.ico";
michael@0 1098 stmt.execute();
michael@0 1099 stmt.finalize();
michael@0 1100 // Insert a place using the existing favicon entry
michael@0 1101 this._validIconPlaceId = addPlace("http://www1.mozilla.org", 1);
michael@0 1102
michael@0 1103 // Insert a place using a nonexistent favicon entry
michael@0 1104 this._invalidIconPlaceId = addPlace("http://www2.mozilla.org", 1337);
michael@0 1105 },
michael@0 1106
michael@0 1107 check: function() {
michael@0 1108 // Check that bogus favicon is not there
michael@0 1109 let stmt = mDBConn.createStatement("SELECT id FROM moz_places WHERE favicon_id = :favicon_id");
michael@0 1110 stmt.params["favicon_id"] = 1337;
michael@0 1111 do_check_false(stmt.executeStep());
michael@0 1112 stmt.reset();
michael@0 1113 // Check that valid favicon is still there
michael@0 1114 stmt.params["favicon_id"] = 1;
michael@0 1115 do_check_true(stmt.executeStep());
michael@0 1116 stmt.finalize();
michael@0 1117 // Check that place entries are there
michael@0 1118 stmt = mDBConn.createStatement("SELECT id FROM moz_places WHERE id = :place_id");
michael@0 1119 stmt.params["place_id"] = this._validIconPlaceId;
michael@0 1120 do_check_true(stmt.executeStep());
michael@0 1121 stmt.reset();
michael@0 1122 stmt.params["place_id"] = this._invalidIconPlaceId;
michael@0 1123 do_check_true(stmt.executeStep());
michael@0 1124 stmt.finalize();
michael@0 1125 }
michael@0 1126 });
michael@0 1127
michael@0 1128 //------------------------------------------------------------------------------
michael@0 1129
michael@0 1130 tests.push({
michael@0 1131 name: "L.2",
michael@0 1132 desc: "Recalculate visit_count and last_visit_date",
michael@0 1133
michael@0 1134 setup: function() {
michael@0 1135 function setVisitCount(aURL, aValue) {
michael@0 1136 let stmt = mDBConn.createStatement(
michael@0 1137 "UPDATE moz_places SET visit_count = :count WHERE url = :url"
michael@0 1138 );
michael@0 1139 stmt.params.count = aValue;
michael@0 1140 stmt.params.url = aURL;
michael@0 1141 stmt.execute();
michael@0 1142 stmt.finalize();
michael@0 1143 }
michael@0 1144 function setLastVisitDate(aURL, aValue) {
michael@0 1145 let stmt = mDBConn.createStatement(
michael@0 1146 "UPDATE moz_places SET last_visit_date = :date WHERE url = :url"
michael@0 1147 );
michael@0 1148 stmt.params.date = aValue;
michael@0 1149 stmt.params.url = aURL;
michael@0 1150 stmt.execute();
michael@0 1151 stmt.finalize();
michael@0 1152 }
michael@0 1153
michael@0 1154 let now = Date.now() * 1000;
michael@0 1155 // Add a page with 1 visit.
michael@0 1156 let url = "http://1.moz.org/";
michael@0 1157 yield promiseAddVisits({ uri: uri(url), visitDate: now++ });
michael@0 1158 // Add a page with 1 visit and set wrong visit_count.
michael@0 1159 url = "http://2.moz.org/";
michael@0 1160 yield promiseAddVisits({ uri: uri(url), visitDate: now++ });
michael@0 1161 setVisitCount(url, 10);
michael@0 1162 // Add a page with 1 visit and set wrong last_visit_date.
michael@0 1163 url = "http://3.moz.org/";
michael@0 1164 yield promiseAddVisits({ uri: uri(url), visitDate: now++ });
michael@0 1165 setLastVisitDate(url, now++);
michael@0 1166 // Add a page with 1 visit and set wrong stats.
michael@0 1167 url = "http://4.moz.org/";
michael@0 1168 yield promiseAddVisits({ uri: uri(url), visitDate: now++ });
michael@0 1169 setVisitCount(url, 10);
michael@0 1170 setLastVisitDate(url, now++);
michael@0 1171
michael@0 1172 // Add a page without visits.
michael@0 1173 let url = "http://5.moz.org/";
michael@0 1174 addPlace(url);
michael@0 1175 // Add a page without visits and set wrong visit_count.
michael@0 1176 url = "http://6.moz.org/";
michael@0 1177 addPlace(url);
michael@0 1178 setVisitCount(url, 10);
michael@0 1179 // Add a page without visits and set wrong last_visit_date.
michael@0 1180 url = "http://7.moz.org/";
michael@0 1181 addPlace(url);
michael@0 1182 setLastVisitDate(url, now++);
michael@0 1183 // Add a page without visits and set wrong stats.
michael@0 1184 url = "http://8.moz.org/";
michael@0 1185 addPlace(url);
michael@0 1186 setVisitCount(url, 10);
michael@0 1187 setLastVisitDate(url, now++);
michael@0 1188 },
michael@0 1189
michael@0 1190 check: function() {
michael@0 1191 let stmt = mDBConn.createStatement(
michael@0 1192 "SELECT h.id FROM moz_places h " +
michael@0 1193 "JOIN moz_historyvisits v ON v.place_id = h.id AND visit_type NOT IN (0,4,7,8) " +
michael@0 1194 "GROUP BY h.id HAVING h.visit_count <> count(*) " +
michael@0 1195 "UNION ALL " +
michael@0 1196 "SELECT h.id FROM moz_places h " +
michael@0 1197 "JOIN moz_historyvisits v ON v.place_id = h.id " +
michael@0 1198 "GROUP BY h.id HAVING h.last_visit_date <> MAX(v.visit_date) "
michael@0 1199 );
michael@0 1200 do_check_false(stmt.executeStep());
michael@0 1201 stmt.finalize();
michael@0 1202 }
michael@0 1203 });
michael@0 1204
michael@0 1205 //------------------------------------------------------------------------------
michael@0 1206
michael@0 1207 tests.push({
michael@0 1208 name: "L.3",
michael@0 1209 desc: "recalculate hidden for redirects.",
michael@0 1210
michael@0 1211 setup: function() {
michael@0 1212 promiseAddVisits([
michael@0 1213 { uri: NetUtil.newURI("http://l3.moz.org/"),
michael@0 1214 transition: TRANSITION_TYPED },
michael@0 1215 { uri: NetUtil.newURI("http://l3.moz.org/redirecting/"),
michael@0 1216 transition: TRANSITION_TYPED },
michael@0 1217 { uri: NetUtil.newURI("http://l3.moz.org/redirecting2/"),
michael@0 1218 transition: TRANSITION_REDIRECT_TEMPORARY,
michael@0 1219 referrer: NetUtil.newURI("http://l3.moz.org/redirecting/") },
michael@0 1220 { uri: NetUtil.newURI("http://l3.moz.org/target/"),
michael@0 1221 transition: TRANSITION_REDIRECT_PERMANENT,
michael@0 1222 referrer: NetUtil.newURI("http://l3.moz.org/redirecting2/") },
michael@0 1223 ]);
michael@0 1224 },
michael@0 1225
michael@0 1226 asyncCheck: function(aCallback) {
michael@0 1227 let stmt = mDBConn.createAsyncStatement(
michael@0 1228 "SELECT h.url FROM moz_places h WHERE h.hidden = 1"
michael@0 1229 );
michael@0 1230 stmt.executeAsync({
michael@0 1231 _count: 0,
michael@0 1232 handleResult: function(aResultSet) {
michael@0 1233 for (let row; (row = aResultSet.getNextRow());) {
michael@0 1234 let url = row.getResultByIndex(0);
michael@0 1235 do_check_true(/redirecting/.test(url));
michael@0 1236 this._count++;
michael@0 1237 }
michael@0 1238 },
michael@0 1239 handleError: function(aError) {
michael@0 1240 },
michael@0 1241 handleCompletion: function(aReason) {
michael@0 1242 dump_table("moz_places");
michael@0 1243 dump_table("moz_historyvisits");
michael@0 1244 do_check_eq(aReason, Ci.mozIStorageStatementCallback.REASON_FINISHED);
michael@0 1245 do_check_eq(this._count, 2);
michael@0 1246 aCallback();
michael@0 1247 }
michael@0 1248 });
michael@0 1249 stmt.finalize();
michael@0 1250 }
michael@0 1251 });
michael@0 1252
michael@0 1253 //------------------------------------------------------------------------------
michael@0 1254
michael@0 1255 tests.push({
michael@0 1256 name: "Z",
michael@0 1257 desc: "Sanity: Preventive maintenance does not touch valid items",
michael@0 1258
michael@0 1259 _uri1: uri("http://www1.mozilla.org"),
michael@0 1260 _uri2: uri("http://www2.mozilla.org"),
michael@0 1261 _folderId: null,
michael@0 1262 _bookmarkId: null,
michael@0 1263 _separatorId: null,
michael@0 1264
michael@0 1265 setup: function() {
michael@0 1266 // use valid api calls to create a bunch of items
michael@0 1267 yield promiseAddVisits([
michael@0 1268 { uri: this._uri1 },
michael@0 1269 { uri: this._uri2 },
michael@0 1270 ]);
michael@0 1271
michael@0 1272 this._folderId = bs.createFolder(bs.toolbarFolder, "testfolder",
michael@0 1273 bs.DEFAULT_INDEX);
michael@0 1274 do_check_true(this._folderId > 0);
michael@0 1275 this._bookmarkId = bs.insertBookmark(this._folderId, this._uri1,
michael@0 1276 bs.DEFAULT_INDEX, "testbookmark");
michael@0 1277 do_check_true(this._bookmarkId > 0);
michael@0 1278 this._separatorId = bs.insertSeparator(bs.unfiledBookmarksFolder,
michael@0 1279 bs.DEFAULT_INDEX);
michael@0 1280 do_check_true(this._separatorId > 0);
michael@0 1281 ts.tagURI(this._uri1, ["testtag"]);
michael@0 1282 fs.setAndFetchFaviconForPage(this._uri2, SMALLPNG_DATA_URI, false,
michael@0 1283 PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE);
michael@0 1284 bs.setKeywordForBookmark(this._bookmarkId, "testkeyword");
michael@0 1285 as.setPageAnnotation(this._uri2, "anno", "anno", 0, as.EXPIRE_NEVER);
michael@0 1286 as.setItemAnnotation(this._bookmarkId, "anno", "anno", 0, as.EXPIRE_NEVER);
michael@0 1287 },
michael@0 1288
michael@0 1289 asyncCheck: function (aCallback) {
michael@0 1290 // Check that all items are correct
michael@0 1291 PlacesUtils.asyncHistory.isURIVisited(this._uri1, function(aURI, aIsVisited) {
michael@0 1292 do_check_true(aIsVisited);
michael@0 1293 PlacesUtils.asyncHistory.isURIVisited(this._uri2, function(aURI, aIsVisited) {
michael@0 1294 do_check_true(aIsVisited);
michael@0 1295
michael@0 1296 do_check_eq(bs.getBookmarkURI(this._bookmarkId).spec, this._uri1.spec);
michael@0 1297 do_check_eq(bs.getItemIndex(this._folderId), 0);
michael@0 1298
michael@0 1299 do_check_eq(bs.getItemType(this._folderId), bs.TYPE_FOLDER);
michael@0 1300 do_check_eq(bs.getItemType(this._separatorId), bs.TYPE_SEPARATOR);
michael@0 1301
michael@0 1302 do_check_eq(ts.getTagsForURI(this._uri1).length, 1);
michael@0 1303 do_check_eq(bs.getKeywordForBookmark(this._bookmarkId), "testkeyword");
michael@0 1304 do_check_eq(as.getPageAnnotation(this._uri2, "anno"), "anno");
michael@0 1305 do_check_eq(as.getItemAnnotation(this._bookmarkId, "anno"), "anno");
michael@0 1306
michael@0 1307 fs.getFaviconURLForPage(this._uri2, function (aFaviconURI) {
michael@0 1308 do_check_true(aFaviconURI.equals(SMALLPNG_DATA_URI));
michael@0 1309 aCallback();
michael@0 1310 });
michael@0 1311 }.bind(this));
michael@0 1312 }.bind(this));
michael@0 1313 }
michael@0 1314 });
michael@0 1315
michael@0 1316 //------------------------------------------------------------------------------
michael@0 1317
michael@0 1318 // main
michael@0 1319 function run_test()
michael@0 1320 {
michael@0 1321 run_next_test();
michael@0 1322 }
michael@0 1323
michael@0 1324 add_task(function test_preventive_maintenance()
michael@0 1325 {
michael@0 1326 // Force initialization of the bookmarks hash. This test could cause
michael@0 1327 // it to go out of sync due to direct queries on the database.
michael@0 1328 yield promiseAddVisits(uri("http://force.bookmarks.hash"));
michael@0 1329 do_check_false(bs.isBookmarked(uri("http://force.bookmarks.hash")));
michael@0 1330
michael@0 1331 // Get current bookmarks max ID for cleanup
michael@0 1332 let stmt = mDBConn.createStatement("SELECT MAX(id) FROM moz_bookmarks");
michael@0 1333 stmt.executeStep();
michael@0 1334 defaultBookmarksMaxId = stmt.getInt32(0);
michael@0 1335 stmt.finalize();
michael@0 1336 do_check_true(defaultBookmarksMaxId > 0);
michael@0 1337
michael@0 1338 for ([, test] in Iterator(tests)) {
michael@0 1339 dump("\nExecuting test: " + test.name + "\n" + "*** " + test.desc + "\n");
michael@0 1340 yield test.setup();
michael@0 1341
michael@0 1342 let promiseMaintenanceFinished =
michael@0 1343 promiseTopicObserved(FINISHED_MAINTENANCE_NOTIFICATION_TOPIC);
michael@0 1344 PlacesDBUtils.maintenanceOnIdle();
michael@0 1345 yield promiseMaintenanceFinished;
michael@0 1346
michael@0 1347 // Check the lastMaintenance time has been saved.
michael@0 1348 do_check_neq(Services.prefs.getIntPref("places.database.lastMaintenance"), null);
michael@0 1349
michael@0 1350 if (test.asyncCheck) {
michael@0 1351 let deferred = Promise.defer();
michael@0 1352 test.asyncCheck(deferred.resolve);
michael@0 1353 yield deferred.promise;
michael@0 1354 } else {
michael@0 1355 test.check();
michael@0 1356 }
michael@0 1357
michael@0 1358 cleanDatabase();
michael@0 1359 }
michael@0 1360
michael@0 1361 // Sanity check: all roots should be intact
michael@0 1362 do_check_eq(bs.getFolderIdForItem(bs.placesRoot), 0);
michael@0 1363 do_check_eq(bs.getFolderIdForItem(bs.bookmarksMenuFolder), bs.placesRoot);
michael@0 1364 do_check_eq(bs.getFolderIdForItem(bs.tagsFolder), bs.placesRoot);
michael@0 1365 do_check_eq(bs.getFolderIdForItem(bs.unfiledBookmarksFolder), bs.placesRoot);
michael@0 1366 do_check_eq(bs.getFolderIdForItem(bs.toolbarFolder), bs.placesRoot);
michael@0 1367 });

mercurial