toolkit/components/places/tests/unit/test_async_history_api.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 /* Any copyright is dedicated to the Public Domain.
     2    http://creativecommons.org/publicdomain/zero/1.0/ */
     4 /**
     5  * This file tests the async history API exposed by mozIAsyncHistory.
     6  */
     8 ////////////////////////////////////////////////////////////////////////////////
     9 //// Globals
    11 const TEST_DOMAIN = "http://mozilla.org/";
    12 const URI_VISIT_SAVED = "uri-visit-saved";
    13 const RECENT_EVENT_THRESHOLD = 15 * 60 * 1000000;
    15 ////////////////////////////////////////////////////////////////////////////////
    16 //// Helpers
    17 /**
    18  * Object that represents a mozIVisitInfo object.
    19  *
    20  * @param [optional] aTransitionType
    21  *        The transition type of the visit.  Defaults to TRANSITION_LINK if not
    22  *        provided.
    23  * @param [optional] aVisitTime
    24  *        The time of the visit.  Defaults to now if not provided.
    25  */
    26 function VisitInfo(aTransitionType,
    27                    aVisitTime)
    28 {
    29   this.transitionType =
    30     aTransitionType === undefined ? TRANSITION_LINK : aTransitionType;
    31   this.visitDate = aVisitTime || Date.now() * 1000;
    32 }
    34 function promiseUpdatePlaces(aPlaces) {
    35   let deferred = Promise.defer();
    36   PlacesUtils.asyncHistory.updatePlaces(aPlaces, {
    37     _errors: [],
    38     _results: [],
    39     handleError: function handleError(aResultCode, aPlace) {
    40       this._errors.push({ resultCode: aResultCode, info: aPlace});
    41     },
    42     handleResult: function handleResult(aPlace) {
    43       this._results.push(aPlace);
    44     },
    45     handleCompletion: function handleCompletion() {
    46       deferred.resolve({ errors: this._errors, results: this._results });
    47     }
    48   });
    50   return deferred.promise;
    51 }
    53 /**
    54  * Listens for a title change notification, and calls aCallback when it gets it.
    55  *
    56  * @param aURI
    57  *        The URI of the page we expect a notification for.
    58  * @param aExpectedTitle
    59  *        The expected title of the URI we expect a notification for.
    60  * @param aCallback
    61  *        The method to call when we have gotten the proper notification about
    62  *        the title changing.
    63  */
    64 function TitleChangedObserver(aURI,
    65                               aExpectedTitle,
    66                               aCallback)
    67 {
    68   this.uri = aURI;
    69   this.expectedTitle = aExpectedTitle;
    70   this.callback = aCallback;
    71 }
    72 TitleChangedObserver.prototype = {
    73   __proto__: NavHistoryObserver.prototype,
    74   onTitleChanged: function(aURI,
    75                            aTitle,
    76                            aGUID)
    77   {
    78     do_log_info("onTitleChanged(" + aURI.spec + ", " + aTitle + ", " + aGUID + ")");
    79     if (!this.uri.equals(aURI)) {
    80       return;
    81     }
    82     do_check_eq(aTitle, this.expectedTitle);
    83     do_check_guid_for_uri(aURI, aGUID);
    84     this.callback();
    85   },
    86 };
    88 /**
    89  * Listens for a visit notification, and calls aCallback when it gets it.
    90  *
    91  * @param aURI
    92  *        The URI of the page we expect a notification for.
    93  * @param aCallback
    94  *        The method to call when we have gotten the proper notification about
    95  *        being visited.
    96  */
    97 function VisitObserver(aURI,
    98                        aGUID,
    99                        aCallback)
   100 {
   101   this.uri = aURI;
   102   this.guid = aGUID;
   103   this.callback = aCallback;
   104 }
   105 VisitObserver.prototype = {
   106   __proto__: NavHistoryObserver.prototype,
   107   onVisit: function(aURI,
   108                     aVisitId,
   109                     aTime,
   110                     aSessionId,
   111                     aReferringId,
   112                     aTransitionType,
   113                     aGUID)
   114   {
   115     do_log_info("onVisit(" + aURI.spec + ", " + aVisitId + ", " + aTime +
   116                 ", " + aSessionId + ", " + aReferringId + ", " +
   117                 aTransitionType + ", " + aGUID + ")"); 
   118     if (!this.uri.equals(aURI) || this.guid != aGUID) {
   119       return;
   120     }
   121     this.callback(aTime, aTransitionType);
   122   },
   123 };
   125 /**
   126  * Tests that a title was set properly in the database.
   127  *
   128  * @param aURI
   129  *        The uri to check.
   130  * @param aTitle
   131  *        The expected title in the database.
   132  */
   133 function do_check_title_for_uri(aURI,
   134                                 aTitle)
   135 {
   136   let stack = Components.stack.caller;
   137   let stmt = DBConn().createStatement(
   138     "SELECT title " +
   139     "FROM moz_places " +
   140     "WHERE url = :url "
   141   );
   142   stmt.params.url = aURI.spec;
   143   do_check_true(stmt.executeStep(), stack);
   144   do_check_eq(stmt.row.title, aTitle, stack);
   145   stmt.finalize();
   146 }
   148 ////////////////////////////////////////////////////////////////////////////////
   149 //// Test Functions
   151 function test_interface_exists()
   152 {
   153   let history = Cc["@mozilla.org/browser/history;1"].getService(Ci.nsISupports);
   154   do_check_true(history instanceof Ci.mozIAsyncHistory);
   155 }
   157 function test_invalid_uri_throws()
   158 {
   159   // First, test passing in nothing.
   160   let place = {
   161     visits: [
   162       new VisitInfo(),
   163     ],
   164   };
   165   try {
   166     yield promiseUpdatePlaces(place);
   167     do_throw("Should have thrown!");
   168   }
   169   catch (e) {
   170     do_check_eq(e.result, Cr.NS_ERROR_INVALID_ARG);
   171   }
   173   // Now, test other bogus things.
   174   const TEST_VALUES = [
   175     null,
   176     undefined,
   177     {},
   178     [],
   179     TEST_DOMAIN + "test_invalid_id_throws",
   180   ];
   181   for (let i = 0; i < TEST_VALUES.length; i++) {
   182     place.uri = TEST_VALUES[i];
   183     try {
   184       yield promiseUpdatePlaces(place);
   185       do_throw("Should have thrown!");
   186     }
   187     catch (e) {
   188       do_check_eq(e.result, Cr.NS_ERROR_INVALID_ARG);
   189     }
   190   }
   191 }
   193 function test_invalid_places_throws()
   194 {
   195   // First, test passing in nothing.
   196   try {
   197     PlacesUtils.asyncHistory.updatePlaces();
   198     do_throw("Should have thrown!");
   199   }
   200   catch (e) {
   201     do_check_eq(e.result, Cr.NS_ERROR_XPC_NOT_ENOUGH_ARGS);
   202   }
   204   // Now, test other bogus things.
   205   const TEST_VALUES = [
   206     null,
   207     undefined,
   208     {},
   209     [],
   210     "",
   211   ];
   212   for (let i = 0; i < TEST_VALUES.length; i++) {
   213     let value = TEST_VALUES[i];
   214     try {
   215       yield promiseUpdatePlaces(value);
   216       do_throw("Should have thrown!");
   217     }
   218     catch (e) {
   219       do_check_eq(e.result, Cr.NS_ERROR_INVALID_ARG);
   220     }
   221   }
   222 }
   224 function test_invalid_guid_throws()
   225 {
   226   // First check invalid length guid.
   227   let place = {
   228     guid: "BAD_GUID",
   229     uri: NetUtil.newURI(TEST_DOMAIN + "test_invalid_guid_throws"),
   230     visits: [
   231       new VisitInfo(),
   232     ],
   233   };
   234   try {
   235     yield promiseUpdatePlaces(place);
   236     do_throw("Should have thrown!");
   237   }
   238   catch (e) {
   239     do_check_eq(e.result, Cr.NS_ERROR_INVALID_ARG);
   240   }
   242   // Now check invalid character guid.
   243   place.guid = "__BADGUID+__";
   244   do_check_eq(place.guid.length, 12);
   245   try {
   246     yield promiseUpdatePlaces(place);
   247     do_throw("Should have thrown!");
   248   }
   249   catch (e) {
   250     do_check_eq(e.result, Cr.NS_ERROR_INVALID_ARG);
   251   }
   252 }
   254 function test_no_visits_throws()
   255 {
   256   const TEST_URI =
   257     NetUtil.newURI(TEST_DOMAIN + "test_no_id_or_guid_no_visits_throws");
   258   const TEST_GUID = "_RANDOMGUID_";
   259   const TEST_PLACEID = 2;
   261   let log_test_conditions = function(aPlace) {
   262     let str = "Testing place with " +
   263       (aPlace.uri ? "uri" : "no uri") + ", " +
   264       (aPlace.guid ? "guid" : "no guid") + ", " +
   265       (aPlace.visits ? "visits array" : "no visits array");
   266     do_log_info(str);
   267   };
   269   // Loop through every possible case.  Note that we don't actually care about
   270   // the case where we have no uri, place id, or guid (covered by another test),
   271   // but it is easier to just make sure it too throws than to exclude it.
   272   let place = { };
   273   for (let uri = 1; uri >= 0; uri--) {
   274     place.uri = uri ? TEST_URI : undefined;
   276     for (let guid = 1; guid >= 0; guid--) {
   277       place.guid = guid ? TEST_GUID : undefined;
   279       for (let visits = 1; visits >= 0; visits--) {
   280         place.visits = visits ? [] : undefined;
   282         log_test_conditions(place);
   283         try {
   284           yield promiseUpdatePlaces(place);
   285           do_throw("Should have thrown!");
   286         }
   287         catch (e) {
   288           do_check_eq(e.result, Cr.NS_ERROR_INVALID_ARG);
   289         }
   290       }
   291     }
   292   }
   293 }
   295 function test_add_visit_no_date_throws()
   296 {
   297   let place = {
   298     uri: NetUtil.newURI(TEST_DOMAIN + "test_add_visit_no_date_throws"),
   299     visits: [
   300       new VisitInfo(),
   301     ],
   302   };
   303   delete place.visits[0].visitDate;
   304   try {
   305     yield promiseUpdatePlaces(place);
   306     do_throw("Should have thrown!");
   307   }
   308   catch (e) {
   309     do_check_eq(e.result, Cr.NS_ERROR_INVALID_ARG);
   310   }
   311 }
   313 function test_add_visit_no_transitionType_throws()
   314 {
   315   let place = {
   316     uri: NetUtil.newURI(TEST_DOMAIN + "test_add_visit_no_transitionType_throws"),
   317     visits: [
   318       new VisitInfo(),
   319     ],
   320   };
   321   delete place.visits[0].transitionType;
   322   try {
   323     yield promiseUpdatePlaces(place);
   324     do_throw("Should have thrown!");
   325   }
   326   catch (e) {
   327     do_check_eq(e.result, Cr.NS_ERROR_INVALID_ARG);
   328   }
   329 }
   331 function test_add_visit_invalid_transitionType_throws()
   332 {
   333   // First, test something that has a transition type lower than the first one.
   334   let place = {
   335     uri: NetUtil.newURI(TEST_DOMAIN +
   336                         "test_add_visit_invalid_transitionType_throws"),
   337     visits: [
   338       new VisitInfo(TRANSITION_LINK - 1),
   339     ],
   340   };
   341   try {
   342     yield promiseUpdatePlaces(place);
   343     do_throw("Should have thrown!");
   344   }
   345   catch (e) {
   346     do_check_eq(e.result, Cr.NS_ERROR_INVALID_ARG);
   347   }
   349   // Now, test something that has a transition type greater than the last one.
   350   place.visits[0] = new VisitInfo(TRANSITION_FRAMED_LINK + 1);
   351   try {
   352     yield promiseUpdatePlaces(place);
   353     do_throw("Should have thrown!");
   354   }
   355   catch (e) {
   356     do_check_eq(e.result, Cr.NS_ERROR_INVALID_ARG);
   357   }
   358 }
   360 function test_non_addable_uri_errors()
   361 {
   362   // Array of protocols that nsINavHistoryService::canAddURI returns false for.
   363   const URLS = [
   364     "about:config",
   365     "imap://cyrus.andrew.cmu.edu/archive.imap",
   366     "news://new.mozilla.org/mozilla.dev.apps.firefox",
   367     "mailbox:Inbox",
   368     "moz-anno:favicon:http://mozilla.org/made-up-favicon",
   369     "view-source:http://mozilla.org",
   370     "chrome://browser/content/browser.xul",
   371     "resource://gre-resources/hiddenWindow.html",
   372     "data:,Hello%2C%20World!",
   373     "wyciwyg:/0/http://mozilla.org",
   374     "javascript:alert('hello wolrd!');",
   375     "blob:foo",
   376   ];
   377   let places = [];
   378   URLS.forEach(function(url) {
   379     try {
   380       let place = {
   381         uri: NetUtil.newURI(url),
   382         title: "test for " + url,
   383         visits: [
   384           new VisitInfo(),
   385         ],
   386       };
   387       places.push(place);
   388     }
   389     catch (e if e.result === Cr.NS_ERROR_FAILURE) {
   390       // NetUtil.newURI() can throw if e.g. our app knows about imap://
   391       // but the account is not set up and so the URL is invalid for us.
   392       // Note this in the log but ignore as it's not the subject of this test.
   393       do_log_info("Could not construct URI for '" + url + "'; ignoring");
   394     }
   395   });
   397   let placesResult = yield promiseUpdatePlaces(places);
   398   if (placesResult.results.length > 0) {
   399     do_throw("Unexpected success.");
   400   }
   401   for (let place of placesResult.errors) {
   402     do_log_info("Checking '" + place.info.uri.spec + "'");
   403     do_check_eq(place.resultCode, Cr.NS_ERROR_INVALID_ARG);
   404     do_check_false(yield promiseIsURIVisited(place.info.uri));
   405   }
   406   yield promiseAsyncUpdates();
   407 }
   409 function test_duplicate_guid_errors()
   410 {
   411   // This test ensures that trying to add a visit, with a guid already found in
   412   // another visit, fails.
   413   let place = {
   414     uri: NetUtil.newURI(TEST_DOMAIN + "test_duplicate_guid_fails_first"),
   415     visits: [
   416       new VisitInfo(),
   417     ],
   418   };
   420   do_check_false(yield promiseIsURIVisited(place.uri));
   421   let placesResult = yield promiseUpdatePlaces(place);
   422   if (placesResult.errors.length > 0) {
   423     do_throw("Unexpected error.");
   424   }
   425   let placeInfo = placesResult.results[0];
   426   do_check_true(yield promiseIsURIVisited(placeInfo.uri));
   428   let badPlace = {
   429     uri: NetUtil.newURI(TEST_DOMAIN + "test_duplicate_guid_fails_second"),
   430     visits: [
   431       new VisitInfo(),
   432     ],
   433     guid: placeInfo.guid,
   434   };
   436   do_check_false(yield promiseIsURIVisited(badPlace.uri));
   437   placesResult = yield promiseUpdatePlaces(badPlace);
   438   if (placesResult.results.length > 0) {
   439     do_throw("Unexpected success.");
   440   }
   441   let badPlaceInfo = placesResult.errors[0];
   442   do_check_eq(badPlaceInfo.resultCode, Cr.NS_ERROR_STORAGE_CONSTRAINT);
   443   do_check_false(yield promiseIsURIVisited(badPlaceInfo.info.uri));
   445   yield promiseAsyncUpdates();
   446 }
   448 function test_invalid_referrerURI_ignored()
   449 {
   450   let place = {
   451     uri: NetUtil.newURI(TEST_DOMAIN +
   452                         "test_invalid_referrerURI_ignored"),
   453     visits: [
   454       new VisitInfo(),
   455     ],
   456   };
   457   place.visits[0].referrerURI = NetUtil.newURI(place.uri.spec + "_unvisistedURI");
   458   do_check_false(yield promiseIsURIVisited(place.uri));
   459   do_check_false(yield promiseIsURIVisited(place.visits[0].referrerURI));
   461   let placesResult = yield promiseUpdatePlaces(place);
   462   if (placesResult.errors.length > 0) {
   463     do_throw("Unexpected error.");
   464   }
   465   let placeInfo = placesResult.results[0];
   466   do_check_true(yield promiseIsURIVisited(placeInfo.uri));
   468   // Check to make sure we do not visit the invalid referrer.
   469   do_check_false(yield promiseIsURIVisited(place.visits[0].referrerURI));
   471   // Check to make sure from_visit is zero in database.
   472   let stmt = DBConn().createStatement(
   473     "SELECT from_visit " +
   474     "FROM moz_historyvisits " +
   475     "WHERE id = :visit_id"
   476   );
   477   stmt.params.visit_id = placeInfo.visits[0].visitId;
   478   do_check_true(stmt.executeStep());
   479   do_check_eq(stmt.row.from_visit, 0);
   480   stmt.finalize();
   482   yield promiseAsyncUpdates();
   483 }
   485 function test_nonnsIURI_referrerURI_ignored()
   486 {
   487   let place = {
   488     uri: NetUtil.newURI(TEST_DOMAIN +
   489                         "test_nonnsIURI_referrerURI_ignored"),
   490     visits: [
   491       new VisitInfo(),
   492     ],
   493   };
   494   place.visits[0].referrerURI = place.uri.spec + "_nonnsIURI";
   495   do_check_false(yield promiseIsURIVisited(place.uri));
   497   let placesResult = yield promiseUpdatePlaces(place);
   498   if (placesResult.errors.length > 0) {
   499     do_throw("Unexpected error.");
   500   }
   501   let placeInfo = placesResult.results[0];
   502   do_check_true(yield promiseIsURIVisited(placeInfo.uri));
   504   // Check to make sure from_visit is zero in database.
   505   let stmt = DBConn().createStatement(
   506     "SELECT from_visit " +
   507     "FROM moz_historyvisits " +
   508     "WHERE id = :visit_id"
   509   );
   510   stmt.params.visit_id = placeInfo.visits[0].visitId;
   511   do_check_true(stmt.executeStep());
   512   do_check_eq(stmt.row.from_visit, 0);
   513   stmt.finalize();
   515   yield promiseAsyncUpdates();
   516 }
   518 function test_old_referrer_ignored()
   519 {
   520   // This tests that a referrer for a visit which is not recent (specifically,
   521   // older than 15 minutes as per RECENT_EVENT_THRESHOLD) is not saved by
   522   // updatePlaces.
   523   let oldTime = (Date.now() * 1000) - (RECENT_EVENT_THRESHOLD + 1);
   524   let referrerPlace = {
   525     uri: NetUtil.newURI(TEST_DOMAIN + "test_old_referrer_ignored_referrer"),
   526     visits: [
   527       new VisitInfo(TRANSITION_LINK, oldTime),
   528     ],
   529   };
   531   // First we must add our referrer to the history so that it is not ignored
   532   // as being invalid.
   533   do_check_false(yield promiseIsURIVisited(referrerPlace.uri));
   534   let placesResult = yield promiseUpdatePlaces(referrerPlace);
   535   if (placesResult.errors.length > 0) {
   536     do_throw("Unexpected error.");
   537   }
   539   // Now that the referrer is added, we can add a page with a valid
   540   // referrer to determine if the recency of the referrer is taken into
   541   // account.
   542   do_check_true(yield promiseIsURIVisited(referrerPlace.uri));
   544   let visitInfo = new VisitInfo();
   545   visitInfo.referrerURI = referrerPlace.uri;
   546   let place = {
   547     uri: NetUtil.newURI(TEST_DOMAIN + "test_old_referrer_ignored_page"),
   548     visits: [
   549       visitInfo,
   550     ],
   551   };
   553   do_check_false(yield promiseIsURIVisited(place.uri));
   554   placesResult = yield promiseUpdatePlaces(place);
   555   if (placesResult.errors.length > 0) {
   556     do_throw("Unexpected error.");
   557   }
   558   let placeInfo = placesResult.results[0];
   559   do_check_true(yield promiseIsURIVisited(place.uri));
   561   // Though the visit will not contain the referrer, we must examine the
   562   // database to be sure.
   563   do_check_eq(placeInfo.visits[0].referrerURI, null);
   564   let stmt = DBConn().createStatement(
   565     "SELECT COUNT(1) AS count " +
   566     "FROM moz_historyvisits " +
   567     "WHERE place_id = (SELECT id FROM moz_places WHERE url = :page_url) " +
   568     "AND from_visit = 0 "
   569   );
   570   stmt.params.page_url = place.uri.spec;
   571   do_check_true(stmt.executeStep());
   572   do_check_eq(stmt.row.count, 1);
   573   stmt.finalize();
   575   yield promiseAsyncUpdates();
   576 }
   578 function test_place_id_ignored()
   579 {
   580   let place = {
   581     uri: NetUtil.newURI(TEST_DOMAIN + "test_place_id_ignored_first"),
   582     visits: [
   583       new VisitInfo(),
   584     ],
   585   };
   587   do_check_false(yield promiseIsURIVisited(place.uri));
   588   let placesResult = yield promiseUpdatePlaces(place);
   589   if (placesResult.errors.length > 0) {
   590     do_throw("Unexpected error.");
   591   }
   592   let placeInfo = placesResult.results[0];
   593   do_check_true(yield promiseIsURIVisited(place.uri));
   595   let placeId = placeInfo.placeId;
   596   do_check_neq(placeId, 0);
   598   let badPlace = {
   599     uri: NetUtil.newURI(TEST_DOMAIN + "test_place_id_ignored_second"),
   600     visits: [
   601       new VisitInfo(),
   602     ],
   603     placeId: placeId,
   604   };
   606   do_check_false(yield promiseIsURIVisited(badPlace.uri));
   607   placesResult = yield promiseUpdatePlaces(badPlace);
   608   if (placesResult.errors.length > 0) {
   609     do_throw("Unexpected error.");
   610   }
   611   placeInfo = placesResult.results[0];
   613   do_check_neq(placeInfo.placeId, placeId);
   614   do_check_true(yield promiseIsURIVisited(badPlace.uri));
   616   yield promiseAsyncUpdates();
   617 }
   619 function test_handleCompletion_called_when_complete()
   620 {
   621   // We test a normal visit, and embeded visit, and a uri that would fail
   622   // the canAddURI test to make sure that the notification happens after *all*
   623   // of them have had a callback.
   624   let places = [
   625     { uri: NetUtil.newURI(TEST_DOMAIN +
   626                           "test_handleCompletion_called_when_complete"),
   627       visits: [
   628         new VisitInfo(),
   629         new VisitInfo(TRANSITION_EMBED),
   630       ],
   631     },
   632     { uri: NetUtil.newURI("data:,Hello%2C%20World!"),
   633       visits: [
   634         new VisitInfo(),
   635       ],
   636     },
   637   ];
   638   do_check_false(yield promiseIsURIVisited(places[0].uri));
   639   do_check_false(yield promiseIsURIVisited(places[1].uri));
   641   const EXPECTED_COUNT_SUCCESS = 2;
   642   const EXPECTED_COUNT_FAILURE = 1;
   643   let callbackCountSuccess = 0;
   644   let callbackCountFailure = 0;
   646   let placesResult = yield promiseUpdatePlaces(places);
   647   for (let place of placesResult.results) {
   648     let checker = PlacesUtils.history.canAddURI(place.uri) ?
   649       do_check_true : do_check_false;
   650     callbackCountSuccess++;
   651   }
   652   for (let error of placesResult.errors) {
   653     callbackCountFailure++;
   654   }
   656   do_check_eq(callbackCountSuccess, EXPECTED_COUNT_SUCCESS);
   657   do_check_eq(callbackCountFailure, EXPECTED_COUNT_FAILURE);
   658   yield promiseAsyncUpdates();
   659 }
   661 function test_add_visit()
   662 {
   663   const VISIT_TIME = Date.now() * 1000;
   664   let place = {
   665     uri: NetUtil.newURI(TEST_DOMAIN + "test_add_visit"),
   666     title: "test_add_visit title",
   667     visits: [],
   668   };
   669   for (let transitionType = TRANSITION_LINK;
   670        transitionType <= TRANSITION_FRAMED_LINK;
   671        transitionType++) {
   672     place.visits.push(new VisitInfo(transitionType, VISIT_TIME));
   673   }
   674   do_check_false(yield promiseIsURIVisited(place.uri));
   676   let callbackCount = 0;
   677   let placesResult = yield promiseUpdatePlaces(place);
   678   if (placesResult.errors.length > 0) {
   679     do_throw("Unexpected error.");
   680   }
   681   for (let placeInfo of placesResult.results) {
   682     do_check_true(yield promiseIsURIVisited(place.uri));
   684     // Check mozIPlaceInfo properties.
   685     do_check_true(place.uri.equals(placeInfo.uri));
   686     do_check_eq(placeInfo.frecency, -1); // We don't pass frecency here!
   687     do_check_eq(placeInfo.title, place.title);
   689     // Check mozIVisitInfo properties.
   690     let visits = placeInfo.visits;
   691     do_check_eq(visits.length, 1);
   692     let visit = visits[0];
   693     do_check_eq(visit.visitDate, VISIT_TIME);
   694     do_check_true(visit.transitionType >= TRANSITION_LINK &&
   695                     visit.transitionType <= TRANSITION_FRAMED_LINK);
   696     do_check_true(visit.referrerURI === null);
   698     // For TRANSITION_EMBED visits, many properties will always be zero or
   699     // undefined.
   700     if (visit.transitionType == TRANSITION_EMBED) {
   701       // Check mozIPlaceInfo properties.
   702       do_check_eq(placeInfo.placeId, 0, '//');
   703       do_check_eq(placeInfo.guid, null);
   705       // Check mozIVisitInfo properties.
   706       do_check_eq(visit.visitId, 0);
   707     }
   708     // But they should be valid for non-embed visits.
   709     else {
   710       // Check mozIPlaceInfo properties.
   711       do_check_true(placeInfo.placeId > 0);
   712       do_check_valid_places_guid(placeInfo.guid);
   714       // Check mozIVisitInfo properties.
   715       do_check_true(visit.visitId > 0);
   716     }
   718     // If we have had all of our callbacks, continue running tests.
   719     if (++callbackCount == place.visits.length) {
   720       yield promiseAsyncUpdates();
   721     }
   722   }
   723 }
   725 function test_properties_saved()
   726 {
   727   // Check each transition type to make sure it is saved properly.
   728   let places = [];
   729   for (let transitionType = TRANSITION_LINK;
   730        transitionType <= TRANSITION_FRAMED_LINK;
   731        transitionType++) {
   732     let place = {
   733       uri: NetUtil.newURI(TEST_DOMAIN + "test_properties_saved/" +
   734                           transitionType),
   735       title: "test_properties_saved test",
   736       visits: [
   737         new VisitInfo(transitionType),
   738       ],
   739     };
   740     do_check_false(yield promiseIsURIVisited(place.uri));
   741     places.push(place);
   742   }
   744   let callbackCount = 0;
   745   let placesResult = yield promiseUpdatePlaces(places);
   746   if (placesResult.errors.length > 0) {
   747     do_throw("Unexpected error.");
   748   }
   749   for (let placeInfo of placesResult.results) {
   750     let uri = placeInfo.uri;
   751     do_check_true(yield promiseIsURIVisited(uri));
   752     let visit = placeInfo.visits[0];
   753     print("TEST-INFO | test_properties_saved | updatePlaces callback for " +
   754           "transition type " + visit.transitionType);
   756     // Note that TRANSITION_EMBED should not be in the database.
   757     const EXPECTED_COUNT = visit.transitionType == TRANSITION_EMBED ? 0 : 1;
   759     // mozIVisitInfo::date
   760     let stmt = DBConn().createStatement(
   761       "SELECT COUNT(1) AS count " +
   762       "FROM moz_places h " +
   763       "JOIN moz_historyvisits v " +
   764       "ON h.id = v.place_id " +
   765       "WHERE h.url = :page_url " +
   766       "AND v.visit_date = :visit_date "
   767     );
   768     stmt.params.page_url = uri.spec;
   769     stmt.params.visit_date = visit.visitDate;
   770     do_check_true(stmt.executeStep());
   771     do_check_eq(stmt.row.count, EXPECTED_COUNT);
   772     stmt.finalize();
   774     // mozIVisitInfo::transitionType
   775     stmt = DBConn().createStatement(
   776       "SELECT COUNT(1) AS count " +
   777       "FROM moz_places h " +
   778       "JOIN moz_historyvisits v " +
   779       "ON h.id = v.place_id " +
   780       "WHERE h.url = :page_url " +
   781       "AND v.visit_type = :transition_type "
   782     );
   783     stmt.params.page_url = uri.spec;
   784     stmt.params.transition_type = visit.transitionType;
   785     do_check_true(stmt.executeStep());
   786     do_check_eq(stmt.row.count, EXPECTED_COUNT);
   787     stmt.finalize();
   789     // mozIPlaceInfo::title
   790     stmt = DBConn().createStatement(
   791       "SELECT COUNT(1) AS count " +
   792       "FROM moz_places h " +
   793       "WHERE h.url = :page_url " +
   794       "AND h.title = :title "
   795     );
   796     stmt.params.page_url = uri.spec;
   797     stmt.params.title = placeInfo.title;
   798     do_check_true(stmt.executeStep());
   799     do_check_eq(stmt.row.count, EXPECTED_COUNT);
   800     stmt.finalize();
   802     // If we have had all of our callbacks, continue running tests.
   803     if (++callbackCount == places.length) {
   804       yield promiseAsyncUpdates();
   805     }
   806   }
   807 }
   809 function test_guid_saved()
   810 {
   811   let place = {
   812     uri: NetUtil.newURI(TEST_DOMAIN + "test_guid_saved"),
   813     guid: "__TESTGUID__",
   814     visits: [
   815       new VisitInfo(),
   816     ],
   817   };
   818   do_check_valid_places_guid(place.guid);
   819   do_check_false(yield promiseIsURIVisited(place.uri));
   821   let placesResult = yield promiseUpdatePlaces(place);
   822   if (placesResult.errors.length > 0) {
   823     do_throw("Unexpected error.");
   824   }
   825   let placeInfo = placesResult.results[0];
   826   let uri = placeInfo.uri;
   827   do_check_true(yield promiseIsURIVisited(uri));
   828   do_check_eq(placeInfo.guid, place.guid);
   829   do_check_guid_for_uri(uri, place.guid);
   830   yield promiseAsyncUpdates();
   831 }
   833 function test_referrer_saved()
   834 {
   835   let places = [
   836     { uri: NetUtil.newURI(TEST_DOMAIN + "test_referrer_saved/referrer"),
   837       visits: [
   838         new VisitInfo(),
   839       ],
   840     },
   841     { uri: NetUtil.newURI(TEST_DOMAIN + "test_referrer_saved/test"),
   842       visits: [
   843         new VisitInfo(),
   844       ],
   845     },
   846   ];
   847   places[1].visits[0].referrerURI = places[0].uri;
   848   do_check_false(yield promiseIsURIVisited(places[0].uri));
   849   do_check_false(yield promiseIsURIVisited(places[1].uri));
   851   let resultCount = 0;
   852   let placesResult = yield promiseUpdatePlaces(places);
   853   if (placesResult.errors.length > 0) {
   854     do_throw("Unexpected error.");
   855   }
   856   for (let placeInfo of placesResult.results) {
   857     let uri = placeInfo.uri;
   858     do_check_true(yield promiseIsURIVisited(uri));
   859     let visit = placeInfo.visits[0];
   861     // We need to insert all of our visits before we can test conditions.
   862     if (++resultCount == places.length) {
   863       do_check_true(places[0].uri.equals(visit.referrerURI));
   865       let stmt = DBConn().createStatement(
   866         "SELECT COUNT(1) AS count " +
   867         "FROM moz_historyvisits " +
   868         "WHERE place_id = (SELECT id FROM moz_places WHERE url = :page_url) " +
   869         "AND from_visit = ( " +
   870           "SELECT id " +
   871           "FROM moz_historyvisits " +
   872           "WHERE place_id = (SELECT id FROM moz_places WHERE url = :referrer) " +
   873         ") "
   874       );
   875       stmt.params.page_url = uri.spec;
   876       stmt.params.referrer = visit.referrerURI.spec;
   877       do_check_true(stmt.executeStep());
   878       do_check_eq(stmt.row.count, 1);
   879       stmt.finalize();
   881       yield promiseAsyncUpdates();
   882     }
   883   }
   884 }
   886 function test_guid_change_saved()
   887 {
   888   // First, add a visit for it.
   889   let place = {
   890     uri: NetUtil.newURI(TEST_DOMAIN + "test_guid_change_saved"),
   891     visits: [
   892       new VisitInfo(),
   893     ],
   894   };
   895   do_check_false(yield promiseIsURIVisited(place.uri));
   897   let placesResult = yield promiseUpdatePlaces(place);
   898   if (placesResult.errors.length > 0) {
   899     do_throw("Unexpected error.");
   900   }
   901   // Then, change the guid with visits.
   902   place.guid = "_GUIDCHANGE_";
   903   place.visits = [new VisitInfo()];
   904   placesResult = yield promiseUpdatePlaces(place);
   905   if (placesResult.errors.length > 0) {
   906     do_throw("Unexpected error.");
   907   }
   908   do_check_guid_for_uri(place.uri, place.guid);
   910   yield promiseAsyncUpdates();
   911 }
   913 function test_title_change_saved()
   914 {
   915   // First, add a visit for it.
   916   let place = {
   917     uri: NetUtil.newURI(TEST_DOMAIN + "test_title_change_saved"),
   918     title: "original title",
   919     visits: [
   920       new VisitInfo(),
   921     ],
   922   };
   923   do_check_false(yield promiseIsURIVisited(place.uri));
   925   let placesResult = yield promiseUpdatePlaces(place);
   926   if (placesResult.errors.length > 0) {
   927     do_throw("Unexpected error.");
   928   }
   930   // Now, make sure the empty string clears the title.
   931   place.title = "";
   932   place.visits = [new VisitInfo()];
   933   placesResult = yield promiseUpdatePlaces(place);
   934   if (placesResult.errors.length > 0) {
   935     do_throw("Unexpected error.");
   936   }
   937   do_check_title_for_uri(place.uri, null);
   939   // Then, change the title with visits.
   940   place.title = "title change";
   941   place.visits = [new VisitInfo()];
   942   placesResult = yield promiseUpdatePlaces(place);
   943   if (placesResult.errors.length > 0) {
   944     do_throw("Unexpected error.");
   945   }
   946   do_check_title_for_uri(place.uri, place.title);
   948   // Lastly, check that the title is cleared if we set it to null.
   949   place.title = null;
   950   place.visits = [new VisitInfo()];
   951   placesResult = yield promiseUpdatePlaces(place);
   952   if (placesResult.errors.length > 0) {
   953     do_throw("Unexpected error.");
   954   }
   955   do_check_title_for_uri(place.uri, place.title);
   957   yield promiseAsyncUpdates();
   958 }
   960 function test_no_title_does_not_clear_title()
   961 {
   962   const TITLE = "test title";
   963   // First, add a visit for it.
   964   let place = {
   965     uri: NetUtil.newURI(TEST_DOMAIN + "test_no_title_does_not_clear_title"),
   966     title: TITLE,
   967     visits: [
   968       new VisitInfo(),
   969     ],
   970   };
   971   do_check_false(yield promiseIsURIVisited(place.uri));
   973   let placesResult = yield promiseUpdatePlaces(place);
   974   if (placesResult.errors.length > 0) {
   975     do_throw("Unexpected error.");
   976   }
   977   // Now, make sure that not specifying a title does not clear it.
   978   delete place.title;
   979   place.visits = [new VisitInfo()];
   980   placesResult = yield promiseUpdatePlaces(place);
   981   if (placesResult.errors.length > 0) {
   982     do_throw("Unexpected error.");
   983   }
   984   do_check_title_for_uri(place.uri, TITLE);
   986   yield promiseAsyncUpdates();
   987 }
   989 function test_title_change_notifies()
   990 {
   991   // There are three cases to test.  The first case is to make sure we do not
   992   // get notified if we do not specify a title.
   993   let place = {
   994     uri: NetUtil.newURI(TEST_DOMAIN + "test_title_change_notifies"),
   995     visits: [
   996       new VisitInfo(),
   997     ],
   998   };
   999   do_check_false(yield promiseIsURIVisited(place.uri));
  1001   let silentObserver =
  1002     new TitleChangedObserver(place.uri, "DO NOT WANT", function() {
  1003       do_throw("unexpected callback!");
  1004     });
  1006   PlacesUtils.history.addObserver(silentObserver, false);
  1007   let placesResult = yield promiseUpdatePlaces(place);
  1008   if (placesResult.errors.length > 0) {
  1009     do_throw("Unexpected error.");
  1012   // The second case to test is that we get the notification when we add
  1013   // it for the first time.  The first case will fail before our callback if it
  1014   // is busted, so we can do this now.
  1015   place.uri = NetUtil.newURI(place.uri.spec + "/new-visit-with-title");
  1016   place.title = "title 1";
  1017   function promiseTitleChangedObserver(aPlace) {
  1018     let deferred = Promise.defer();
  1019     let callbackCount = 0;
  1020     let observer = new TitleChangedObserver(aPlace.uri, aPlace.title, function() {
  1021       switch (++callbackCount) {
  1022         case 1:
  1023           // The third case to test is to make sure we get a notification when
  1024           // we change an existing place.
  1025           observer.expectedTitle = place.title = "title 2";
  1026           place.visits = [new VisitInfo()];
  1027           PlacesUtils.asyncHistory.updatePlaces(place);
  1028           break;
  1029         case 2:
  1030           PlacesUtils.history.removeObserver(silentObserver);
  1031           PlacesUtils.history.removeObserver(observer);
  1032           deferred.resolve();
  1033           break;
  1034       };
  1035     });
  1037     PlacesUtils.history.addObserver(observer, false);
  1038     PlacesUtils.asyncHistory.updatePlaces(aPlace);
  1039     return deferred.promise;
  1042   yield promiseTitleChangedObserver(place);
  1043   yield promiseAsyncUpdates();
  1046 function test_visit_notifies()
  1048   // There are two observers we need to see for each visit.  One is an
  1049   // nsINavHistoryObserver and the other is the uri-visit-saved observer topic.
  1050   let place = {
  1051     guid: "abcdefghijkl",
  1052     uri: NetUtil.newURI(TEST_DOMAIN + "test_visit_notifies"),
  1053     visits: [
  1054       new VisitInfo(),
  1055     ],
  1056   };
  1057   do_check_false(yield promiseIsURIVisited(place.uri));
  1059   function promiseVisitObserver(aPlace) {
  1060     let deferred = Promise.defer();
  1061     let callbackCount = 0;
  1062     let finisher = function() {
  1063       if (++callbackCount == 2) {
  1064         deferred.resolve();
  1067     let visitObserver = new VisitObserver(place.uri, place.guid,
  1068                                           function(aVisitDate,
  1069                                                    aTransitionType) {
  1070       let visit = place.visits[0];
  1071       do_check_eq(visit.visitDate, aVisitDate);
  1072       do_check_eq(visit.transitionType, aTransitionType);
  1074       PlacesUtils.history.removeObserver(visitObserver);
  1075       finisher();
  1076     });
  1077     PlacesUtils.history.addObserver(visitObserver, false);
  1078     let observer = function(aSubject, aTopic, aData) {
  1079       do_log_info("observe(" + aSubject + ", " + aTopic + ", " + aData + ")");
  1080       do_check_true(aSubject instanceof Ci.nsIURI);
  1081       do_check_true(aSubject.equals(place.uri));
  1083       Services.obs.removeObserver(observer, URI_VISIT_SAVED);
  1084       finisher();
  1085     };
  1086     Services.obs.addObserver(observer, URI_VISIT_SAVED, false);
  1087     PlacesUtils.asyncHistory.updatePlaces(place);
  1088     return deferred.promise;
  1091   yield promiseVisitObserver(place);
  1092   yield promiseAsyncUpdates();
  1095 // test with empty mozIVisitInfoCallback object
  1096 function test_callbacks_not_supplied()
  1098   const URLS = [
  1099     "imap://cyrus.andrew.cmu.edu/archive.imap",  // bad URI
  1100     "http://mozilla.org/" // valid URI
  1101   ];
  1102   let places = [];
  1103   URLS.forEach(function(url) {
  1104     try {
  1105       let place = {
  1106         uri: NetUtil.newURI(url),
  1107         title: "test for " + url,
  1108         visits: [
  1109           new VisitInfo(),
  1110         ],
  1111       };
  1112       places.push(place);
  1114     catch (e if e.result === Cr.NS_ERROR_FAILURE) {
  1115       // NetUtil.newURI() can throw if e.g. our app knows about imap://
  1116       // but the account is not set up and so the URL is invalid for us.
  1117       // Note this in the log but ignore as it's not the subject of this test.
  1118       do_log_info("Could not construct URI for '" + url + "'; ignoring");
  1120   });
  1122   PlacesUtils.asyncHistory.updatePlaces(places, {});
  1123   yield promiseAsyncUpdates();
  1126 ////////////////////////////////////////////////////////////////////////////////
  1127 //// Test Runner
  1130   test_interface_exists,
  1131   test_invalid_uri_throws,
  1132   test_invalid_places_throws,
  1133   test_invalid_guid_throws,
  1134   test_no_visits_throws,
  1135   test_add_visit_no_date_throws,
  1136   test_add_visit_no_transitionType_throws,
  1137   test_add_visit_invalid_transitionType_throws,
  1138   // Note: all asynchronous tests (every test below this point) should wait for
  1139   // async updates before calling run_next_test.
  1140   test_non_addable_uri_errors,
  1141   test_duplicate_guid_errors,
  1142   test_invalid_referrerURI_ignored,
  1143   test_nonnsIURI_referrerURI_ignored,
  1144   test_old_referrer_ignored,
  1145   test_place_id_ignored,
  1146   test_handleCompletion_called_when_complete,
  1147   test_add_visit,
  1148   test_properties_saved,
  1149   test_guid_saved,
  1150   test_referrer_saved,
  1151   test_guid_change_saved,
  1152   test_title_change_saved,
  1153   test_no_title_does_not_clear_title,
  1154   test_title_change_notifies,
  1155   test_visit_notifies,
  1156   test_callbacks_not_supplied,
  1157 ].forEach(add_task);
  1159 function run_test()
  1161   run_next_test();

mercurial