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.

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

mercurial