toolkit/components/places/tests/head_common.js

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
michael@0 2 * This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 const CURRENT_SCHEMA_VERSION = 23;
michael@0 7
michael@0 8 const NS_APP_USER_PROFILE_50_DIR = "ProfD";
michael@0 9 const NS_APP_PROFILE_DIR_STARTUP = "ProfDS";
michael@0 10
michael@0 11 // Shortcuts to transitions type.
michael@0 12 const TRANSITION_LINK = Ci.nsINavHistoryService.TRANSITION_LINK;
michael@0 13 const TRANSITION_TYPED = Ci.nsINavHistoryService.TRANSITION_TYPED;
michael@0 14 const TRANSITION_BOOKMARK = Ci.nsINavHistoryService.TRANSITION_BOOKMARK;
michael@0 15 const TRANSITION_EMBED = Ci.nsINavHistoryService.TRANSITION_EMBED;
michael@0 16 const TRANSITION_FRAMED_LINK = Ci.nsINavHistoryService.TRANSITION_FRAMED_LINK;
michael@0 17 const TRANSITION_REDIRECT_PERMANENT = Ci.nsINavHistoryService.TRANSITION_REDIRECT_PERMANENT;
michael@0 18 const TRANSITION_REDIRECT_TEMPORARY = Ci.nsINavHistoryService.TRANSITION_REDIRECT_TEMPORARY;
michael@0 19 const TRANSITION_DOWNLOAD = Ci.nsINavHistoryService.TRANSITION_DOWNLOAD;
michael@0 20
michael@0 21 const TITLE_LENGTH_MAX = 4096;
michael@0 22
michael@0 23 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 24
michael@0 25 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
michael@0 26 "resource://gre/modules/FileUtils.jsm");
michael@0 27 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
michael@0 28 "resource://gre/modules/NetUtil.jsm");
michael@0 29 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
michael@0 30 "resource://gre/modules/Promise.jsm");
michael@0 31 XPCOMUtils.defineLazyModuleGetter(this, "Services",
michael@0 32 "resource://gre/modules/Services.jsm");
michael@0 33 XPCOMUtils.defineLazyModuleGetter(this, "Task",
michael@0 34 "resource://gre/modules/Task.jsm");
michael@0 35 XPCOMUtils.defineLazyModuleGetter(this, "BookmarkJSONUtils",
michael@0 36 "resource://gre/modules/BookmarkJSONUtils.jsm");
michael@0 37 XPCOMUtils.defineLazyModuleGetter(this, "BookmarkHTMLUtils",
michael@0 38 "resource://gre/modules/BookmarkHTMLUtils.jsm");
michael@0 39 XPCOMUtils.defineLazyModuleGetter(this, "PlacesBackups",
michael@0 40 "resource://gre/modules/PlacesBackups.jsm");
michael@0 41 XPCOMUtils.defineLazyModuleGetter(this, "PlacesTransactions",
michael@0 42 "resource://gre/modules/PlacesTransactions.jsm");
michael@0 43 XPCOMUtils.defineLazyModuleGetter(this, "OS",
michael@0 44 "resource://gre/modules/osfile.jsm");
michael@0 45
michael@0 46 // This imports various other objects in addition to PlacesUtils.
michael@0 47 Cu.import("resource://gre/modules/PlacesUtils.jsm");
michael@0 48
michael@0 49 XPCOMUtils.defineLazyGetter(this, "SMALLPNG_DATA_URI", function() {
michael@0 50 return NetUtil.newURI(
michael@0 51 "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAA" +
michael@0 52 "AAAA6fptVAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg==");
michael@0 53 });
michael@0 54
michael@0 55 function LOG(aMsg) {
michael@0 56 aMsg = ("*** PLACES TESTS: " + aMsg);
michael@0 57 Services.console.logStringMessage(aMsg);
michael@0 58 print(aMsg);
michael@0 59 }
michael@0 60
michael@0 61 let gTestDir = do_get_cwd();
michael@0 62
michael@0 63 // Initialize profile.
michael@0 64 let gProfD = do_get_profile();
michael@0 65
michael@0 66 // Remove any old database.
michael@0 67 clearDB();
michael@0 68
michael@0 69 /**
michael@0 70 * Shortcut to create a nsIURI.
michael@0 71 *
michael@0 72 * @param aSpec
michael@0 73 * URLString of the uri.
michael@0 74 */
michael@0 75 function uri(aSpec) NetUtil.newURI(aSpec);
michael@0 76
michael@0 77
michael@0 78 /**
michael@0 79 * Gets the database connection. If the Places connection is invalid it will
michael@0 80 * try to create a new connection.
michael@0 81 *
michael@0 82 * @param [optional] aForceNewConnection
michael@0 83 * Forces creation of a new connection to the database. When a
michael@0 84 * connection is asyncClosed it cannot anymore schedule async statements,
michael@0 85 * though connectionReady will keep returning true (Bug 726990).
michael@0 86 *
michael@0 87 * @return The database connection or null if unable to get one.
michael@0 88 */
michael@0 89 let gDBConn;
michael@0 90 function DBConn(aForceNewConnection) {
michael@0 91 if (!aForceNewConnection) {
michael@0 92 let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
michael@0 93 .DBConnection;
michael@0 94 if (db.connectionReady)
michael@0 95 return db;
michael@0 96 }
michael@0 97
michael@0 98 // If the Places database connection has been closed, create a new connection.
michael@0 99 if (!gDBConn || aForceNewConnection) {
michael@0 100 let file = Services.dirsvc.get('ProfD', Ci.nsIFile);
michael@0 101 file.append("places.sqlite");
michael@0 102 let dbConn = gDBConn = Services.storage.openDatabase(file);
michael@0 103
michael@0 104 // Be sure to cleanly close this connection.
michael@0 105 Services.obs.addObserver(function DBCloseCallback(aSubject, aTopic, aData) {
michael@0 106 Services.obs.removeObserver(DBCloseCallback, aTopic);
michael@0 107 dbConn.asyncClose();
michael@0 108 }, "profile-before-change", false);
michael@0 109 }
michael@0 110
michael@0 111 return gDBConn.connectionReady ? gDBConn : null;
michael@0 112 };
michael@0 113
michael@0 114 /**
michael@0 115 * Reads data from the provided inputstream.
michael@0 116 *
michael@0 117 * @return an array of bytes.
michael@0 118 */
michael@0 119 function readInputStreamData(aStream) {
michael@0 120 let bistream = Cc["@mozilla.org/binaryinputstream;1"].
michael@0 121 createInstance(Ci.nsIBinaryInputStream);
michael@0 122 try {
michael@0 123 bistream.setInputStream(aStream);
michael@0 124 let expectedData = [];
michael@0 125 let avail;
michael@0 126 while ((avail = bistream.available())) {
michael@0 127 expectedData = expectedData.concat(bistream.readByteArray(avail));
michael@0 128 }
michael@0 129 return expectedData;
michael@0 130 } finally {
michael@0 131 bistream.close();
michael@0 132 }
michael@0 133 }
michael@0 134
michael@0 135 /**
michael@0 136 * Reads the data from the specified nsIFile.
michael@0 137 *
michael@0 138 * @param aFile
michael@0 139 * The nsIFile to read from.
michael@0 140 * @return an array of bytes.
michael@0 141 */
michael@0 142 function readFileData(aFile) {
michael@0 143 let inputStream = Cc["@mozilla.org/network/file-input-stream;1"].
michael@0 144 createInstance(Ci.nsIFileInputStream);
michael@0 145 // init the stream as RD_ONLY, -1 == default permissions.
michael@0 146 inputStream.init(aFile, 0x01, -1, null);
michael@0 147
michael@0 148 // Check the returned size versus the expected size.
michael@0 149 let size = inputStream.available();
michael@0 150 let bytes = readInputStreamData(inputStream);
michael@0 151 if (size != bytes.length) {
michael@0 152 throw "Didn't read expected number of bytes";
michael@0 153 }
michael@0 154 return bytes;
michael@0 155 }
michael@0 156
michael@0 157 /**
michael@0 158 * Reads the data from the named file, verifying the expected file length.
michael@0 159 *
michael@0 160 * @param aFileName
michael@0 161 * This file should be located in the same folder as the test.
michael@0 162 * @param aExpectedLength
michael@0 163 * Expected length of the file.
michael@0 164 *
michael@0 165 * @return The array of bytes read from the file.
michael@0 166 */
michael@0 167 function readFileOfLength(aFileName, aExpectedLength) {
michael@0 168 let data = readFileData(do_get_file(aFileName));
michael@0 169 do_check_eq(data.length, aExpectedLength);
michael@0 170 return data;
michael@0 171 }
michael@0 172
michael@0 173
michael@0 174 /**
michael@0 175 * Returns the base64-encoded version of the given string. This function is
michael@0 176 * similar to window.btoa, but is available to xpcshell tests also.
michael@0 177 *
michael@0 178 * @param aString
michael@0 179 * Each character in this string corresponds to a byte, and must be a
michael@0 180 * code point in the range 0-255.
michael@0 181 *
michael@0 182 * @return The base64-encoded string.
michael@0 183 */
michael@0 184 function base64EncodeString(aString) {
michael@0 185 var stream = Cc["@mozilla.org/io/string-input-stream;1"]
michael@0 186 .createInstance(Ci.nsIStringInputStream);
michael@0 187 stream.setData(aString, aString.length);
michael@0 188 var encoder = Cc["@mozilla.org/scriptablebase64encoder;1"]
michael@0 189 .createInstance(Ci.nsIScriptableBase64Encoder);
michael@0 190 return encoder.encodeToString(stream, aString.length);
michael@0 191 }
michael@0 192
michael@0 193
michael@0 194 /**
michael@0 195 * Compares two arrays, and returns true if they are equal.
michael@0 196 *
michael@0 197 * @param aArray1
michael@0 198 * First array to compare.
michael@0 199 * @param aArray2
michael@0 200 * Second array to compare.
michael@0 201 */
michael@0 202 function compareArrays(aArray1, aArray2) {
michael@0 203 if (aArray1.length != aArray2.length) {
michael@0 204 print("compareArrays: array lengths differ\n");
michael@0 205 return false;
michael@0 206 }
michael@0 207
michael@0 208 for (let i = 0; i < aArray1.length; i++) {
michael@0 209 if (aArray1[i] != aArray2[i]) {
michael@0 210 print("compareArrays: arrays differ at index " + i + ": " +
michael@0 211 "(" + aArray1[i] + ") != (" + aArray2[i] +")\n");
michael@0 212 return false;
michael@0 213 }
michael@0 214 }
michael@0 215
michael@0 216 return true;
michael@0 217 }
michael@0 218
michael@0 219
michael@0 220 /**
michael@0 221 * Deletes a previously created sqlite file from the profile folder.
michael@0 222 */
michael@0 223 function clearDB() {
michael@0 224 try {
michael@0 225 let file = Services.dirsvc.get('ProfD', Ci.nsIFile);
michael@0 226 file.append("places.sqlite");
michael@0 227 if (file.exists())
michael@0 228 file.remove(false);
michael@0 229 } catch(ex) { dump("Exception: " + ex); }
michael@0 230 }
michael@0 231
michael@0 232
michael@0 233 /**
michael@0 234 * Dumps the rows of a table out to the console.
michael@0 235 *
michael@0 236 * @param aName
michael@0 237 * The name of the table or view to output.
michael@0 238 */
michael@0 239 function dump_table(aName)
michael@0 240 {
michael@0 241 let stmt = DBConn().createStatement("SELECT * FROM " + aName);
michael@0 242
michael@0 243 print("\n*** Printing data from " + aName);
michael@0 244 let count = 0;
michael@0 245 while (stmt.executeStep()) {
michael@0 246 let columns = stmt.numEntries;
michael@0 247
michael@0 248 if (count == 0) {
michael@0 249 // Print the column names.
michael@0 250 for (let i = 0; i < columns; i++)
michael@0 251 dump(stmt.getColumnName(i) + "\t");
michael@0 252 dump("\n");
michael@0 253 }
michael@0 254
michael@0 255 // Print the rows.
michael@0 256 for (let i = 0; i < columns; i++) {
michael@0 257 switch (stmt.getTypeOfIndex(i)) {
michael@0 258 case Ci.mozIStorageValueArray.VALUE_TYPE_NULL:
michael@0 259 dump("NULL\t");
michael@0 260 break;
michael@0 261 case Ci.mozIStorageValueArray.VALUE_TYPE_INTEGER:
michael@0 262 dump(stmt.getInt64(i) + "\t");
michael@0 263 break;
michael@0 264 case Ci.mozIStorageValueArray.VALUE_TYPE_FLOAT:
michael@0 265 dump(stmt.getDouble(i) + "\t");
michael@0 266 break;
michael@0 267 case Ci.mozIStorageValueArray.VALUE_TYPE_TEXT:
michael@0 268 dump(stmt.getString(i) + "\t");
michael@0 269 break;
michael@0 270 }
michael@0 271 }
michael@0 272 dump("\n");
michael@0 273
michael@0 274 count++;
michael@0 275 }
michael@0 276 print("*** There were a total of " + count + " rows of data.\n");
michael@0 277
michael@0 278 stmt.finalize();
michael@0 279 }
michael@0 280
michael@0 281
michael@0 282 /**
michael@0 283 * Checks if an address is found in the database.
michael@0 284 * @param aURI
michael@0 285 * nsIURI or address to look for.
michael@0 286 * @return place id of the page or 0 if not found
michael@0 287 */
michael@0 288 function page_in_database(aURI)
michael@0 289 {
michael@0 290 let url = aURI instanceof Ci.nsIURI ? aURI.spec : aURI;
michael@0 291 let stmt = DBConn().createStatement(
michael@0 292 "SELECT id FROM moz_places WHERE url = :url"
michael@0 293 );
michael@0 294 stmt.params.url = url;
michael@0 295 try {
michael@0 296 if (!stmt.executeStep())
michael@0 297 return 0;
michael@0 298 return stmt.getInt64(0);
michael@0 299 }
michael@0 300 finally {
michael@0 301 stmt.finalize();
michael@0 302 }
michael@0 303 }
michael@0 304
michael@0 305 /**
michael@0 306 * Checks how many visits exist for a specified page.
michael@0 307 * @param aURI
michael@0 308 * nsIURI or address to look for.
michael@0 309 * @return number of visits found.
michael@0 310 */
michael@0 311 function visits_in_database(aURI)
michael@0 312 {
michael@0 313 let url = aURI instanceof Ci.nsIURI ? aURI.spec : aURI;
michael@0 314 let stmt = DBConn().createStatement(
michael@0 315 "SELECT count(*) FROM moz_historyvisits v "
michael@0 316 + "JOIN moz_places h ON h.id = v.place_id "
michael@0 317 + "WHERE url = :url"
michael@0 318 );
michael@0 319 stmt.params.url = url;
michael@0 320 try {
michael@0 321 if (!stmt.executeStep())
michael@0 322 return 0;
michael@0 323 return stmt.getInt64(0);
michael@0 324 }
michael@0 325 finally {
michael@0 326 stmt.finalize();
michael@0 327 }
michael@0 328 }
michael@0 329
michael@0 330 /**
michael@0 331 * Removes all bookmarks and checks for correct cleanup
michael@0 332 */
michael@0 333 function remove_all_bookmarks() {
michael@0 334 let PU = PlacesUtils;
michael@0 335 // Clear all bookmarks
michael@0 336 PU.bookmarks.removeFolderChildren(PU.bookmarks.bookmarksMenuFolder);
michael@0 337 PU.bookmarks.removeFolderChildren(PU.bookmarks.toolbarFolder);
michael@0 338 PU.bookmarks.removeFolderChildren(PU.bookmarks.unfiledBookmarksFolder);
michael@0 339 // Check for correct cleanup
michael@0 340 check_no_bookmarks();
michael@0 341 }
michael@0 342
michael@0 343
michael@0 344 /**
michael@0 345 * Checks that we don't have any bookmark
michael@0 346 */
michael@0 347 function check_no_bookmarks() {
michael@0 348 let query = PlacesUtils.history.getNewQuery();
michael@0 349 let folders = [
michael@0 350 PlacesUtils.bookmarks.toolbarFolder,
michael@0 351 PlacesUtils.bookmarks.bookmarksMenuFolder,
michael@0 352 PlacesUtils.bookmarks.unfiledBookmarksFolder,
michael@0 353 ];
michael@0 354 query.setFolders(folders, 3);
michael@0 355 let options = PlacesUtils.history.getNewQueryOptions();
michael@0 356 options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS;
michael@0 357 let root = PlacesUtils.history.executeQuery(query, options).root;
michael@0 358 root.containerOpen = true;
michael@0 359 if (root.childCount != 0)
michael@0 360 do_throw("Unable to remove all bookmarks");
michael@0 361 root.containerOpen = false;
michael@0 362 }
michael@0 363
michael@0 364 /**
michael@0 365 * Allows waiting for an observer notification once.
michael@0 366 *
michael@0 367 * @param aTopic
michael@0 368 * Notification topic to observe.
michael@0 369 *
michael@0 370 * @return {Promise}
michael@0 371 * @resolves The array [aSubject, aData] from the observed notification.
michael@0 372 * @rejects Never.
michael@0 373 */
michael@0 374 function promiseTopicObserved(aTopic)
michael@0 375 {
michael@0 376 let deferred = Promise.defer();
michael@0 377
michael@0 378 Services.obs.addObserver(
michael@0 379 function PTO_observe(aSubject, aTopic, aData) {
michael@0 380 Services.obs.removeObserver(PTO_observe, aTopic);
michael@0 381 deferred.resolve([aSubject, aData]);
michael@0 382 }, aTopic, false);
michael@0 383
michael@0 384 return deferred.promise;
michael@0 385 }
michael@0 386
michael@0 387 /**
michael@0 388 * Clears history asynchronously.
michael@0 389 *
michael@0 390 * @return {Promise}
michael@0 391 * @resolves When history has been cleared.
michael@0 392 * @rejects Never.
michael@0 393 */
michael@0 394 function promiseClearHistory() {
michael@0 395 let promise = promiseTopicObserved(PlacesUtils.TOPIC_EXPIRATION_FINISHED);
michael@0 396 do_execute_soon(function() PlacesUtils.bhistory.removeAllPages());
michael@0 397 return promise;
michael@0 398 }
michael@0 399
michael@0 400
michael@0 401 /**
michael@0 402 * Simulates a Places shutdown.
michael@0 403 */
michael@0 404 function shutdownPlaces(aKeepAliveConnection)
michael@0 405 {
michael@0 406 let hs = PlacesUtils.history.QueryInterface(Ci.nsIObserver);
michael@0 407 hs.observe(null, "profile-change-teardown", null);
michael@0 408 hs.observe(null, "profile-before-change", null);
michael@0 409 }
michael@0 410
michael@0 411 const FILENAME_BOOKMARKS_HTML = "bookmarks.html";
michael@0 412 let (backup_date = new Date().toLocaleFormat("%Y-%m-%d")) {
michael@0 413 const FILENAME_BOOKMARKS_JSON = "bookmarks-" + backup_date + ".json";
michael@0 414 }
michael@0 415
michael@0 416 /**
michael@0 417 * Creates a bookmarks.html file in the profile folder from a given source file.
michael@0 418 *
michael@0 419 * @param aFilename
michael@0 420 * Name of the file to copy to the profile folder. This file must
michael@0 421 * exist in the directory that contains the test files.
michael@0 422 *
michael@0 423 * @return nsIFile object for the file.
michael@0 424 */
michael@0 425 function create_bookmarks_html(aFilename) {
michael@0 426 if (!aFilename)
michael@0 427 do_throw("you must pass a filename to create_bookmarks_html function");
michael@0 428 remove_bookmarks_html();
michael@0 429 let bookmarksHTMLFile = gTestDir.clone();
michael@0 430 bookmarksHTMLFile.append(aFilename);
michael@0 431 do_check_true(bookmarksHTMLFile.exists());
michael@0 432 bookmarksHTMLFile.copyTo(gProfD, FILENAME_BOOKMARKS_HTML);
michael@0 433 let profileBookmarksHTMLFile = gProfD.clone();
michael@0 434 profileBookmarksHTMLFile.append(FILENAME_BOOKMARKS_HTML);
michael@0 435 do_check_true(profileBookmarksHTMLFile.exists());
michael@0 436 return profileBookmarksHTMLFile;
michael@0 437 }
michael@0 438
michael@0 439
michael@0 440 /**
michael@0 441 * Remove bookmarks.html file from the profile folder.
michael@0 442 */
michael@0 443 function remove_bookmarks_html() {
michael@0 444 let profileBookmarksHTMLFile = gProfD.clone();
michael@0 445 profileBookmarksHTMLFile.append(FILENAME_BOOKMARKS_HTML);
michael@0 446 if (profileBookmarksHTMLFile.exists()) {
michael@0 447 profileBookmarksHTMLFile.remove(false);
michael@0 448 do_check_false(profileBookmarksHTMLFile.exists());
michael@0 449 }
michael@0 450 }
michael@0 451
michael@0 452
michael@0 453 /**
michael@0 454 * Check bookmarks.html file exists in the profile folder.
michael@0 455 *
michael@0 456 * @return nsIFile object for the file.
michael@0 457 */
michael@0 458 function check_bookmarks_html() {
michael@0 459 let profileBookmarksHTMLFile = gProfD.clone();
michael@0 460 profileBookmarksHTMLFile.append(FILENAME_BOOKMARKS_HTML);
michael@0 461 do_check_true(profileBookmarksHTMLFile.exists());
michael@0 462 return profileBookmarksHTMLFile;
michael@0 463 }
michael@0 464
michael@0 465
michael@0 466 /**
michael@0 467 * Creates a JSON backup in the profile folder folder from a given source file.
michael@0 468 *
michael@0 469 * @param aFilename
michael@0 470 * Name of the file to copy to the profile folder. This file must
michael@0 471 * exist in the directory that contains the test files.
michael@0 472 *
michael@0 473 * @return nsIFile object for the file.
michael@0 474 */
michael@0 475 function create_JSON_backup(aFilename) {
michael@0 476 if (!aFilename)
michael@0 477 do_throw("you must pass a filename to create_JSON_backup function");
michael@0 478 let bookmarksBackupDir = gProfD.clone();
michael@0 479 bookmarksBackupDir.append("bookmarkbackups");
michael@0 480 if (!bookmarksBackupDir.exists()) {
michael@0 481 bookmarksBackupDir.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0755", 8));
michael@0 482 do_check_true(bookmarksBackupDir.exists());
michael@0 483 }
michael@0 484 let profileBookmarksJSONFile = bookmarksBackupDir.clone();
michael@0 485 profileBookmarksJSONFile.append(FILENAME_BOOKMARKS_JSON);
michael@0 486 if (profileBookmarksJSONFile.exists()) {
michael@0 487 profileBookmarksJSONFile.remove();
michael@0 488 }
michael@0 489 let bookmarksJSONFile = gTestDir.clone();
michael@0 490 bookmarksJSONFile.append(aFilename);
michael@0 491 do_check_true(bookmarksJSONFile.exists());
michael@0 492 bookmarksJSONFile.copyTo(bookmarksBackupDir, FILENAME_BOOKMARKS_JSON);
michael@0 493 profileBookmarksJSONFile = bookmarksBackupDir.clone();
michael@0 494 profileBookmarksJSONFile.append(FILENAME_BOOKMARKS_JSON);
michael@0 495 do_check_true(profileBookmarksJSONFile.exists());
michael@0 496 return profileBookmarksJSONFile;
michael@0 497 }
michael@0 498
michael@0 499
michael@0 500 /**
michael@0 501 * Remove bookmarksbackup dir and all backups from the profile folder.
michael@0 502 */
michael@0 503 function remove_all_JSON_backups() {
michael@0 504 let bookmarksBackupDir = gProfD.clone();
michael@0 505 bookmarksBackupDir.append("bookmarkbackups");
michael@0 506 if (bookmarksBackupDir.exists()) {
michael@0 507 bookmarksBackupDir.remove(true);
michael@0 508 do_check_false(bookmarksBackupDir.exists());
michael@0 509 }
michael@0 510 }
michael@0 511
michael@0 512
michael@0 513 /**
michael@0 514 * Check a JSON backup file for today exists in the profile folder.
michael@0 515 *
michael@0 516 * @param aIsAutomaticBackup The boolean indicates whether it's an automatic
michael@0 517 * backup.
michael@0 518 * @return nsIFile object for the file.
michael@0 519 */
michael@0 520 function check_JSON_backup(aIsAutomaticBackup) {
michael@0 521 let profileBookmarksJSONFile;
michael@0 522 if (aIsAutomaticBackup) {
michael@0 523 let bookmarksBackupDir = gProfD.clone();
michael@0 524 bookmarksBackupDir.append("bookmarkbackups");
michael@0 525 let files = bookmarksBackupDir.directoryEntries;
michael@0 526 let backup_date = new Date().toLocaleFormat("%Y-%m-%d");
michael@0 527 while (files.hasMoreElements()) {
michael@0 528 let entry = files.getNext().QueryInterface(Ci.nsIFile);
michael@0 529 if (PlacesBackups.filenamesRegex.test(entry.leafName)) {
michael@0 530 profileBookmarksJSONFile = entry;
michael@0 531 break;
michael@0 532 }
michael@0 533 }
michael@0 534 } else {
michael@0 535 profileBookmarksJSONFile = gProfD.clone();
michael@0 536 profileBookmarksJSONFile.append("bookmarkbackups");
michael@0 537 profileBookmarksJSONFile.append(FILENAME_BOOKMARKS_JSON);
michael@0 538 }
michael@0 539 do_check_true(profileBookmarksJSONFile.exists());
michael@0 540 return profileBookmarksJSONFile;
michael@0 541 }
michael@0 542
michael@0 543 /**
michael@0 544 * Returns the frecency of a url.
michael@0 545 *
michael@0 546 * @param aURI
michael@0 547 * The URI or spec to get frecency for.
michael@0 548 * @return the frecency value.
michael@0 549 */
michael@0 550 function frecencyForUrl(aURI)
michael@0 551 {
michael@0 552 let url = aURI instanceof Ci.nsIURI ? aURI.spec : aURI;
michael@0 553 let stmt = DBConn().createStatement(
michael@0 554 "SELECT frecency FROM moz_places WHERE url = ?1"
michael@0 555 );
michael@0 556 stmt.bindByIndex(0, url);
michael@0 557 try {
michael@0 558 if (!stmt.executeStep()) {
michael@0 559 throw new Error("No result for frecency.");
michael@0 560 }
michael@0 561 return stmt.getInt32(0);
michael@0 562 } finally {
michael@0 563 stmt.finalize();
michael@0 564 }
michael@0 565 }
michael@0 566
michael@0 567 /**
michael@0 568 * Returns the hidden status of a url.
michael@0 569 *
michael@0 570 * @param aURI
michael@0 571 * The URI or spec to get hidden for.
michael@0 572 * @return @return true if the url is hidden, false otherwise.
michael@0 573 */
michael@0 574 function isUrlHidden(aURI)
michael@0 575 {
michael@0 576 let url = aURI instanceof Ci.nsIURI ? aURI.spec : aURI;
michael@0 577 let stmt = DBConn().createStatement(
michael@0 578 "SELECT hidden FROM moz_places WHERE url = ?1"
michael@0 579 );
michael@0 580 stmt.bindByIndex(0, url);
michael@0 581 if (!stmt.executeStep())
michael@0 582 throw new Error("No result for hidden.");
michael@0 583 let hidden = stmt.getInt32(0);
michael@0 584 stmt.finalize();
michael@0 585
michael@0 586 return !!hidden;
michael@0 587 }
michael@0 588
michael@0 589 /**
michael@0 590 * Compares two times in usecs, considering eventual platform timers skews.
michael@0 591 *
michael@0 592 * @param aTimeBefore
michael@0 593 * The older time in usecs.
michael@0 594 * @param aTimeAfter
michael@0 595 * The newer time in usecs.
michael@0 596 * @return true if times are ordered, false otherwise.
michael@0 597 */
michael@0 598 function is_time_ordered(before, after) {
michael@0 599 // Windows has an estimated 16ms timers precision, since Date.now() and
michael@0 600 // PR_Now() use different code atm, the results can be unordered by this
michael@0 601 // amount of time. See bug 558745 and bug 557406.
michael@0 602 let isWindows = ("@mozilla.org/windows-registry-key;1" in Cc);
michael@0 603 // Just to be safe we consider 20ms.
michael@0 604 let skew = isWindows ? 20000000 : 0;
michael@0 605 return after - before > -skew;
michael@0 606 }
michael@0 607
michael@0 608 /**
michael@0 609 * Waits for all pending async statements on the default connection.
michael@0 610 *
michael@0 611 * @return {Promise}
michael@0 612 * @resolves When all pending async statements finished.
michael@0 613 * @rejects Never.
michael@0 614 *
michael@0 615 * @note The result is achieved by asynchronously executing a query requiring
michael@0 616 * a write lock. Since all statements on the same connection are
michael@0 617 * serialized, the end of this write operation means that all writes are
michael@0 618 * complete. Note that WAL makes so that writers don't block readers, but
michael@0 619 * this is a problem only across different connections.
michael@0 620 */
michael@0 621 function promiseAsyncUpdates()
michael@0 622 {
michael@0 623 let deferred = Promise.defer();
michael@0 624
michael@0 625 let db = DBConn();
michael@0 626 let begin = db.createAsyncStatement("BEGIN EXCLUSIVE");
michael@0 627 begin.executeAsync();
michael@0 628 begin.finalize();
michael@0 629
michael@0 630 let commit = db.createAsyncStatement("COMMIT");
michael@0 631 commit.executeAsync({
michael@0 632 handleResult: function () {},
michael@0 633 handleError: function () {},
michael@0 634 handleCompletion: function(aReason)
michael@0 635 {
michael@0 636 deferred.resolve();
michael@0 637 }
michael@0 638 });
michael@0 639 commit.finalize();
michael@0 640
michael@0 641 return deferred.promise;
michael@0 642 }
michael@0 643
michael@0 644 /**
michael@0 645 * Shutdowns Places, invoking the callback when the connection has been closed.
michael@0 646 *
michael@0 647 * @param aCallback
michael@0 648 * Function to be called when done.
michael@0 649 */
michael@0 650 function waitForConnectionClosed(aCallback)
michael@0 651 {
michael@0 652 Services.obs.addObserver(function WFCCCallback() {
michael@0 653 Services.obs.removeObserver(WFCCCallback, "places-connection-closed");
michael@0 654 aCallback();
michael@0 655 }, "places-connection-closed", false);
michael@0 656 shutdownPlaces();
michael@0 657 }
michael@0 658
michael@0 659 /**
michael@0 660 * Tests if a given guid is valid for use in Places or not.
michael@0 661 *
michael@0 662 * @param aGuid
michael@0 663 * The guid to test.
michael@0 664 * @param [optional] aStack
michael@0 665 * The stack frame used to report the error.
michael@0 666 */
michael@0 667 function do_check_valid_places_guid(aGuid,
michael@0 668 aStack)
michael@0 669 {
michael@0 670 if (!aStack) {
michael@0 671 aStack = Components.stack.caller;
michael@0 672 }
michael@0 673 do_check_true(/^[a-zA-Z0-9\-_]{12}$/.test(aGuid), aStack);
michael@0 674 }
michael@0 675
michael@0 676 /**
michael@0 677 * Retrieves the guid for a given uri.
michael@0 678 *
michael@0 679 * @param aURI
michael@0 680 * The uri to check.
michael@0 681 * @param [optional] aStack
michael@0 682 * The stack frame used to report the error.
michael@0 683 * @return the associated the guid.
michael@0 684 */
michael@0 685 function do_get_guid_for_uri(aURI,
michael@0 686 aStack)
michael@0 687 {
michael@0 688 if (!aStack) {
michael@0 689 aStack = Components.stack.caller;
michael@0 690 }
michael@0 691 let stmt = DBConn().createStatement(
michael@0 692 "SELECT guid "
michael@0 693 + "FROM moz_places "
michael@0 694 + "WHERE url = :url "
michael@0 695 );
michael@0 696 stmt.params.url = aURI.spec;
michael@0 697 do_check_true(stmt.executeStep(), aStack);
michael@0 698 let guid = stmt.row.guid;
michael@0 699 stmt.finalize();
michael@0 700 do_check_valid_places_guid(guid, aStack);
michael@0 701 return guid;
michael@0 702 }
michael@0 703
michael@0 704 /**
michael@0 705 * Tests that a guid was set in moz_places for a given uri.
michael@0 706 *
michael@0 707 * @param aURI
michael@0 708 * The uri to check.
michael@0 709 * @param [optional] aGUID
michael@0 710 * The expected guid in the database.
michael@0 711 */
michael@0 712 function do_check_guid_for_uri(aURI,
michael@0 713 aGUID)
michael@0 714 {
michael@0 715 let caller = Components.stack.caller;
michael@0 716 let guid = do_get_guid_for_uri(aURI, caller);
michael@0 717 if (aGUID) {
michael@0 718 do_check_valid_places_guid(aGUID, caller);
michael@0 719 do_check_eq(guid, aGUID, caller);
michael@0 720 }
michael@0 721 }
michael@0 722
michael@0 723 /**
michael@0 724 * Retrieves the guid for a given bookmark.
michael@0 725 *
michael@0 726 * @param aId
michael@0 727 * The bookmark id to check.
michael@0 728 * @param [optional] aStack
michael@0 729 * The stack frame used to report the error.
michael@0 730 * @return the associated the guid.
michael@0 731 */
michael@0 732 function do_get_guid_for_bookmark(aId,
michael@0 733 aStack)
michael@0 734 {
michael@0 735 if (!aStack) {
michael@0 736 aStack = Components.stack.caller;
michael@0 737 }
michael@0 738 let stmt = DBConn().createStatement(
michael@0 739 "SELECT guid "
michael@0 740 + "FROM moz_bookmarks "
michael@0 741 + "WHERE id = :item_id "
michael@0 742 );
michael@0 743 stmt.params.item_id = aId;
michael@0 744 do_check_true(stmt.executeStep(), aStack);
michael@0 745 let guid = stmt.row.guid;
michael@0 746 stmt.finalize();
michael@0 747 do_check_valid_places_guid(guid, aStack);
michael@0 748 return guid;
michael@0 749 }
michael@0 750
michael@0 751 /**
michael@0 752 * Tests that a guid was set in moz_places for a given bookmark.
michael@0 753 *
michael@0 754 * @param aId
michael@0 755 * The bookmark id to check.
michael@0 756 * @param [optional] aGUID
michael@0 757 * The expected guid in the database.
michael@0 758 */
michael@0 759 function do_check_guid_for_bookmark(aId,
michael@0 760 aGUID)
michael@0 761 {
michael@0 762 let caller = Components.stack.caller;
michael@0 763 let guid = do_get_guid_for_bookmark(aId, caller);
michael@0 764 if (aGUID) {
michael@0 765 do_check_valid_places_guid(aGUID, caller);
michael@0 766 do_check_eq(guid, aGUID, caller);
michael@0 767 }
michael@0 768 }
michael@0 769
michael@0 770 /**
michael@0 771 * Logs info to the console in the standard way (includes the filename).
michael@0 772 *
michael@0 773 * @param aMessage
michael@0 774 * The message to log to the console.
michael@0 775 */
michael@0 776 function do_log_info(aMessage)
michael@0 777 {
michael@0 778 print("TEST-INFO | " + _TEST_FILE + " | " + aMessage);
michael@0 779 }
michael@0 780
michael@0 781 /**
michael@0 782 * Compares 2 arrays returning whether they contains the same elements.
michael@0 783 *
michael@0 784 * @param a1
michael@0 785 * First array to compare.
michael@0 786 * @param a2
michael@0 787 * Second array to compare.
michael@0 788 * @param [optional] sorted
michael@0 789 * Whether the comparison should take in count position of the elements.
michael@0 790 * @return true if the arrays contain the same elements, false otherwise.
michael@0 791 */
michael@0 792 function do_compare_arrays(a1, a2, sorted)
michael@0 793 {
michael@0 794 if (a1.length != a2.length)
michael@0 795 return false;
michael@0 796
michael@0 797 if (sorted) {
michael@0 798 return a1.every(function (e, i) e == a2[i]);
michael@0 799 }
michael@0 800 else {
michael@0 801 return a1.filter(function (e) a2.indexOf(e) == -1).length == 0 &&
michael@0 802 a2.filter(function (e) a1.indexOf(e) == -1).length == 0;
michael@0 803 }
michael@0 804 }
michael@0 805
michael@0 806 /**
michael@0 807 * Generic nsINavBookmarkObserver that doesn't implement anything, but provides
michael@0 808 * dummy methods to prevent errors about an object not having a certain method.
michael@0 809 */
michael@0 810 function NavBookmarkObserver() {}
michael@0 811
michael@0 812 NavBookmarkObserver.prototype = {
michael@0 813 onBeginUpdateBatch: function () {},
michael@0 814 onEndUpdateBatch: function () {},
michael@0 815 onItemAdded: function () {},
michael@0 816 onItemRemoved: function () {},
michael@0 817 onItemChanged: function () {},
michael@0 818 onItemVisited: function () {},
michael@0 819 onItemMoved: function () {},
michael@0 820 QueryInterface: XPCOMUtils.generateQI([
michael@0 821 Ci.nsINavBookmarkObserver,
michael@0 822 ])
michael@0 823 };
michael@0 824
michael@0 825 /**
michael@0 826 * Generic nsINavHistoryObserver that doesn't implement anything, but provides
michael@0 827 * dummy methods to prevent errors about an object not having a certain method.
michael@0 828 */
michael@0 829 function NavHistoryObserver() {}
michael@0 830
michael@0 831 NavHistoryObserver.prototype = {
michael@0 832 onBeginUpdateBatch: function () {},
michael@0 833 onEndUpdateBatch: function () {},
michael@0 834 onVisit: function () {},
michael@0 835 onTitleChanged: function () {},
michael@0 836 onDeleteURI: function () {},
michael@0 837 onClearHistory: function () {},
michael@0 838 onPageChanged: function () {},
michael@0 839 onDeleteVisits: function () {},
michael@0 840 QueryInterface: XPCOMUtils.generateQI([
michael@0 841 Ci.nsINavHistoryObserver,
michael@0 842 ])
michael@0 843 };
michael@0 844
michael@0 845 /**
michael@0 846 * Generic nsINavHistoryResultObserver that doesn't implement anything, but
michael@0 847 * provides dummy methods to prevent errors about an object not having a certain
michael@0 848 * method.
michael@0 849 */
michael@0 850 function NavHistoryResultObserver() {}
michael@0 851
michael@0 852 NavHistoryResultObserver.prototype = {
michael@0 853 batching: function () {},
michael@0 854 containerStateChanged: function () {},
michael@0 855 invalidateContainer: function () {},
michael@0 856 nodeAnnotationChanged: function () {},
michael@0 857 nodeDateAddedChanged: function () {},
michael@0 858 nodeHistoryDetailsChanged: function () {},
michael@0 859 nodeIconChanged: function () {},
michael@0 860 nodeInserted: function () {},
michael@0 861 nodeKeywordChanged: function () {},
michael@0 862 nodeLastModifiedChanged: function () {},
michael@0 863 nodeMoved: function () {},
michael@0 864 nodeRemoved: function () {},
michael@0 865 nodeTagsChanged: function () {},
michael@0 866 nodeTitleChanged: function () {},
michael@0 867 nodeURIChanged: function () {},
michael@0 868 sortingChanged: function () {},
michael@0 869 QueryInterface: XPCOMUtils.generateQI([
michael@0 870 Ci.nsINavHistoryResultObserver,
michael@0 871 ])
michael@0 872 };
michael@0 873
michael@0 874 /**
michael@0 875 * Asynchronously adds visits to a page.
michael@0 876 *
michael@0 877 * @param aPlaceInfo
michael@0 878 * Can be an nsIURI, in such a case a single LINK visit will be added.
michael@0 879 * Otherwise can be an object describing the visit to add, or an array
michael@0 880 * of these objects:
michael@0 881 * { uri: nsIURI of the page,
michael@0 882 * transition: one of the TRANSITION_* from nsINavHistoryService,
michael@0 883 * [optional] title: title of the page,
michael@0 884 * [optional] visitDate: visit date in microseconds from the epoch
michael@0 885 * [optional] referrer: nsIURI of the referrer for this visit
michael@0 886 * }
michael@0 887 *
michael@0 888 * @return {Promise}
michael@0 889 * @resolves When all visits have been added successfully.
michael@0 890 * @rejects JavaScript exception.
michael@0 891 */
michael@0 892 function promiseAddVisits(aPlaceInfo)
michael@0 893 {
michael@0 894 let deferred = Promise.defer();
michael@0 895 let places = [];
michael@0 896 if (aPlaceInfo instanceof Ci.nsIURI) {
michael@0 897 places.push({ uri: aPlaceInfo });
michael@0 898 }
michael@0 899 else if (Array.isArray(aPlaceInfo)) {
michael@0 900 places = places.concat(aPlaceInfo);
michael@0 901 } else {
michael@0 902 places.push(aPlaceInfo)
michael@0 903 }
michael@0 904
michael@0 905 // Create mozIVisitInfo for each entry.
michael@0 906 let now = Date.now();
michael@0 907 for (let i = 0; i < places.length; i++) {
michael@0 908 if (!places[i].title) {
michael@0 909 places[i].title = "test visit for " + places[i].uri.spec;
michael@0 910 }
michael@0 911 places[i].visits = [{
michael@0 912 transitionType: places[i].transition === undefined ? TRANSITION_LINK
michael@0 913 : places[i].transition,
michael@0 914 visitDate: places[i].visitDate || (now++) * 1000,
michael@0 915 referrerURI: places[i].referrer
michael@0 916 }];
michael@0 917 }
michael@0 918
michael@0 919 PlacesUtils.asyncHistory.updatePlaces(
michael@0 920 places,
michael@0 921 {
michael@0 922 handleError: function AAV_handleError(aResultCode, aPlaceInfo) {
michael@0 923 let ex = new Components.Exception("Unexpected error in adding visits.",
michael@0 924 aResultCode);
michael@0 925 deferred.reject(ex);
michael@0 926 },
michael@0 927 handleResult: function () {},
michael@0 928 handleCompletion: function UP_handleCompletion() {
michael@0 929 deferred.resolve();
michael@0 930 }
michael@0 931 }
michael@0 932 );
michael@0 933
michael@0 934 return deferred.promise;
michael@0 935 }
michael@0 936
michael@0 937 /**
michael@0 938 * Asynchronously check a url is visited.
michael@0 939 *
michael@0 940 * @param aURI The URI.
michael@0 941 * @return {Promise}
michael@0 942 * @resolves When the check has been added successfully.
michael@0 943 * @rejects JavaScript exception.
michael@0 944 */
michael@0 945 function promiseIsURIVisited(aURI) {
michael@0 946 let deferred = Promise.defer();
michael@0 947
michael@0 948 PlacesUtils.asyncHistory.isURIVisited(aURI, function(aURI, aIsVisited) {
michael@0 949 deferred.resolve(aIsVisited);
michael@0 950 });
michael@0 951
michael@0 952 return deferred.promise;
michael@0 953 }
michael@0 954

mercurial