toolkit/components/places/tests/head_common.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/toolkit/components/places/tests/head_common.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,954 @@
     1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
     1.5 + * This Source Code Form is subject to the terms of the Mozilla Public
     1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.8 +
     1.9 +const CURRENT_SCHEMA_VERSION = 23;
    1.10 +
    1.11 +const NS_APP_USER_PROFILE_50_DIR = "ProfD";
    1.12 +const NS_APP_PROFILE_DIR_STARTUP = "ProfDS";
    1.13 +
    1.14 +// Shortcuts to transitions type.
    1.15 +const TRANSITION_LINK = Ci.nsINavHistoryService.TRANSITION_LINK;
    1.16 +const TRANSITION_TYPED = Ci.nsINavHistoryService.TRANSITION_TYPED;
    1.17 +const TRANSITION_BOOKMARK = Ci.nsINavHistoryService.TRANSITION_BOOKMARK;
    1.18 +const TRANSITION_EMBED = Ci.nsINavHistoryService.TRANSITION_EMBED;
    1.19 +const TRANSITION_FRAMED_LINK = Ci.nsINavHistoryService.TRANSITION_FRAMED_LINK;
    1.20 +const TRANSITION_REDIRECT_PERMANENT = Ci.nsINavHistoryService.TRANSITION_REDIRECT_PERMANENT;
    1.21 +const TRANSITION_REDIRECT_TEMPORARY = Ci.nsINavHistoryService.TRANSITION_REDIRECT_TEMPORARY;
    1.22 +const TRANSITION_DOWNLOAD = Ci.nsINavHistoryService.TRANSITION_DOWNLOAD;
    1.23 +
    1.24 +const TITLE_LENGTH_MAX = 4096;
    1.25 +
    1.26 +Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    1.27 +
    1.28 +XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
    1.29 +                                  "resource://gre/modules/FileUtils.jsm");
    1.30 +XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
    1.31 +                                  "resource://gre/modules/NetUtil.jsm");
    1.32 +XPCOMUtils.defineLazyModuleGetter(this, "Promise",
    1.33 +                                  "resource://gre/modules/Promise.jsm");
    1.34 +XPCOMUtils.defineLazyModuleGetter(this, "Services",
    1.35 +                                  "resource://gre/modules/Services.jsm");
    1.36 +XPCOMUtils.defineLazyModuleGetter(this, "Task",
    1.37 +                                  "resource://gre/modules/Task.jsm");
    1.38 +XPCOMUtils.defineLazyModuleGetter(this, "BookmarkJSONUtils",
    1.39 +                                  "resource://gre/modules/BookmarkJSONUtils.jsm");
    1.40 +XPCOMUtils.defineLazyModuleGetter(this, "BookmarkHTMLUtils",
    1.41 +                                  "resource://gre/modules/BookmarkHTMLUtils.jsm");
    1.42 +XPCOMUtils.defineLazyModuleGetter(this, "PlacesBackups",
    1.43 +                                  "resource://gre/modules/PlacesBackups.jsm");
    1.44 +XPCOMUtils.defineLazyModuleGetter(this, "PlacesTransactions",
    1.45 +                                  "resource://gre/modules/PlacesTransactions.jsm");
    1.46 +XPCOMUtils.defineLazyModuleGetter(this, "OS",
    1.47 +                                  "resource://gre/modules/osfile.jsm");
    1.48 +
    1.49 +// This imports various other objects in addition to PlacesUtils.
    1.50 +Cu.import("resource://gre/modules/PlacesUtils.jsm");
    1.51 +
    1.52 +XPCOMUtils.defineLazyGetter(this, "SMALLPNG_DATA_URI", function() {
    1.53 +  return NetUtil.newURI(
    1.54 +         "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAA" +
    1.55 +         "AAAA6fptVAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg==");
    1.56 +});
    1.57 +
    1.58 +function LOG(aMsg) {
    1.59 +  aMsg = ("*** PLACES TESTS: " + aMsg);
    1.60 +  Services.console.logStringMessage(aMsg);
    1.61 +  print(aMsg);
    1.62 +}
    1.63 +
    1.64 +let gTestDir = do_get_cwd();
    1.65 +
    1.66 +// Initialize profile.
    1.67 +let gProfD = do_get_profile();
    1.68 +
    1.69 +// Remove any old database.
    1.70 +clearDB();
    1.71 +
    1.72 +/**
    1.73 + * Shortcut to create a nsIURI.
    1.74 + *
    1.75 + * @param aSpec
    1.76 + *        URLString of the uri.
    1.77 + */
    1.78 +function uri(aSpec) NetUtil.newURI(aSpec);
    1.79 +
    1.80 +
    1.81 +/**
    1.82 + * Gets the database connection.  If the Places connection is invalid it will
    1.83 + * try to create a new connection.
    1.84 + *
    1.85 + * @param [optional] aForceNewConnection
    1.86 + *        Forces creation of a new connection to the database.  When a
    1.87 + *        connection is asyncClosed it cannot anymore schedule async statements,
    1.88 + *        though connectionReady will keep returning true (Bug 726990).
    1.89 + *
    1.90 + * @return The database connection or null if unable to get one.
    1.91 + */
    1.92 +let gDBConn;
    1.93 +function DBConn(aForceNewConnection) {
    1.94 +  if (!aForceNewConnection) {
    1.95 +    let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
    1.96 +                                .DBConnection;
    1.97 +    if (db.connectionReady)
    1.98 +      return db;
    1.99 +  }
   1.100 +
   1.101 +  // If the Places database connection has been closed, create a new connection.
   1.102 +  if (!gDBConn || aForceNewConnection) {
   1.103 +    let file = Services.dirsvc.get('ProfD', Ci.nsIFile);
   1.104 +    file.append("places.sqlite");
   1.105 +    let dbConn = gDBConn = Services.storage.openDatabase(file);
   1.106 +
   1.107 +    // Be sure to cleanly close this connection.
   1.108 +    Services.obs.addObserver(function DBCloseCallback(aSubject, aTopic, aData) {
   1.109 +      Services.obs.removeObserver(DBCloseCallback, aTopic);
   1.110 +      dbConn.asyncClose();
   1.111 +    }, "profile-before-change", false);
   1.112 +  }
   1.113 +
   1.114 +  return gDBConn.connectionReady ? gDBConn : null;
   1.115 +};
   1.116 +
   1.117 +/**
   1.118 + * Reads data from the provided inputstream.
   1.119 + *
   1.120 + * @return an array of bytes.
   1.121 + */ 
   1.122 +function readInputStreamData(aStream) {
   1.123 +  let bistream = Cc["@mozilla.org/binaryinputstream;1"].
   1.124 +                 createInstance(Ci.nsIBinaryInputStream);
   1.125 +  try {
   1.126 +    bistream.setInputStream(aStream);
   1.127 +    let expectedData = [];
   1.128 +    let avail;
   1.129 +    while ((avail = bistream.available())) {
   1.130 +      expectedData = expectedData.concat(bistream.readByteArray(avail));
   1.131 +    }
   1.132 +    return expectedData;
   1.133 +  } finally {
   1.134 +    bistream.close();
   1.135 +  }
   1.136 +}
   1.137 +
   1.138 +/**
   1.139 + * Reads the data from the specified nsIFile.
   1.140 + *
   1.141 + * @param aFile
   1.142 + *        The nsIFile to read from.
   1.143 + * @return an array of bytes.
   1.144 + */
   1.145 +function readFileData(aFile) {
   1.146 +  let inputStream = Cc["@mozilla.org/network/file-input-stream;1"].
   1.147 +                    createInstance(Ci.nsIFileInputStream);
   1.148 +  // init the stream as RD_ONLY, -1 == default permissions.
   1.149 +  inputStream.init(aFile, 0x01, -1, null);
   1.150 +
   1.151 +  // Check the returned size versus the expected size.
   1.152 +  let size  = inputStream.available();
   1.153 +  let bytes = readInputStreamData(inputStream);
   1.154 +  if (size != bytes.length) {
   1.155 +    throw "Didn't read expected number of bytes";
   1.156 +  }
   1.157 +  return bytes;
   1.158 +}
   1.159 +
   1.160 +/**
   1.161 + * Reads the data from the named file, verifying the expected file length.
   1.162 + *
   1.163 + * @param aFileName
   1.164 + *        This file should be located in the same folder as the test.
   1.165 + * @param aExpectedLength
   1.166 + *        Expected length of the file.
   1.167 + *
   1.168 + * @return The array of bytes read from the file.
   1.169 + */
   1.170 +function readFileOfLength(aFileName, aExpectedLength) {
   1.171 +  let data = readFileData(do_get_file(aFileName));
   1.172 +  do_check_eq(data.length, aExpectedLength);
   1.173 +  return data;
   1.174 +}
   1.175 +
   1.176 +
   1.177 +/**
   1.178 + * Returns the base64-encoded version of the given string.  This function is
   1.179 + * similar to window.btoa, but is available to xpcshell tests also.
   1.180 + *
   1.181 + * @param aString
   1.182 + *        Each character in this string corresponds to a byte, and must be a
   1.183 + *        code point in the range 0-255.
   1.184 + *
   1.185 + * @return The base64-encoded string.
   1.186 + */
   1.187 +function base64EncodeString(aString) {
   1.188 +  var stream = Cc["@mozilla.org/io/string-input-stream;1"]
   1.189 +               .createInstance(Ci.nsIStringInputStream);
   1.190 +  stream.setData(aString, aString.length);
   1.191 +  var encoder = Cc["@mozilla.org/scriptablebase64encoder;1"]
   1.192 +                .createInstance(Ci.nsIScriptableBase64Encoder);
   1.193 +  return encoder.encodeToString(stream, aString.length);
   1.194 +}
   1.195 +
   1.196 +
   1.197 +/**
   1.198 + * Compares two arrays, and returns true if they are equal.
   1.199 + *
   1.200 + * @param aArray1
   1.201 + *        First array to compare.
   1.202 + * @param aArray2
   1.203 + *        Second array to compare.
   1.204 + */
   1.205 +function compareArrays(aArray1, aArray2) {
   1.206 +  if (aArray1.length != aArray2.length) {
   1.207 +    print("compareArrays: array lengths differ\n");
   1.208 +    return false;
   1.209 +  }
   1.210 +
   1.211 +  for (let i = 0; i < aArray1.length; i++) {
   1.212 +    if (aArray1[i] != aArray2[i]) {
   1.213 +      print("compareArrays: arrays differ at index " + i + ": " +
   1.214 +            "(" + aArray1[i] + ") != (" + aArray2[i] +")\n");
   1.215 +      return false;
   1.216 +    }
   1.217 +  }
   1.218 +
   1.219 +  return true;
   1.220 +}
   1.221 +
   1.222 +
   1.223 +/**
   1.224 + * Deletes a previously created sqlite file from the profile folder.
   1.225 + */
   1.226 +function clearDB() {
   1.227 +  try {
   1.228 +    let file = Services.dirsvc.get('ProfD', Ci.nsIFile);
   1.229 +    file.append("places.sqlite");
   1.230 +    if (file.exists())
   1.231 +      file.remove(false);
   1.232 +  } catch(ex) { dump("Exception: " + ex); }
   1.233 +}
   1.234 +
   1.235 +
   1.236 +/**
   1.237 + * Dumps the rows of a table out to the console.
   1.238 + *
   1.239 + * @param aName
   1.240 + *        The name of the table or view to output.
   1.241 + */
   1.242 +function dump_table(aName)
   1.243 +{
   1.244 +  let stmt = DBConn().createStatement("SELECT * FROM " + aName);
   1.245 +
   1.246 +  print("\n*** Printing data from " + aName);
   1.247 +  let count = 0;
   1.248 +  while (stmt.executeStep()) {
   1.249 +    let columns = stmt.numEntries;
   1.250 +
   1.251 +    if (count == 0) {
   1.252 +      // Print the column names.
   1.253 +      for (let i = 0; i < columns; i++)
   1.254 +        dump(stmt.getColumnName(i) + "\t");
   1.255 +      dump("\n");
   1.256 +    }
   1.257 +
   1.258 +    // Print the rows.
   1.259 +    for (let i = 0; i < columns; i++) {
   1.260 +      switch (stmt.getTypeOfIndex(i)) {
   1.261 +        case Ci.mozIStorageValueArray.VALUE_TYPE_NULL:
   1.262 +          dump("NULL\t");
   1.263 +          break;
   1.264 +        case Ci.mozIStorageValueArray.VALUE_TYPE_INTEGER:
   1.265 +          dump(stmt.getInt64(i) + "\t");
   1.266 +          break;
   1.267 +        case Ci.mozIStorageValueArray.VALUE_TYPE_FLOAT:
   1.268 +          dump(stmt.getDouble(i) + "\t");
   1.269 +          break;
   1.270 +        case Ci.mozIStorageValueArray.VALUE_TYPE_TEXT:
   1.271 +          dump(stmt.getString(i) + "\t");
   1.272 +          break;
   1.273 +      }
   1.274 +    }
   1.275 +    dump("\n");
   1.276 +
   1.277 +    count++;
   1.278 +  }
   1.279 +  print("*** There were a total of " + count + " rows of data.\n");
   1.280 +
   1.281 +  stmt.finalize();
   1.282 +}
   1.283 +
   1.284 +
   1.285 +/**
   1.286 + * Checks if an address is found in the database.
   1.287 + * @param aURI
   1.288 + *        nsIURI or address to look for.
   1.289 + * @return place id of the page or 0 if not found
   1.290 + */
   1.291 +function page_in_database(aURI)
   1.292 +{
   1.293 +  let url = aURI instanceof Ci.nsIURI ? aURI.spec : aURI;
   1.294 +  let stmt = DBConn().createStatement(
   1.295 +    "SELECT id FROM moz_places WHERE url = :url"
   1.296 +  );
   1.297 +  stmt.params.url = url;
   1.298 +  try {
   1.299 +    if (!stmt.executeStep())
   1.300 +      return 0;
   1.301 +    return stmt.getInt64(0);
   1.302 +  }
   1.303 +  finally {
   1.304 +    stmt.finalize();
   1.305 +  }
   1.306 +}
   1.307 +
   1.308 +/**
   1.309 + * Checks how many visits exist for a specified page.
   1.310 + * @param aURI
   1.311 + *        nsIURI or address to look for.
   1.312 + * @return number of visits found.
   1.313 + */
   1.314 +function visits_in_database(aURI)
   1.315 +{
   1.316 +  let url = aURI instanceof Ci.nsIURI ? aURI.spec : aURI;
   1.317 +  let stmt = DBConn().createStatement(
   1.318 +    "SELECT count(*) FROM moz_historyvisits v "
   1.319 +  + "JOIN moz_places h ON h.id = v.place_id "
   1.320 +  + "WHERE url = :url"
   1.321 +  );
   1.322 +  stmt.params.url = url;
   1.323 +  try {
   1.324 +    if (!stmt.executeStep())
   1.325 +      return 0;
   1.326 +    return stmt.getInt64(0);
   1.327 +  }
   1.328 +  finally {
   1.329 +    stmt.finalize();
   1.330 +  }
   1.331 +}
   1.332 +
   1.333 +/**
   1.334 + * Removes all bookmarks and checks for correct cleanup
   1.335 + */
   1.336 +function remove_all_bookmarks() {
   1.337 +  let PU = PlacesUtils;
   1.338 +  // Clear all bookmarks
   1.339 +  PU.bookmarks.removeFolderChildren(PU.bookmarks.bookmarksMenuFolder);
   1.340 +  PU.bookmarks.removeFolderChildren(PU.bookmarks.toolbarFolder);
   1.341 +  PU.bookmarks.removeFolderChildren(PU.bookmarks.unfiledBookmarksFolder);
   1.342 +  // Check for correct cleanup
   1.343 +  check_no_bookmarks();
   1.344 +}
   1.345 +
   1.346 +
   1.347 +/**
   1.348 + * Checks that we don't have any bookmark
   1.349 + */
   1.350 +function check_no_bookmarks() {
   1.351 +  let query = PlacesUtils.history.getNewQuery();
   1.352 +  let folders = [
   1.353 +    PlacesUtils.bookmarks.toolbarFolder,
   1.354 +    PlacesUtils.bookmarks.bookmarksMenuFolder,
   1.355 +    PlacesUtils.bookmarks.unfiledBookmarksFolder,
   1.356 +  ];
   1.357 +  query.setFolders(folders, 3);
   1.358 +  let options = PlacesUtils.history.getNewQueryOptions();
   1.359 +  options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS;
   1.360 +  let root = PlacesUtils.history.executeQuery(query, options).root;
   1.361 +  root.containerOpen = true;
   1.362 +  if (root.childCount != 0)
   1.363 +    do_throw("Unable to remove all bookmarks");
   1.364 +  root.containerOpen = false;
   1.365 +}
   1.366 +
   1.367 +/**
   1.368 + * Allows waiting for an observer notification once.
   1.369 + *
   1.370 + * @param aTopic
   1.371 + *        Notification topic to observe.
   1.372 + *
   1.373 + * @return {Promise}
   1.374 + * @resolves The array [aSubject, aData] from the observed notification.
   1.375 + * @rejects Never.
   1.376 + */
   1.377 +function promiseTopicObserved(aTopic)
   1.378 +{
   1.379 +  let deferred = Promise.defer();
   1.380 +
   1.381 +  Services.obs.addObserver(
   1.382 +    function PTO_observe(aSubject, aTopic, aData) {
   1.383 +      Services.obs.removeObserver(PTO_observe, aTopic);
   1.384 +      deferred.resolve([aSubject, aData]);
   1.385 +    }, aTopic, false);
   1.386 +
   1.387 +  return deferred.promise;
   1.388 +}
   1.389 +
   1.390 +/**
   1.391 + * Clears history asynchronously.
   1.392 + *
   1.393 + * @return {Promise}
   1.394 + * @resolves When history has been cleared.
   1.395 + * @rejects Never.
   1.396 + */
   1.397 +function promiseClearHistory() {
   1.398 +  let promise = promiseTopicObserved(PlacesUtils.TOPIC_EXPIRATION_FINISHED);
   1.399 +  do_execute_soon(function() PlacesUtils.bhistory.removeAllPages());
   1.400 +  return promise;
   1.401 +}
   1.402 +
   1.403 +
   1.404 +/**
   1.405 + * Simulates a Places shutdown.
   1.406 + */
   1.407 +function shutdownPlaces(aKeepAliveConnection)
   1.408 +{
   1.409 +  let hs = PlacesUtils.history.QueryInterface(Ci.nsIObserver);
   1.410 +  hs.observe(null, "profile-change-teardown", null);
   1.411 +  hs.observe(null, "profile-before-change", null);
   1.412 +}
   1.413 +
   1.414 +const FILENAME_BOOKMARKS_HTML = "bookmarks.html";
   1.415 +let (backup_date = new Date().toLocaleFormat("%Y-%m-%d")) {
   1.416 +  const FILENAME_BOOKMARKS_JSON = "bookmarks-" + backup_date + ".json";
   1.417 +}
   1.418 +
   1.419 +/**
   1.420 + * Creates a bookmarks.html file in the profile folder from a given source file.
   1.421 + *
   1.422 + * @param aFilename
   1.423 + *        Name of the file to copy to the profile folder.  This file must
   1.424 + *        exist in the directory that contains the test files.
   1.425 + *
   1.426 + * @return nsIFile object for the file.
   1.427 + */
   1.428 +function create_bookmarks_html(aFilename) {
   1.429 +  if (!aFilename)
   1.430 +    do_throw("you must pass a filename to create_bookmarks_html function");
   1.431 +  remove_bookmarks_html();
   1.432 +  let bookmarksHTMLFile = gTestDir.clone();
   1.433 +  bookmarksHTMLFile.append(aFilename);
   1.434 +  do_check_true(bookmarksHTMLFile.exists());
   1.435 +  bookmarksHTMLFile.copyTo(gProfD, FILENAME_BOOKMARKS_HTML);
   1.436 +  let profileBookmarksHTMLFile = gProfD.clone();
   1.437 +  profileBookmarksHTMLFile.append(FILENAME_BOOKMARKS_HTML);
   1.438 +  do_check_true(profileBookmarksHTMLFile.exists());
   1.439 +  return profileBookmarksHTMLFile;
   1.440 +}
   1.441 +
   1.442 +
   1.443 +/**
   1.444 + * Remove bookmarks.html file from the profile folder.
   1.445 + */
   1.446 +function remove_bookmarks_html() {
   1.447 +  let profileBookmarksHTMLFile = gProfD.clone();
   1.448 +  profileBookmarksHTMLFile.append(FILENAME_BOOKMARKS_HTML);
   1.449 +  if (profileBookmarksHTMLFile.exists()) {
   1.450 +    profileBookmarksHTMLFile.remove(false);
   1.451 +    do_check_false(profileBookmarksHTMLFile.exists());
   1.452 +  }
   1.453 +}
   1.454 +
   1.455 +
   1.456 +/**
   1.457 + * Check bookmarks.html file exists in the profile folder.
   1.458 + *
   1.459 + * @return nsIFile object for the file.
   1.460 + */
   1.461 +function check_bookmarks_html() {
   1.462 +  let profileBookmarksHTMLFile = gProfD.clone();
   1.463 +  profileBookmarksHTMLFile.append(FILENAME_BOOKMARKS_HTML);
   1.464 +  do_check_true(profileBookmarksHTMLFile.exists());
   1.465 +  return profileBookmarksHTMLFile;
   1.466 +}
   1.467 +
   1.468 +
   1.469 +/**
   1.470 + * Creates a JSON backup in the profile folder folder from a given source file.
   1.471 + *
   1.472 + * @param aFilename
   1.473 + *        Name of the file to copy to the profile folder.  This file must
   1.474 + *        exist in the directory that contains the test files.
   1.475 + *
   1.476 + * @return nsIFile object for the file.
   1.477 + */
   1.478 +function create_JSON_backup(aFilename) {
   1.479 +  if (!aFilename)
   1.480 +    do_throw("you must pass a filename to create_JSON_backup function");
   1.481 +  let bookmarksBackupDir = gProfD.clone();
   1.482 +  bookmarksBackupDir.append("bookmarkbackups");
   1.483 +  if (!bookmarksBackupDir.exists()) {
   1.484 +    bookmarksBackupDir.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0755", 8));
   1.485 +    do_check_true(bookmarksBackupDir.exists());
   1.486 +  }
   1.487 +  let profileBookmarksJSONFile = bookmarksBackupDir.clone();
   1.488 +  profileBookmarksJSONFile.append(FILENAME_BOOKMARKS_JSON);
   1.489 +  if (profileBookmarksJSONFile.exists()) {
   1.490 +    profileBookmarksJSONFile.remove();
   1.491 +  }
   1.492 +  let bookmarksJSONFile = gTestDir.clone();
   1.493 +  bookmarksJSONFile.append(aFilename);
   1.494 +  do_check_true(bookmarksJSONFile.exists());
   1.495 +  bookmarksJSONFile.copyTo(bookmarksBackupDir, FILENAME_BOOKMARKS_JSON);
   1.496 +  profileBookmarksJSONFile = bookmarksBackupDir.clone();
   1.497 +  profileBookmarksJSONFile.append(FILENAME_BOOKMARKS_JSON);
   1.498 +  do_check_true(profileBookmarksJSONFile.exists());
   1.499 +  return profileBookmarksJSONFile;
   1.500 +}
   1.501 +
   1.502 +
   1.503 +/**
   1.504 + * Remove bookmarksbackup dir and all backups from the profile folder.
   1.505 + */
   1.506 +function remove_all_JSON_backups() {
   1.507 +  let bookmarksBackupDir = gProfD.clone();
   1.508 +  bookmarksBackupDir.append("bookmarkbackups");
   1.509 +  if (bookmarksBackupDir.exists()) {
   1.510 +    bookmarksBackupDir.remove(true);
   1.511 +    do_check_false(bookmarksBackupDir.exists());
   1.512 +  }
   1.513 +}
   1.514 +
   1.515 +
   1.516 +/**
   1.517 + * Check a JSON backup file for today exists in the profile folder.
   1.518 + *
   1.519 + * @param aIsAutomaticBackup The boolean indicates whether it's an automatic
   1.520 + *        backup.
   1.521 + * @return nsIFile object for the file.
   1.522 + */
   1.523 +function check_JSON_backup(aIsAutomaticBackup) {
   1.524 +  let profileBookmarksJSONFile;
   1.525 +  if (aIsAutomaticBackup) {
   1.526 +    let bookmarksBackupDir = gProfD.clone();
   1.527 +    bookmarksBackupDir.append("bookmarkbackups");
   1.528 +    let files = bookmarksBackupDir.directoryEntries;
   1.529 +    let backup_date = new Date().toLocaleFormat("%Y-%m-%d");
   1.530 +    while (files.hasMoreElements()) {
   1.531 +      let entry = files.getNext().QueryInterface(Ci.nsIFile);
   1.532 +      if (PlacesBackups.filenamesRegex.test(entry.leafName)) {
   1.533 +        profileBookmarksJSONFile = entry;
   1.534 +        break;
   1.535 +      }
   1.536 +    }
   1.537 +  } else {
   1.538 +    profileBookmarksJSONFile = gProfD.clone();
   1.539 +    profileBookmarksJSONFile.append("bookmarkbackups");
   1.540 +    profileBookmarksJSONFile.append(FILENAME_BOOKMARKS_JSON);
   1.541 +  }
   1.542 +  do_check_true(profileBookmarksJSONFile.exists());
   1.543 +  return profileBookmarksJSONFile;
   1.544 +}
   1.545 +
   1.546 +/**
   1.547 + * Returns the frecency of a url.
   1.548 + *
   1.549 + * @param aURI
   1.550 + *        The URI or spec to get frecency for.
   1.551 + * @return the frecency value.
   1.552 + */
   1.553 +function frecencyForUrl(aURI)
   1.554 +{
   1.555 +  let url = aURI instanceof Ci.nsIURI ? aURI.spec : aURI;
   1.556 +  let stmt = DBConn().createStatement(
   1.557 +    "SELECT frecency FROM moz_places WHERE url = ?1"
   1.558 +  );
   1.559 +  stmt.bindByIndex(0, url);
   1.560 +  try {
   1.561 +    if (!stmt.executeStep()) {
   1.562 +      throw new Error("No result for frecency.");
   1.563 +    }
   1.564 +    return stmt.getInt32(0);
   1.565 +  } finally {
   1.566 +    stmt.finalize();
   1.567 +  }
   1.568 +}
   1.569 +
   1.570 +/**
   1.571 + * Returns the hidden status of a url.
   1.572 + *
   1.573 + * @param aURI
   1.574 + *        The URI or spec to get hidden for.
   1.575 + * @return @return true if the url is hidden, false otherwise.
   1.576 + */
   1.577 +function isUrlHidden(aURI)
   1.578 +{
   1.579 +  let url = aURI instanceof Ci.nsIURI ? aURI.spec : aURI;
   1.580 +  let stmt = DBConn().createStatement(
   1.581 +    "SELECT hidden FROM moz_places WHERE url = ?1"
   1.582 +  );
   1.583 +  stmt.bindByIndex(0, url);
   1.584 +  if (!stmt.executeStep())
   1.585 +    throw new Error("No result for hidden.");
   1.586 +  let hidden = stmt.getInt32(0);
   1.587 +  stmt.finalize();
   1.588 +
   1.589 +  return !!hidden;
   1.590 +}
   1.591 +
   1.592 +/**
   1.593 + * Compares two times in usecs, considering eventual platform timers skews.
   1.594 + *
   1.595 + * @param aTimeBefore
   1.596 + *        The older time in usecs.
   1.597 + * @param aTimeAfter
   1.598 + *        The newer time in usecs.
   1.599 + * @return true if times are ordered, false otherwise.
   1.600 + */
   1.601 +function is_time_ordered(before, after) {
   1.602 +  // Windows has an estimated 16ms timers precision, since Date.now() and
   1.603 +  // PR_Now() use different code atm, the results can be unordered by this
   1.604 +  // amount of time.  See bug 558745 and bug 557406.
   1.605 +  let isWindows = ("@mozilla.org/windows-registry-key;1" in Cc);
   1.606 +  // Just to be safe we consider 20ms.
   1.607 +  let skew = isWindows ? 20000000 : 0;
   1.608 +  return after - before > -skew;
   1.609 +}
   1.610 +
   1.611 +/**
   1.612 + * Waits for all pending async statements on the default connection.
   1.613 + *
   1.614 + * @return {Promise}
   1.615 + * @resolves When all pending async statements finished.
   1.616 + * @rejects Never.
   1.617 + *
   1.618 + * @note The result is achieved by asynchronously executing a query requiring
   1.619 + *       a write lock.  Since all statements on the same connection are
   1.620 + *       serialized, the end of this write operation means that all writes are
   1.621 + *       complete.  Note that WAL makes so that writers don't block readers, but
   1.622 + *       this is a problem only across different connections.
   1.623 + */
   1.624 +function promiseAsyncUpdates()
   1.625 +{
   1.626 +  let deferred = Promise.defer();
   1.627 +
   1.628 +  let db = DBConn();
   1.629 +  let begin = db.createAsyncStatement("BEGIN EXCLUSIVE");
   1.630 +  begin.executeAsync();
   1.631 +  begin.finalize();
   1.632 +
   1.633 +  let commit = db.createAsyncStatement("COMMIT");
   1.634 +  commit.executeAsync({
   1.635 +    handleResult: function () {},
   1.636 +    handleError: function () {},
   1.637 +    handleCompletion: function(aReason)
   1.638 +    {
   1.639 +      deferred.resolve();
   1.640 +    }
   1.641 +  });
   1.642 +  commit.finalize();
   1.643 +
   1.644 +  return deferred.promise;
   1.645 +}
   1.646 +
   1.647 +/**
   1.648 + * Shutdowns Places, invoking the callback when the connection has been closed.
   1.649 + *
   1.650 + * @param aCallback
   1.651 + *        Function to be called when done.
   1.652 + */
   1.653 +function waitForConnectionClosed(aCallback)
   1.654 +{
   1.655 +  Services.obs.addObserver(function WFCCCallback() {
   1.656 +    Services.obs.removeObserver(WFCCCallback, "places-connection-closed");
   1.657 +    aCallback();
   1.658 +  }, "places-connection-closed", false);
   1.659 +  shutdownPlaces();
   1.660 +}
   1.661 +
   1.662 +/**
   1.663 + * Tests if a given guid is valid for use in Places or not.
   1.664 + *
   1.665 + * @param aGuid
   1.666 + *        The guid to test.
   1.667 + * @param [optional] aStack
   1.668 + *        The stack frame used to report the error.
   1.669 + */
   1.670 +function do_check_valid_places_guid(aGuid,
   1.671 +                                    aStack)
   1.672 +{
   1.673 +  if (!aStack) {
   1.674 +    aStack = Components.stack.caller;
   1.675 +  }
   1.676 +  do_check_true(/^[a-zA-Z0-9\-_]{12}$/.test(aGuid), aStack);
   1.677 +}
   1.678 +
   1.679 +/**
   1.680 + * Retrieves the guid for a given uri.
   1.681 + *
   1.682 + * @param aURI
   1.683 + *        The uri to check.
   1.684 + * @param [optional] aStack
   1.685 + *        The stack frame used to report the error.
   1.686 + * @return the associated the guid.
   1.687 + */
   1.688 +function do_get_guid_for_uri(aURI,
   1.689 +                             aStack)
   1.690 +{
   1.691 +  if (!aStack) {
   1.692 +    aStack = Components.stack.caller;
   1.693 +  }
   1.694 +  let stmt = DBConn().createStatement(
   1.695 +    "SELECT guid "
   1.696 +  + "FROM moz_places "
   1.697 +  + "WHERE url = :url "
   1.698 +  );
   1.699 +  stmt.params.url = aURI.spec;
   1.700 +  do_check_true(stmt.executeStep(), aStack);
   1.701 +  let guid = stmt.row.guid;
   1.702 +  stmt.finalize();
   1.703 +  do_check_valid_places_guid(guid, aStack);
   1.704 +  return guid;
   1.705 +}
   1.706 +
   1.707 +/**
   1.708 + * Tests that a guid was set in moz_places for a given uri.
   1.709 + *
   1.710 + * @param aURI
   1.711 + *        The uri to check.
   1.712 + * @param [optional] aGUID
   1.713 + *        The expected guid in the database.
   1.714 + */
   1.715 +function do_check_guid_for_uri(aURI,
   1.716 +                               aGUID)
   1.717 +{
   1.718 +  let caller = Components.stack.caller;
   1.719 +  let guid = do_get_guid_for_uri(aURI, caller);
   1.720 +  if (aGUID) {
   1.721 +    do_check_valid_places_guid(aGUID, caller);
   1.722 +    do_check_eq(guid, aGUID, caller);
   1.723 +  }
   1.724 +}
   1.725 +
   1.726 +/**
   1.727 + * Retrieves the guid for a given bookmark.
   1.728 + *
   1.729 + * @param aId
   1.730 + *        The bookmark id to check.
   1.731 + * @param [optional] aStack
   1.732 + *        The stack frame used to report the error.
   1.733 + * @return the associated the guid.
   1.734 + */
   1.735 +function do_get_guid_for_bookmark(aId,
   1.736 +                                  aStack)
   1.737 +{
   1.738 +  if (!aStack) {
   1.739 +    aStack = Components.stack.caller;
   1.740 +  }
   1.741 +  let stmt = DBConn().createStatement(
   1.742 +    "SELECT guid "
   1.743 +  + "FROM moz_bookmarks "
   1.744 +  + "WHERE id = :item_id "
   1.745 +  );
   1.746 +  stmt.params.item_id = aId;
   1.747 +  do_check_true(stmt.executeStep(), aStack);
   1.748 +  let guid = stmt.row.guid;
   1.749 +  stmt.finalize();
   1.750 +  do_check_valid_places_guid(guid, aStack);
   1.751 +  return guid;
   1.752 +}
   1.753 +
   1.754 +/**
   1.755 + * Tests that a guid was set in moz_places for a given bookmark.
   1.756 + *
   1.757 + * @param aId
   1.758 + *        The bookmark id to check.
   1.759 + * @param [optional] aGUID
   1.760 + *        The expected guid in the database.
   1.761 + */
   1.762 +function do_check_guid_for_bookmark(aId,
   1.763 +                                    aGUID)
   1.764 +{
   1.765 +  let caller = Components.stack.caller;
   1.766 +  let guid = do_get_guid_for_bookmark(aId, caller);
   1.767 +  if (aGUID) {
   1.768 +    do_check_valid_places_guid(aGUID, caller);
   1.769 +    do_check_eq(guid, aGUID, caller);
   1.770 +  }
   1.771 +}
   1.772 +
   1.773 +/**
   1.774 + * Logs info to the console in the standard way (includes the filename).
   1.775 + *
   1.776 + * @param aMessage
   1.777 + *        The message to log to the console.
   1.778 + */
   1.779 +function do_log_info(aMessage)
   1.780 +{
   1.781 +  print("TEST-INFO | " + _TEST_FILE + " | " + aMessage);
   1.782 +}
   1.783 +
   1.784 +/**
   1.785 + * Compares 2 arrays returning whether they contains the same elements.
   1.786 + *
   1.787 + * @param a1
   1.788 + *        First array to compare.
   1.789 + * @param a2
   1.790 + *        Second array to compare.
   1.791 + * @param [optional] sorted
   1.792 + *        Whether the comparison should take in count position of the elements.
   1.793 + * @return true if the arrays contain the same elements, false otherwise.
   1.794 + */
   1.795 +function do_compare_arrays(a1, a2, sorted)
   1.796 +{
   1.797 +  if (a1.length != a2.length)
   1.798 +    return false;
   1.799 +
   1.800 +  if (sorted) {
   1.801 +    return a1.every(function (e, i) e == a2[i]);
   1.802 +  }
   1.803 +  else {
   1.804 +    return a1.filter(function (e) a2.indexOf(e) == -1).length == 0 &&
   1.805 +           a2.filter(function (e) a1.indexOf(e) == -1).length == 0;
   1.806 +  }
   1.807 +}
   1.808 +
   1.809 +/**
   1.810 + * Generic nsINavBookmarkObserver that doesn't implement anything, but provides
   1.811 + * dummy methods to prevent errors about an object not having a certain method.
   1.812 + */
   1.813 +function NavBookmarkObserver() {}
   1.814 +
   1.815 +NavBookmarkObserver.prototype = {
   1.816 +  onBeginUpdateBatch: function () {},
   1.817 +  onEndUpdateBatch: function () {},
   1.818 +  onItemAdded: function () {},
   1.819 +  onItemRemoved: function () {},
   1.820 +  onItemChanged: function () {},
   1.821 +  onItemVisited: function () {},
   1.822 +  onItemMoved: function () {},
   1.823 +  QueryInterface: XPCOMUtils.generateQI([
   1.824 +    Ci.nsINavBookmarkObserver,
   1.825 +  ])
   1.826 +};
   1.827 +
   1.828 +/**
   1.829 + * Generic nsINavHistoryObserver that doesn't implement anything, but provides
   1.830 + * dummy methods to prevent errors about an object not having a certain method.
   1.831 + */
   1.832 +function NavHistoryObserver() {}
   1.833 +
   1.834 +NavHistoryObserver.prototype = {
   1.835 +  onBeginUpdateBatch: function () {},
   1.836 +  onEndUpdateBatch: function () {},
   1.837 +  onVisit: function () {},
   1.838 +  onTitleChanged: function () {},
   1.839 +  onDeleteURI: function () {},
   1.840 +  onClearHistory: function () {},
   1.841 +  onPageChanged: function () {},
   1.842 +  onDeleteVisits: function () {},
   1.843 +  QueryInterface: XPCOMUtils.generateQI([
   1.844 +    Ci.nsINavHistoryObserver,
   1.845 +  ])
   1.846 +};
   1.847 +
   1.848 +/**
   1.849 + * Generic nsINavHistoryResultObserver that doesn't implement anything, but
   1.850 + * provides dummy methods to prevent errors about an object not having a certain
   1.851 + * method.
   1.852 + */
   1.853 +function NavHistoryResultObserver() {}
   1.854 +
   1.855 +NavHistoryResultObserver.prototype = {
   1.856 +  batching: function () {},
   1.857 +  containerStateChanged: function () {},
   1.858 +  invalidateContainer: function () {},
   1.859 +  nodeAnnotationChanged: function () {},
   1.860 +  nodeDateAddedChanged: function () {},
   1.861 +  nodeHistoryDetailsChanged: function () {},
   1.862 +  nodeIconChanged: function () {},
   1.863 +  nodeInserted: function () {},
   1.864 +  nodeKeywordChanged: function () {},
   1.865 +  nodeLastModifiedChanged: function () {},
   1.866 +  nodeMoved: function () {},
   1.867 +  nodeRemoved: function () {},
   1.868 +  nodeTagsChanged: function () {},
   1.869 +  nodeTitleChanged: function () {},
   1.870 +  nodeURIChanged: function () {},
   1.871 +  sortingChanged: function () {},
   1.872 +  QueryInterface: XPCOMUtils.generateQI([
   1.873 +    Ci.nsINavHistoryResultObserver,
   1.874 +  ])
   1.875 +};
   1.876 +
   1.877 +/**
   1.878 + * Asynchronously adds visits to a page.
   1.879 + *
   1.880 + * @param aPlaceInfo
   1.881 + *        Can be an nsIURI, in such a case a single LINK visit will be added.
   1.882 + *        Otherwise can be an object describing the visit to add, or an array
   1.883 + *        of these objects:
   1.884 + *          { uri: nsIURI of the page,
   1.885 + *            transition: one of the TRANSITION_* from nsINavHistoryService,
   1.886 + *            [optional] title: title of the page,
   1.887 + *            [optional] visitDate: visit date in microseconds from the epoch
   1.888 + *            [optional] referrer: nsIURI of the referrer for this visit
   1.889 + *          }
   1.890 + *
   1.891 + * @return {Promise}
   1.892 + * @resolves When all visits have been added successfully.
   1.893 + * @rejects JavaScript exception.
   1.894 + */
   1.895 +function promiseAddVisits(aPlaceInfo)
   1.896 +{
   1.897 +  let deferred = Promise.defer();
   1.898 +  let places = [];
   1.899 +  if (aPlaceInfo instanceof Ci.nsIURI) {
   1.900 +    places.push({ uri: aPlaceInfo });
   1.901 +  }
   1.902 +  else if (Array.isArray(aPlaceInfo)) {
   1.903 +    places = places.concat(aPlaceInfo);
   1.904 +  } else {
   1.905 +    places.push(aPlaceInfo)
   1.906 +  }
   1.907 +
   1.908 +  // Create mozIVisitInfo for each entry.
   1.909 +  let now = Date.now();
   1.910 +  for (let i = 0; i < places.length; i++) {
   1.911 +    if (!places[i].title) {
   1.912 +      places[i].title = "test visit for " + places[i].uri.spec;
   1.913 +    }
   1.914 +    places[i].visits = [{
   1.915 +      transitionType: places[i].transition === undefined ? TRANSITION_LINK
   1.916 +                                                         : places[i].transition,
   1.917 +      visitDate: places[i].visitDate || (now++) * 1000,
   1.918 +      referrerURI: places[i].referrer
   1.919 +    }];
   1.920 +  }
   1.921 +
   1.922 +  PlacesUtils.asyncHistory.updatePlaces(
   1.923 +    places,
   1.924 +    {
   1.925 +      handleError: function AAV_handleError(aResultCode, aPlaceInfo) {
   1.926 +        let ex = new Components.Exception("Unexpected error in adding visits.",
   1.927 +                                          aResultCode);
   1.928 +        deferred.reject(ex);
   1.929 +      },
   1.930 +      handleResult: function () {},
   1.931 +      handleCompletion: function UP_handleCompletion() {
   1.932 +        deferred.resolve();
   1.933 +      }
   1.934 +    }
   1.935 +  );
   1.936 +
   1.937 +  return deferred.promise;
   1.938 +}
   1.939 +
   1.940 +/**
   1.941 + * Asynchronously check a url is visited.
   1.942 + *
   1.943 + * @param aURI The URI.
   1.944 + * @return {Promise}
   1.945 + * @resolves When the check has been added successfully.
   1.946 + * @rejects JavaScript exception.
   1.947 + */
   1.948 +function promiseIsURIVisited(aURI) {
   1.949 +  let deferred = Promise.defer();
   1.950 +
   1.951 +  PlacesUtils.asyncHistory.isURIVisited(aURI, function(aURI, aIsVisited) {
   1.952 +    deferred.resolve(aIsVisited);
   1.953 +  });
   1.954 +
   1.955 +  return deferred.promise;
   1.956 +}
   1.957 +

mercurial