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

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/toolkit/components/places/tests/unit/test_async_history_api.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1162 @@
     1.4 +/* Any copyright is dedicated to the Public Domain.
     1.5 +   http://creativecommons.org/publicdomain/zero/1.0/ */
     1.6 +
     1.7 +/**
     1.8 + * This file tests the async history API exposed by mozIAsyncHistory.
     1.9 + */
    1.10 +
    1.11 +////////////////////////////////////////////////////////////////////////////////
    1.12 +//// Globals
    1.13 +
    1.14 +const TEST_DOMAIN = "http://mozilla.org/";
    1.15 +const URI_VISIT_SAVED = "uri-visit-saved";
    1.16 +const RECENT_EVENT_THRESHOLD = 15 * 60 * 1000000;
    1.17 +
    1.18 +////////////////////////////////////////////////////////////////////////////////
    1.19 +//// Helpers
    1.20 +/**
    1.21 + * Object that represents a mozIVisitInfo object.
    1.22 + *
    1.23 + * @param [optional] aTransitionType
    1.24 + *        The transition type of the visit.  Defaults to TRANSITION_LINK if not
    1.25 + *        provided.
    1.26 + * @param [optional] aVisitTime
    1.27 + *        The time of the visit.  Defaults to now if not provided.
    1.28 + */
    1.29 +function VisitInfo(aTransitionType,
    1.30 +                   aVisitTime)
    1.31 +{
    1.32 +  this.transitionType =
    1.33 +    aTransitionType === undefined ? TRANSITION_LINK : aTransitionType;
    1.34 +  this.visitDate = aVisitTime || Date.now() * 1000;
    1.35 +}
    1.36 +
    1.37 +function promiseUpdatePlaces(aPlaces) {
    1.38 +  let deferred = Promise.defer();
    1.39 +  PlacesUtils.asyncHistory.updatePlaces(aPlaces, {
    1.40 +    _errors: [],
    1.41 +    _results: [],
    1.42 +    handleError: function handleError(aResultCode, aPlace) {
    1.43 +      this._errors.push({ resultCode: aResultCode, info: aPlace});
    1.44 +    },
    1.45 +    handleResult: function handleResult(aPlace) {
    1.46 +      this._results.push(aPlace);
    1.47 +    },
    1.48 +    handleCompletion: function handleCompletion() {
    1.49 +      deferred.resolve({ errors: this._errors, results: this._results });
    1.50 +    }
    1.51 +  });
    1.52 +
    1.53 +  return deferred.promise;
    1.54 +}
    1.55 +
    1.56 +/**
    1.57 + * Listens for a title change notification, and calls aCallback when it gets it.
    1.58 + *
    1.59 + * @param aURI
    1.60 + *        The URI of the page we expect a notification for.
    1.61 + * @param aExpectedTitle
    1.62 + *        The expected title of the URI we expect a notification for.
    1.63 + * @param aCallback
    1.64 + *        The method to call when we have gotten the proper notification about
    1.65 + *        the title changing.
    1.66 + */
    1.67 +function TitleChangedObserver(aURI,
    1.68 +                              aExpectedTitle,
    1.69 +                              aCallback)
    1.70 +{
    1.71 +  this.uri = aURI;
    1.72 +  this.expectedTitle = aExpectedTitle;
    1.73 +  this.callback = aCallback;
    1.74 +}
    1.75 +TitleChangedObserver.prototype = {
    1.76 +  __proto__: NavHistoryObserver.prototype,
    1.77 +  onTitleChanged: function(aURI,
    1.78 +                           aTitle,
    1.79 +                           aGUID)
    1.80 +  {
    1.81 +    do_log_info("onTitleChanged(" + aURI.spec + ", " + aTitle + ", " + aGUID + ")");
    1.82 +    if (!this.uri.equals(aURI)) {
    1.83 +      return;
    1.84 +    }
    1.85 +    do_check_eq(aTitle, this.expectedTitle);
    1.86 +    do_check_guid_for_uri(aURI, aGUID);
    1.87 +    this.callback();
    1.88 +  },
    1.89 +};
    1.90 +
    1.91 +/**
    1.92 + * Listens for a visit notification, and calls aCallback when it gets it.
    1.93 + *
    1.94 + * @param aURI
    1.95 + *        The URI of the page we expect a notification for.
    1.96 + * @param aCallback
    1.97 + *        The method to call when we have gotten the proper notification about
    1.98 + *        being visited.
    1.99 + */
   1.100 +function VisitObserver(aURI,
   1.101 +                       aGUID,
   1.102 +                       aCallback)
   1.103 +{
   1.104 +  this.uri = aURI;
   1.105 +  this.guid = aGUID;
   1.106 +  this.callback = aCallback;
   1.107 +}
   1.108 +VisitObserver.prototype = {
   1.109 +  __proto__: NavHistoryObserver.prototype,
   1.110 +  onVisit: function(aURI,
   1.111 +                    aVisitId,
   1.112 +                    aTime,
   1.113 +                    aSessionId,
   1.114 +                    aReferringId,
   1.115 +                    aTransitionType,
   1.116 +                    aGUID)
   1.117 +  {
   1.118 +    do_log_info("onVisit(" + aURI.spec + ", " + aVisitId + ", " + aTime +
   1.119 +                ", " + aSessionId + ", " + aReferringId + ", " +
   1.120 +                aTransitionType + ", " + aGUID + ")"); 
   1.121 +    if (!this.uri.equals(aURI) || this.guid != aGUID) {
   1.122 +      return;
   1.123 +    }
   1.124 +    this.callback(aTime, aTransitionType);
   1.125 +  },
   1.126 +};
   1.127 +
   1.128 +/**
   1.129 + * Tests that a title was set properly in the database.
   1.130 + *
   1.131 + * @param aURI
   1.132 + *        The uri to check.
   1.133 + * @param aTitle
   1.134 + *        The expected title in the database.
   1.135 + */
   1.136 +function do_check_title_for_uri(aURI,
   1.137 +                                aTitle)
   1.138 +{
   1.139 +  let stack = Components.stack.caller;
   1.140 +  let stmt = DBConn().createStatement(
   1.141 +    "SELECT title " +
   1.142 +    "FROM moz_places " +
   1.143 +    "WHERE url = :url "
   1.144 +  );
   1.145 +  stmt.params.url = aURI.spec;
   1.146 +  do_check_true(stmt.executeStep(), stack);
   1.147 +  do_check_eq(stmt.row.title, aTitle, stack);
   1.148 +  stmt.finalize();
   1.149 +}
   1.150 +
   1.151 +////////////////////////////////////////////////////////////////////////////////
   1.152 +//// Test Functions
   1.153 +
   1.154 +function test_interface_exists()
   1.155 +{
   1.156 +  let history = Cc["@mozilla.org/browser/history;1"].getService(Ci.nsISupports);
   1.157 +  do_check_true(history instanceof Ci.mozIAsyncHistory);
   1.158 +}
   1.159 +
   1.160 +function test_invalid_uri_throws()
   1.161 +{
   1.162 +  // First, test passing in nothing.
   1.163 +  let place = {
   1.164 +    visits: [
   1.165 +      new VisitInfo(),
   1.166 +    ],
   1.167 +  };
   1.168 +  try {
   1.169 +    yield promiseUpdatePlaces(place);
   1.170 +    do_throw("Should have thrown!");
   1.171 +  }
   1.172 +  catch (e) {
   1.173 +    do_check_eq(e.result, Cr.NS_ERROR_INVALID_ARG);
   1.174 +  }
   1.175 +
   1.176 +  // Now, test other bogus things.
   1.177 +  const TEST_VALUES = [
   1.178 +    null,
   1.179 +    undefined,
   1.180 +    {},
   1.181 +    [],
   1.182 +    TEST_DOMAIN + "test_invalid_id_throws",
   1.183 +  ];
   1.184 +  for (let i = 0; i < TEST_VALUES.length; i++) {
   1.185 +    place.uri = TEST_VALUES[i];
   1.186 +    try {
   1.187 +      yield promiseUpdatePlaces(place);
   1.188 +      do_throw("Should have thrown!");
   1.189 +    }
   1.190 +    catch (e) {
   1.191 +      do_check_eq(e.result, Cr.NS_ERROR_INVALID_ARG);
   1.192 +    }
   1.193 +  }
   1.194 +}
   1.195 +
   1.196 +function test_invalid_places_throws()
   1.197 +{
   1.198 +  // First, test passing in nothing.
   1.199 +  try {
   1.200 +    PlacesUtils.asyncHistory.updatePlaces();
   1.201 +    do_throw("Should have thrown!");
   1.202 +  }
   1.203 +  catch (e) {
   1.204 +    do_check_eq(e.result, Cr.NS_ERROR_XPC_NOT_ENOUGH_ARGS);
   1.205 +  }
   1.206 +
   1.207 +  // Now, test other bogus things.
   1.208 +  const TEST_VALUES = [
   1.209 +    null,
   1.210 +    undefined,
   1.211 +    {},
   1.212 +    [],
   1.213 +    "",
   1.214 +  ];
   1.215 +  for (let i = 0; i < TEST_VALUES.length; i++) {
   1.216 +    let value = TEST_VALUES[i];
   1.217 +    try {
   1.218 +      yield promiseUpdatePlaces(value);
   1.219 +      do_throw("Should have thrown!");
   1.220 +    }
   1.221 +    catch (e) {
   1.222 +      do_check_eq(e.result, Cr.NS_ERROR_INVALID_ARG);
   1.223 +    }
   1.224 +  }
   1.225 +}
   1.226 +
   1.227 +function test_invalid_guid_throws()
   1.228 +{
   1.229 +  // First check invalid length guid.
   1.230 +  let place = {
   1.231 +    guid: "BAD_GUID",
   1.232 +    uri: NetUtil.newURI(TEST_DOMAIN + "test_invalid_guid_throws"),
   1.233 +    visits: [
   1.234 +      new VisitInfo(),
   1.235 +    ],
   1.236 +  };
   1.237 +  try {
   1.238 +    yield promiseUpdatePlaces(place);
   1.239 +    do_throw("Should have thrown!");
   1.240 +  }
   1.241 +  catch (e) {
   1.242 +    do_check_eq(e.result, Cr.NS_ERROR_INVALID_ARG);
   1.243 +  }
   1.244 +
   1.245 +  // Now check invalid character guid.
   1.246 +  place.guid = "__BADGUID+__";
   1.247 +  do_check_eq(place.guid.length, 12);
   1.248 +  try {
   1.249 +    yield promiseUpdatePlaces(place);
   1.250 +    do_throw("Should have thrown!");
   1.251 +  }
   1.252 +  catch (e) {
   1.253 +    do_check_eq(e.result, Cr.NS_ERROR_INVALID_ARG);
   1.254 +  }
   1.255 +}
   1.256 +
   1.257 +function test_no_visits_throws()
   1.258 +{
   1.259 +  const TEST_URI =
   1.260 +    NetUtil.newURI(TEST_DOMAIN + "test_no_id_or_guid_no_visits_throws");
   1.261 +  const TEST_GUID = "_RANDOMGUID_";
   1.262 +  const TEST_PLACEID = 2;
   1.263 +
   1.264 +  let log_test_conditions = function(aPlace) {
   1.265 +    let str = "Testing place with " +
   1.266 +      (aPlace.uri ? "uri" : "no uri") + ", " +
   1.267 +      (aPlace.guid ? "guid" : "no guid") + ", " +
   1.268 +      (aPlace.visits ? "visits array" : "no visits array");
   1.269 +    do_log_info(str);
   1.270 +  };
   1.271 +
   1.272 +  // Loop through every possible case.  Note that we don't actually care about
   1.273 +  // the case where we have no uri, place id, or guid (covered by another test),
   1.274 +  // but it is easier to just make sure it too throws than to exclude it.
   1.275 +  let place = { };
   1.276 +  for (let uri = 1; uri >= 0; uri--) {
   1.277 +    place.uri = uri ? TEST_URI : undefined;
   1.278 +
   1.279 +    for (let guid = 1; guid >= 0; guid--) {
   1.280 +      place.guid = guid ? TEST_GUID : undefined;
   1.281 +
   1.282 +      for (let visits = 1; visits >= 0; visits--) {
   1.283 +        place.visits = visits ? [] : undefined;
   1.284 +
   1.285 +        log_test_conditions(place);
   1.286 +        try {
   1.287 +          yield promiseUpdatePlaces(place);
   1.288 +          do_throw("Should have thrown!");
   1.289 +        }
   1.290 +        catch (e) {
   1.291 +          do_check_eq(e.result, Cr.NS_ERROR_INVALID_ARG);
   1.292 +        }
   1.293 +      }
   1.294 +    }
   1.295 +  }
   1.296 +}
   1.297 +
   1.298 +function test_add_visit_no_date_throws()
   1.299 +{
   1.300 +  let place = {
   1.301 +    uri: NetUtil.newURI(TEST_DOMAIN + "test_add_visit_no_date_throws"),
   1.302 +    visits: [
   1.303 +      new VisitInfo(),
   1.304 +    ],
   1.305 +  };
   1.306 +  delete place.visits[0].visitDate;
   1.307 +  try {
   1.308 +    yield promiseUpdatePlaces(place);
   1.309 +    do_throw("Should have thrown!");
   1.310 +  }
   1.311 +  catch (e) {
   1.312 +    do_check_eq(e.result, Cr.NS_ERROR_INVALID_ARG);
   1.313 +  }
   1.314 +}
   1.315 +
   1.316 +function test_add_visit_no_transitionType_throws()
   1.317 +{
   1.318 +  let place = {
   1.319 +    uri: NetUtil.newURI(TEST_DOMAIN + "test_add_visit_no_transitionType_throws"),
   1.320 +    visits: [
   1.321 +      new VisitInfo(),
   1.322 +    ],
   1.323 +  };
   1.324 +  delete place.visits[0].transitionType;
   1.325 +  try {
   1.326 +    yield promiseUpdatePlaces(place);
   1.327 +    do_throw("Should have thrown!");
   1.328 +  }
   1.329 +  catch (e) {
   1.330 +    do_check_eq(e.result, Cr.NS_ERROR_INVALID_ARG);
   1.331 +  }
   1.332 +}
   1.333 +
   1.334 +function test_add_visit_invalid_transitionType_throws()
   1.335 +{
   1.336 +  // First, test something that has a transition type lower than the first one.
   1.337 +  let place = {
   1.338 +    uri: NetUtil.newURI(TEST_DOMAIN +
   1.339 +                        "test_add_visit_invalid_transitionType_throws"),
   1.340 +    visits: [
   1.341 +      new VisitInfo(TRANSITION_LINK - 1),
   1.342 +    ],
   1.343 +  };
   1.344 +  try {
   1.345 +    yield promiseUpdatePlaces(place);
   1.346 +    do_throw("Should have thrown!");
   1.347 +  }
   1.348 +  catch (e) {
   1.349 +    do_check_eq(e.result, Cr.NS_ERROR_INVALID_ARG);
   1.350 +  }
   1.351 +
   1.352 +  // Now, test something that has a transition type greater than the last one.
   1.353 +  place.visits[0] = new VisitInfo(TRANSITION_FRAMED_LINK + 1);
   1.354 +  try {
   1.355 +    yield promiseUpdatePlaces(place);
   1.356 +    do_throw("Should have thrown!");
   1.357 +  }
   1.358 +  catch (e) {
   1.359 +    do_check_eq(e.result, Cr.NS_ERROR_INVALID_ARG);
   1.360 +  }
   1.361 +}
   1.362 +
   1.363 +function test_non_addable_uri_errors()
   1.364 +{
   1.365 +  // Array of protocols that nsINavHistoryService::canAddURI returns false for.
   1.366 +  const URLS = [
   1.367 +    "about:config",
   1.368 +    "imap://cyrus.andrew.cmu.edu/archive.imap",
   1.369 +    "news://new.mozilla.org/mozilla.dev.apps.firefox",
   1.370 +    "mailbox:Inbox",
   1.371 +    "moz-anno:favicon:http://mozilla.org/made-up-favicon",
   1.372 +    "view-source:http://mozilla.org",
   1.373 +    "chrome://browser/content/browser.xul",
   1.374 +    "resource://gre-resources/hiddenWindow.html",
   1.375 +    "data:,Hello%2C%20World!",
   1.376 +    "wyciwyg:/0/http://mozilla.org",
   1.377 +    "javascript:alert('hello wolrd!');",
   1.378 +    "blob:foo",
   1.379 +  ];
   1.380 +  let places = [];
   1.381 +  URLS.forEach(function(url) {
   1.382 +    try {
   1.383 +      let place = {
   1.384 +        uri: NetUtil.newURI(url),
   1.385 +        title: "test for " + url,
   1.386 +        visits: [
   1.387 +          new VisitInfo(),
   1.388 +        ],
   1.389 +      };
   1.390 +      places.push(place);
   1.391 +    }
   1.392 +    catch (e if e.result === Cr.NS_ERROR_FAILURE) {
   1.393 +      // NetUtil.newURI() can throw if e.g. our app knows about imap://
   1.394 +      // but the account is not set up and so the URL is invalid for us.
   1.395 +      // Note this in the log but ignore as it's not the subject of this test.
   1.396 +      do_log_info("Could not construct URI for '" + url + "'; ignoring");
   1.397 +    }
   1.398 +  });
   1.399 +
   1.400 +  let placesResult = yield promiseUpdatePlaces(places);
   1.401 +  if (placesResult.results.length > 0) {
   1.402 +    do_throw("Unexpected success.");
   1.403 +  }
   1.404 +  for (let place of placesResult.errors) {
   1.405 +    do_log_info("Checking '" + place.info.uri.spec + "'");
   1.406 +    do_check_eq(place.resultCode, Cr.NS_ERROR_INVALID_ARG);
   1.407 +    do_check_false(yield promiseIsURIVisited(place.info.uri));
   1.408 +  }
   1.409 +  yield promiseAsyncUpdates();
   1.410 +}
   1.411 +
   1.412 +function test_duplicate_guid_errors()
   1.413 +{
   1.414 +  // This test ensures that trying to add a visit, with a guid already found in
   1.415 +  // another visit, fails.
   1.416 +  let place = {
   1.417 +    uri: NetUtil.newURI(TEST_DOMAIN + "test_duplicate_guid_fails_first"),
   1.418 +    visits: [
   1.419 +      new VisitInfo(),
   1.420 +    ],
   1.421 +  };
   1.422 +
   1.423 +  do_check_false(yield promiseIsURIVisited(place.uri));
   1.424 +  let placesResult = yield promiseUpdatePlaces(place);
   1.425 +  if (placesResult.errors.length > 0) {
   1.426 +    do_throw("Unexpected error.");
   1.427 +  }
   1.428 +  let placeInfo = placesResult.results[0];
   1.429 +  do_check_true(yield promiseIsURIVisited(placeInfo.uri));
   1.430 +
   1.431 +  let badPlace = {
   1.432 +    uri: NetUtil.newURI(TEST_DOMAIN + "test_duplicate_guid_fails_second"),
   1.433 +    visits: [
   1.434 +      new VisitInfo(),
   1.435 +    ],
   1.436 +    guid: placeInfo.guid,
   1.437 +  };
   1.438 +
   1.439 +  do_check_false(yield promiseIsURIVisited(badPlace.uri));
   1.440 +  placesResult = yield promiseUpdatePlaces(badPlace);
   1.441 +  if (placesResult.results.length > 0) {
   1.442 +    do_throw("Unexpected success.");
   1.443 +  }
   1.444 +  let badPlaceInfo = placesResult.errors[0];
   1.445 +  do_check_eq(badPlaceInfo.resultCode, Cr.NS_ERROR_STORAGE_CONSTRAINT);
   1.446 +  do_check_false(yield promiseIsURIVisited(badPlaceInfo.info.uri));
   1.447 +
   1.448 +  yield promiseAsyncUpdates();
   1.449 +}
   1.450 +
   1.451 +function test_invalid_referrerURI_ignored()
   1.452 +{
   1.453 +  let place = {
   1.454 +    uri: NetUtil.newURI(TEST_DOMAIN +
   1.455 +                        "test_invalid_referrerURI_ignored"),
   1.456 +    visits: [
   1.457 +      new VisitInfo(),
   1.458 +    ],
   1.459 +  };
   1.460 +  place.visits[0].referrerURI = NetUtil.newURI(place.uri.spec + "_unvisistedURI");
   1.461 +  do_check_false(yield promiseIsURIVisited(place.uri));
   1.462 +  do_check_false(yield promiseIsURIVisited(place.visits[0].referrerURI));
   1.463 +
   1.464 +  let placesResult = yield promiseUpdatePlaces(place);
   1.465 +  if (placesResult.errors.length > 0) {
   1.466 +    do_throw("Unexpected error.");
   1.467 +  }
   1.468 +  let placeInfo = placesResult.results[0];
   1.469 +  do_check_true(yield promiseIsURIVisited(placeInfo.uri));
   1.470 +
   1.471 +  // Check to make sure we do not visit the invalid referrer.
   1.472 +  do_check_false(yield promiseIsURIVisited(place.visits[0].referrerURI));
   1.473 +
   1.474 +  // Check to make sure from_visit is zero in database.
   1.475 +  let stmt = DBConn().createStatement(
   1.476 +    "SELECT from_visit " +
   1.477 +    "FROM moz_historyvisits " +
   1.478 +    "WHERE id = :visit_id"
   1.479 +  );
   1.480 +  stmt.params.visit_id = placeInfo.visits[0].visitId;
   1.481 +  do_check_true(stmt.executeStep());
   1.482 +  do_check_eq(stmt.row.from_visit, 0);
   1.483 +  stmt.finalize();
   1.484 +
   1.485 +  yield promiseAsyncUpdates();
   1.486 +}
   1.487 +
   1.488 +function test_nonnsIURI_referrerURI_ignored()
   1.489 +{
   1.490 +  let place = {
   1.491 +    uri: NetUtil.newURI(TEST_DOMAIN +
   1.492 +                        "test_nonnsIURI_referrerURI_ignored"),
   1.493 +    visits: [
   1.494 +      new VisitInfo(),
   1.495 +    ],
   1.496 +  };
   1.497 +  place.visits[0].referrerURI = place.uri.spec + "_nonnsIURI";
   1.498 +  do_check_false(yield promiseIsURIVisited(place.uri));
   1.499 +
   1.500 +  let placesResult = yield promiseUpdatePlaces(place);
   1.501 +  if (placesResult.errors.length > 0) {
   1.502 +    do_throw("Unexpected error.");
   1.503 +  }
   1.504 +  let placeInfo = placesResult.results[0];
   1.505 +  do_check_true(yield promiseIsURIVisited(placeInfo.uri));
   1.506 +
   1.507 +  // Check to make sure from_visit is zero in database.
   1.508 +  let stmt = DBConn().createStatement(
   1.509 +    "SELECT from_visit " +
   1.510 +    "FROM moz_historyvisits " +
   1.511 +    "WHERE id = :visit_id"
   1.512 +  );
   1.513 +  stmt.params.visit_id = placeInfo.visits[0].visitId;
   1.514 +  do_check_true(stmt.executeStep());
   1.515 +  do_check_eq(stmt.row.from_visit, 0);
   1.516 +  stmt.finalize();
   1.517 +
   1.518 +  yield promiseAsyncUpdates();
   1.519 +}
   1.520 +
   1.521 +function test_old_referrer_ignored()
   1.522 +{
   1.523 +  // This tests that a referrer for a visit which is not recent (specifically,
   1.524 +  // older than 15 minutes as per RECENT_EVENT_THRESHOLD) is not saved by
   1.525 +  // updatePlaces.
   1.526 +  let oldTime = (Date.now() * 1000) - (RECENT_EVENT_THRESHOLD + 1);
   1.527 +  let referrerPlace = {
   1.528 +    uri: NetUtil.newURI(TEST_DOMAIN + "test_old_referrer_ignored_referrer"),
   1.529 +    visits: [
   1.530 +      new VisitInfo(TRANSITION_LINK, oldTime),
   1.531 +    ],
   1.532 +  };
   1.533 +
   1.534 +  // First we must add our referrer to the history so that it is not ignored
   1.535 +  // as being invalid.
   1.536 +  do_check_false(yield promiseIsURIVisited(referrerPlace.uri));
   1.537 +  let placesResult = yield promiseUpdatePlaces(referrerPlace);
   1.538 +  if (placesResult.errors.length > 0) {
   1.539 +    do_throw("Unexpected error.");
   1.540 +  }
   1.541 +
   1.542 +  // Now that the referrer is added, we can add a page with a valid
   1.543 +  // referrer to determine if the recency of the referrer is taken into
   1.544 +  // account.
   1.545 +  do_check_true(yield promiseIsURIVisited(referrerPlace.uri));
   1.546 +
   1.547 +  let visitInfo = new VisitInfo();
   1.548 +  visitInfo.referrerURI = referrerPlace.uri;
   1.549 +  let place = {
   1.550 +    uri: NetUtil.newURI(TEST_DOMAIN + "test_old_referrer_ignored_page"),
   1.551 +    visits: [
   1.552 +      visitInfo,
   1.553 +    ],
   1.554 +  };
   1.555 +
   1.556 +  do_check_false(yield promiseIsURIVisited(place.uri));
   1.557 +  placesResult = yield promiseUpdatePlaces(place);
   1.558 +  if (placesResult.errors.length > 0) {
   1.559 +    do_throw("Unexpected error.");
   1.560 +  }
   1.561 +  let placeInfo = placesResult.results[0];
   1.562 +  do_check_true(yield promiseIsURIVisited(place.uri));
   1.563 +
   1.564 +  // Though the visit will not contain the referrer, we must examine the
   1.565 +  // database to be sure.
   1.566 +  do_check_eq(placeInfo.visits[0].referrerURI, null);
   1.567 +  let stmt = DBConn().createStatement(
   1.568 +    "SELECT COUNT(1) AS count " +
   1.569 +    "FROM moz_historyvisits " +
   1.570 +    "WHERE place_id = (SELECT id FROM moz_places WHERE url = :page_url) " +
   1.571 +    "AND from_visit = 0 "
   1.572 +  );
   1.573 +  stmt.params.page_url = place.uri.spec;
   1.574 +  do_check_true(stmt.executeStep());
   1.575 +  do_check_eq(stmt.row.count, 1);
   1.576 +  stmt.finalize();
   1.577 +
   1.578 +  yield promiseAsyncUpdates();
   1.579 +}
   1.580 +
   1.581 +function test_place_id_ignored()
   1.582 +{
   1.583 +  let place = {
   1.584 +    uri: NetUtil.newURI(TEST_DOMAIN + "test_place_id_ignored_first"),
   1.585 +    visits: [
   1.586 +      new VisitInfo(),
   1.587 +    ],
   1.588 +  };
   1.589 +
   1.590 +  do_check_false(yield promiseIsURIVisited(place.uri));
   1.591 +  let placesResult = yield promiseUpdatePlaces(place);
   1.592 +  if (placesResult.errors.length > 0) {
   1.593 +    do_throw("Unexpected error.");
   1.594 +  }
   1.595 +  let placeInfo = placesResult.results[0];
   1.596 +  do_check_true(yield promiseIsURIVisited(place.uri));
   1.597 +
   1.598 +  let placeId = placeInfo.placeId;
   1.599 +  do_check_neq(placeId, 0);
   1.600 +
   1.601 +  let badPlace = {
   1.602 +    uri: NetUtil.newURI(TEST_DOMAIN + "test_place_id_ignored_second"),
   1.603 +    visits: [
   1.604 +      new VisitInfo(),
   1.605 +    ],
   1.606 +    placeId: placeId,
   1.607 +  };
   1.608 +
   1.609 +  do_check_false(yield promiseIsURIVisited(badPlace.uri));
   1.610 +  placesResult = yield promiseUpdatePlaces(badPlace);
   1.611 +  if (placesResult.errors.length > 0) {
   1.612 +    do_throw("Unexpected error.");
   1.613 +  }
   1.614 +  placeInfo = placesResult.results[0];
   1.615 +
   1.616 +  do_check_neq(placeInfo.placeId, placeId);
   1.617 +  do_check_true(yield promiseIsURIVisited(badPlace.uri));
   1.618 +
   1.619 +  yield promiseAsyncUpdates();
   1.620 +}
   1.621 +
   1.622 +function test_handleCompletion_called_when_complete()
   1.623 +{
   1.624 +  // We test a normal visit, and embeded visit, and a uri that would fail
   1.625 +  // the canAddURI test to make sure that the notification happens after *all*
   1.626 +  // of them have had a callback.
   1.627 +  let places = [
   1.628 +    { uri: NetUtil.newURI(TEST_DOMAIN +
   1.629 +                          "test_handleCompletion_called_when_complete"),
   1.630 +      visits: [
   1.631 +        new VisitInfo(),
   1.632 +        new VisitInfo(TRANSITION_EMBED),
   1.633 +      ],
   1.634 +    },
   1.635 +    { uri: NetUtil.newURI("data:,Hello%2C%20World!"),
   1.636 +      visits: [
   1.637 +        new VisitInfo(),
   1.638 +      ],
   1.639 +    },
   1.640 +  ];
   1.641 +  do_check_false(yield promiseIsURIVisited(places[0].uri));
   1.642 +  do_check_false(yield promiseIsURIVisited(places[1].uri));
   1.643 +
   1.644 +  const EXPECTED_COUNT_SUCCESS = 2;
   1.645 +  const EXPECTED_COUNT_FAILURE = 1;
   1.646 +  let callbackCountSuccess = 0;
   1.647 +  let callbackCountFailure = 0;
   1.648 +
   1.649 +  let placesResult = yield promiseUpdatePlaces(places);
   1.650 +  for (let place of placesResult.results) {
   1.651 +    let checker = PlacesUtils.history.canAddURI(place.uri) ?
   1.652 +      do_check_true : do_check_false;
   1.653 +    callbackCountSuccess++;
   1.654 +  }
   1.655 +  for (let error of placesResult.errors) {
   1.656 +    callbackCountFailure++;
   1.657 +  }
   1.658 +
   1.659 +  do_check_eq(callbackCountSuccess, EXPECTED_COUNT_SUCCESS);
   1.660 +  do_check_eq(callbackCountFailure, EXPECTED_COUNT_FAILURE);
   1.661 +  yield promiseAsyncUpdates();
   1.662 +}
   1.663 +
   1.664 +function test_add_visit()
   1.665 +{
   1.666 +  const VISIT_TIME = Date.now() * 1000;
   1.667 +  let place = {
   1.668 +    uri: NetUtil.newURI(TEST_DOMAIN + "test_add_visit"),
   1.669 +    title: "test_add_visit title",
   1.670 +    visits: [],
   1.671 +  };
   1.672 +  for (let transitionType = TRANSITION_LINK;
   1.673 +       transitionType <= TRANSITION_FRAMED_LINK;
   1.674 +       transitionType++) {
   1.675 +    place.visits.push(new VisitInfo(transitionType, VISIT_TIME));
   1.676 +  }
   1.677 +  do_check_false(yield promiseIsURIVisited(place.uri));
   1.678 +
   1.679 +  let callbackCount = 0;
   1.680 +  let placesResult = yield promiseUpdatePlaces(place);
   1.681 +  if (placesResult.errors.length > 0) {
   1.682 +    do_throw("Unexpected error.");
   1.683 +  }
   1.684 +  for (let placeInfo of placesResult.results) {
   1.685 +    do_check_true(yield promiseIsURIVisited(place.uri));
   1.686 +
   1.687 +    // Check mozIPlaceInfo properties.
   1.688 +    do_check_true(place.uri.equals(placeInfo.uri));
   1.689 +    do_check_eq(placeInfo.frecency, -1); // We don't pass frecency here!
   1.690 +    do_check_eq(placeInfo.title, place.title);
   1.691 +
   1.692 +    // Check mozIVisitInfo properties.
   1.693 +    let visits = placeInfo.visits;
   1.694 +    do_check_eq(visits.length, 1);
   1.695 +    let visit = visits[0];
   1.696 +    do_check_eq(visit.visitDate, VISIT_TIME);
   1.697 +    do_check_true(visit.transitionType >= TRANSITION_LINK &&
   1.698 +                    visit.transitionType <= TRANSITION_FRAMED_LINK);
   1.699 +    do_check_true(visit.referrerURI === null);
   1.700 +
   1.701 +    // For TRANSITION_EMBED visits, many properties will always be zero or
   1.702 +    // undefined.
   1.703 +    if (visit.transitionType == TRANSITION_EMBED) {
   1.704 +      // Check mozIPlaceInfo properties.
   1.705 +      do_check_eq(placeInfo.placeId, 0, '//');
   1.706 +      do_check_eq(placeInfo.guid, null);
   1.707 +
   1.708 +      // Check mozIVisitInfo properties.
   1.709 +      do_check_eq(visit.visitId, 0);
   1.710 +    }
   1.711 +    // But they should be valid for non-embed visits.
   1.712 +    else {
   1.713 +      // Check mozIPlaceInfo properties.
   1.714 +      do_check_true(placeInfo.placeId > 0);
   1.715 +      do_check_valid_places_guid(placeInfo.guid);
   1.716 +
   1.717 +      // Check mozIVisitInfo properties.
   1.718 +      do_check_true(visit.visitId > 0);
   1.719 +    }
   1.720 +
   1.721 +    // If we have had all of our callbacks, continue running tests.
   1.722 +    if (++callbackCount == place.visits.length) {
   1.723 +      yield promiseAsyncUpdates();
   1.724 +    }
   1.725 +  }
   1.726 +}
   1.727 +
   1.728 +function test_properties_saved()
   1.729 +{
   1.730 +  // Check each transition type to make sure it is saved properly.
   1.731 +  let places = [];
   1.732 +  for (let transitionType = TRANSITION_LINK;
   1.733 +       transitionType <= TRANSITION_FRAMED_LINK;
   1.734 +       transitionType++) {
   1.735 +    let place = {
   1.736 +      uri: NetUtil.newURI(TEST_DOMAIN + "test_properties_saved/" +
   1.737 +                          transitionType),
   1.738 +      title: "test_properties_saved test",
   1.739 +      visits: [
   1.740 +        new VisitInfo(transitionType),
   1.741 +      ],
   1.742 +    };
   1.743 +    do_check_false(yield promiseIsURIVisited(place.uri));
   1.744 +    places.push(place);
   1.745 +  }
   1.746 +
   1.747 +  let callbackCount = 0;
   1.748 +  let placesResult = yield promiseUpdatePlaces(places);
   1.749 +  if (placesResult.errors.length > 0) {
   1.750 +    do_throw("Unexpected error.");
   1.751 +  }
   1.752 +  for (let placeInfo of placesResult.results) {
   1.753 +    let uri = placeInfo.uri;
   1.754 +    do_check_true(yield promiseIsURIVisited(uri));
   1.755 +    let visit = placeInfo.visits[0];
   1.756 +    print("TEST-INFO | test_properties_saved | updatePlaces callback for " +
   1.757 +          "transition type " + visit.transitionType);
   1.758 +
   1.759 +    // Note that TRANSITION_EMBED should not be in the database.
   1.760 +    const EXPECTED_COUNT = visit.transitionType == TRANSITION_EMBED ? 0 : 1;
   1.761 +
   1.762 +    // mozIVisitInfo::date
   1.763 +    let stmt = DBConn().createStatement(
   1.764 +      "SELECT COUNT(1) AS count " +
   1.765 +      "FROM moz_places h " +
   1.766 +      "JOIN moz_historyvisits v " +
   1.767 +      "ON h.id = v.place_id " +
   1.768 +      "WHERE h.url = :page_url " +
   1.769 +      "AND v.visit_date = :visit_date "
   1.770 +    );
   1.771 +    stmt.params.page_url = uri.spec;
   1.772 +    stmt.params.visit_date = visit.visitDate;
   1.773 +    do_check_true(stmt.executeStep());
   1.774 +    do_check_eq(stmt.row.count, EXPECTED_COUNT);
   1.775 +    stmt.finalize();
   1.776 +
   1.777 +    // mozIVisitInfo::transitionType
   1.778 +    stmt = DBConn().createStatement(
   1.779 +      "SELECT COUNT(1) AS count " +
   1.780 +      "FROM moz_places h " +
   1.781 +      "JOIN moz_historyvisits v " +
   1.782 +      "ON h.id = v.place_id " +
   1.783 +      "WHERE h.url = :page_url " +
   1.784 +      "AND v.visit_type = :transition_type "
   1.785 +    );
   1.786 +    stmt.params.page_url = uri.spec;
   1.787 +    stmt.params.transition_type = visit.transitionType;
   1.788 +    do_check_true(stmt.executeStep());
   1.789 +    do_check_eq(stmt.row.count, EXPECTED_COUNT);
   1.790 +    stmt.finalize();
   1.791 +
   1.792 +    // mozIPlaceInfo::title
   1.793 +    stmt = DBConn().createStatement(
   1.794 +      "SELECT COUNT(1) AS count " +
   1.795 +      "FROM moz_places h " +
   1.796 +      "WHERE h.url = :page_url " +
   1.797 +      "AND h.title = :title "
   1.798 +    );
   1.799 +    stmt.params.page_url = uri.spec;
   1.800 +    stmt.params.title = placeInfo.title;
   1.801 +    do_check_true(stmt.executeStep());
   1.802 +    do_check_eq(stmt.row.count, EXPECTED_COUNT);
   1.803 +    stmt.finalize();
   1.804 +
   1.805 +    // If we have had all of our callbacks, continue running tests.
   1.806 +    if (++callbackCount == places.length) {
   1.807 +      yield promiseAsyncUpdates();
   1.808 +    }
   1.809 +  }
   1.810 +}
   1.811 +
   1.812 +function test_guid_saved()
   1.813 +{
   1.814 +  let place = {
   1.815 +    uri: NetUtil.newURI(TEST_DOMAIN + "test_guid_saved"),
   1.816 +    guid: "__TESTGUID__",
   1.817 +    visits: [
   1.818 +      new VisitInfo(),
   1.819 +    ],
   1.820 +  };
   1.821 +  do_check_valid_places_guid(place.guid);
   1.822 +  do_check_false(yield promiseIsURIVisited(place.uri));
   1.823 +
   1.824 +  let placesResult = yield promiseUpdatePlaces(place);
   1.825 +  if (placesResult.errors.length > 0) {
   1.826 +    do_throw("Unexpected error.");
   1.827 +  }
   1.828 +  let placeInfo = placesResult.results[0];
   1.829 +  let uri = placeInfo.uri;
   1.830 +  do_check_true(yield promiseIsURIVisited(uri));
   1.831 +  do_check_eq(placeInfo.guid, place.guid);
   1.832 +  do_check_guid_for_uri(uri, place.guid);
   1.833 +  yield promiseAsyncUpdates();
   1.834 +}
   1.835 +
   1.836 +function test_referrer_saved()
   1.837 +{
   1.838 +  let places = [
   1.839 +    { uri: NetUtil.newURI(TEST_DOMAIN + "test_referrer_saved/referrer"),
   1.840 +      visits: [
   1.841 +        new VisitInfo(),
   1.842 +      ],
   1.843 +    },
   1.844 +    { uri: NetUtil.newURI(TEST_DOMAIN + "test_referrer_saved/test"),
   1.845 +      visits: [
   1.846 +        new VisitInfo(),
   1.847 +      ],
   1.848 +    },
   1.849 +  ];
   1.850 +  places[1].visits[0].referrerURI = places[0].uri;
   1.851 +  do_check_false(yield promiseIsURIVisited(places[0].uri));
   1.852 +  do_check_false(yield promiseIsURIVisited(places[1].uri));
   1.853 +
   1.854 +  let resultCount = 0;
   1.855 +  let placesResult = yield promiseUpdatePlaces(places);
   1.856 +  if (placesResult.errors.length > 0) {
   1.857 +    do_throw("Unexpected error.");
   1.858 +  }
   1.859 +  for (let placeInfo of placesResult.results) {
   1.860 +    let uri = placeInfo.uri;
   1.861 +    do_check_true(yield promiseIsURIVisited(uri));
   1.862 +    let visit = placeInfo.visits[0];
   1.863 +
   1.864 +    // We need to insert all of our visits before we can test conditions.
   1.865 +    if (++resultCount == places.length) {
   1.866 +      do_check_true(places[0].uri.equals(visit.referrerURI));
   1.867 +
   1.868 +      let stmt = DBConn().createStatement(
   1.869 +        "SELECT COUNT(1) AS count " +
   1.870 +        "FROM moz_historyvisits " +
   1.871 +        "WHERE place_id = (SELECT id FROM moz_places WHERE url = :page_url) " +
   1.872 +        "AND from_visit = ( " +
   1.873 +          "SELECT id " +
   1.874 +          "FROM moz_historyvisits " +
   1.875 +          "WHERE place_id = (SELECT id FROM moz_places WHERE url = :referrer) " +
   1.876 +        ") "
   1.877 +      );
   1.878 +      stmt.params.page_url = uri.spec;
   1.879 +      stmt.params.referrer = visit.referrerURI.spec;
   1.880 +      do_check_true(stmt.executeStep());
   1.881 +      do_check_eq(stmt.row.count, 1);
   1.882 +      stmt.finalize();
   1.883 +
   1.884 +      yield promiseAsyncUpdates();
   1.885 +    }
   1.886 +  }
   1.887 +}
   1.888 +
   1.889 +function test_guid_change_saved()
   1.890 +{
   1.891 +  // First, add a visit for it.
   1.892 +  let place = {
   1.893 +    uri: NetUtil.newURI(TEST_DOMAIN + "test_guid_change_saved"),
   1.894 +    visits: [
   1.895 +      new VisitInfo(),
   1.896 +    ],
   1.897 +  };
   1.898 +  do_check_false(yield promiseIsURIVisited(place.uri));
   1.899 +
   1.900 +  let placesResult = yield promiseUpdatePlaces(place);
   1.901 +  if (placesResult.errors.length > 0) {
   1.902 +    do_throw("Unexpected error.");
   1.903 +  }
   1.904 +  // Then, change the guid with visits.
   1.905 +  place.guid = "_GUIDCHANGE_";
   1.906 +  place.visits = [new VisitInfo()];
   1.907 +  placesResult = yield promiseUpdatePlaces(place);
   1.908 +  if (placesResult.errors.length > 0) {
   1.909 +    do_throw("Unexpected error.");
   1.910 +  }
   1.911 +  do_check_guid_for_uri(place.uri, place.guid);
   1.912 +
   1.913 +  yield promiseAsyncUpdates();
   1.914 +}
   1.915 +
   1.916 +function test_title_change_saved()
   1.917 +{
   1.918 +  // First, add a visit for it.
   1.919 +  let place = {
   1.920 +    uri: NetUtil.newURI(TEST_DOMAIN + "test_title_change_saved"),
   1.921 +    title: "original title",
   1.922 +    visits: [
   1.923 +      new VisitInfo(),
   1.924 +    ],
   1.925 +  };
   1.926 +  do_check_false(yield promiseIsURIVisited(place.uri));
   1.927 +
   1.928 +  let placesResult = yield promiseUpdatePlaces(place);
   1.929 +  if (placesResult.errors.length > 0) {
   1.930 +    do_throw("Unexpected error.");
   1.931 +  }
   1.932 +
   1.933 +  // Now, make sure the empty string clears the title.
   1.934 +  place.title = "";
   1.935 +  place.visits = [new VisitInfo()];
   1.936 +  placesResult = yield promiseUpdatePlaces(place);
   1.937 +  if (placesResult.errors.length > 0) {
   1.938 +    do_throw("Unexpected error.");
   1.939 +  }
   1.940 +  do_check_title_for_uri(place.uri, null);
   1.941 +
   1.942 +  // Then, change the title with visits.
   1.943 +  place.title = "title change";
   1.944 +  place.visits = [new VisitInfo()];
   1.945 +  placesResult = yield promiseUpdatePlaces(place);
   1.946 +  if (placesResult.errors.length > 0) {
   1.947 +    do_throw("Unexpected error.");
   1.948 +  }
   1.949 +  do_check_title_for_uri(place.uri, place.title);
   1.950 +
   1.951 +  // Lastly, check that the title is cleared if we set it to null.
   1.952 +  place.title = null;
   1.953 +  place.visits = [new VisitInfo()];
   1.954 +  placesResult = yield promiseUpdatePlaces(place);
   1.955 +  if (placesResult.errors.length > 0) {
   1.956 +    do_throw("Unexpected error.");
   1.957 +  }
   1.958 +  do_check_title_for_uri(place.uri, place.title);
   1.959 +
   1.960 +  yield promiseAsyncUpdates();
   1.961 +}
   1.962 +
   1.963 +function test_no_title_does_not_clear_title()
   1.964 +{
   1.965 +  const TITLE = "test title";
   1.966 +  // First, add a visit for it.
   1.967 +  let place = {
   1.968 +    uri: NetUtil.newURI(TEST_DOMAIN + "test_no_title_does_not_clear_title"),
   1.969 +    title: TITLE,
   1.970 +    visits: [
   1.971 +      new VisitInfo(),
   1.972 +    ],
   1.973 +  };
   1.974 +  do_check_false(yield promiseIsURIVisited(place.uri));
   1.975 +
   1.976 +  let placesResult = yield promiseUpdatePlaces(place);
   1.977 +  if (placesResult.errors.length > 0) {
   1.978 +    do_throw("Unexpected error.");
   1.979 +  }
   1.980 +  // Now, make sure that not specifying a title does not clear it.
   1.981 +  delete place.title;
   1.982 +  place.visits = [new VisitInfo()];
   1.983 +  placesResult = yield promiseUpdatePlaces(place);
   1.984 +  if (placesResult.errors.length > 0) {
   1.985 +    do_throw("Unexpected error.");
   1.986 +  }
   1.987 +  do_check_title_for_uri(place.uri, TITLE);
   1.988 +
   1.989 +  yield promiseAsyncUpdates();
   1.990 +}
   1.991 +
   1.992 +function test_title_change_notifies()
   1.993 +{
   1.994 +  // There are three cases to test.  The first case is to make sure we do not
   1.995 +  // get notified if we do not specify a title.
   1.996 +  let place = {
   1.997 +    uri: NetUtil.newURI(TEST_DOMAIN + "test_title_change_notifies"),
   1.998 +    visits: [
   1.999 +      new VisitInfo(),
  1.1000 +    ],
  1.1001 +  };
  1.1002 +  do_check_false(yield promiseIsURIVisited(place.uri));
  1.1003 +
  1.1004 +  let silentObserver =
  1.1005 +    new TitleChangedObserver(place.uri, "DO NOT WANT", function() {
  1.1006 +      do_throw("unexpected callback!");
  1.1007 +    });
  1.1008 +
  1.1009 +  PlacesUtils.history.addObserver(silentObserver, false);
  1.1010 +  let placesResult = yield promiseUpdatePlaces(place);
  1.1011 +  if (placesResult.errors.length > 0) {
  1.1012 +    do_throw("Unexpected error.");
  1.1013 +  }
  1.1014 +
  1.1015 +  // The second case to test is that we get the notification when we add
  1.1016 +  // it for the first time.  The first case will fail before our callback if it
  1.1017 +  // is busted, so we can do this now.
  1.1018 +  place.uri = NetUtil.newURI(place.uri.spec + "/new-visit-with-title");
  1.1019 +  place.title = "title 1";
  1.1020 +  function promiseTitleChangedObserver(aPlace) {
  1.1021 +    let deferred = Promise.defer();
  1.1022 +    let callbackCount = 0;
  1.1023 +    let observer = new TitleChangedObserver(aPlace.uri, aPlace.title, function() {
  1.1024 +      switch (++callbackCount) {
  1.1025 +        case 1:
  1.1026 +          // The third case to test is to make sure we get a notification when
  1.1027 +          // we change an existing place.
  1.1028 +          observer.expectedTitle = place.title = "title 2";
  1.1029 +          place.visits = [new VisitInfo()];
  1.1030 +          PlacesUtils.asyncHistory.updatePlaces(place);
  1.1031 +          break;
  1.1032 +        case 2:
  1.1033 +          PlacesUtils.history.removeObserver(silentObserver);
  1.1034 +          PlacesUtils.history.removeObserver(observer);
  1.1035 +          deferred.resolve();
  1.1036 +          break;
  1.1037 +      };
  1.1038 +    });
  1.1039 +
  1.1040 +    PlacesUtils.history.addObserver(observer, false);
  1.1041 +    PlacesUtils.asyncHistory.updatePlaces(aPlace);
  1.1042 +    return deferred.promise;
  1.1043 +  }
  1.1044 +
  1.1045 +  yield promiseTitleChangedObserver(place);
  1.1046 +  yield promiseAsyncUpdates();
  1.1047 +}
  1.1048 +
  1.1049 +function test_visit_notifies()
  1.1050 +{
  1.1051 +  // There are two observers we need to see for each visit.  One is an
  1.1052 +  // nsINavHistoryObserver and the other is the uri-visit-saved observer topic.
  1.1053 +  let place = {
  1.1054 +    guid: "abcdefghijkl",
  1.1055 +    uri: NetUtil.newURI(TEST_DOMAIN + "test_visit_notifies"),
  1.1056 +    visits: [
  1.1057 +      new VisitInfo(),
  1.1058 +    ],
  1.1059 +  };
  1.1060 +  do_check_false(yield promiseIsURIVisited(place.uri));
  1.1061 +
  1.1062 +  function promiseVisitObserver(aPlace) {
  1.1063 +    let deferred = Promise.defer();
  1.1064 +    let callbackCount = 0;
  1.1065 +    let finisher = function() {
  1.1066 +      if (++callbackCount == 2) {
  1.1067 +        deferred.resolve();
  1.1068 +      }
  1.1069 +    }
  1.1070 +    let visitObserver = new VisitObserver(place.uri, place.guid,
  1.1071 +                                          function(aVisitDate,
  1.1072 +                                                   aTransitionType) {
  1.1073 +      let visit = place.visits[0];
  1.1074 +      do_check_eq(visit.visitDate, aVisitDate);
  1.1075 +      do_check_eq(visit.transitionType, aTransitionType);
  1.1076 +
  1.1077 +      PlacesUtils.history.removeObserver(visitObserver);
  1.1078 +      finisher();
  1.1079 +    });
  1.1080 +    PlacesUtils.history.addObserver(visitObserver, false);
  1.1081 +    let observer = function(aSubject, aTopic, aData) {
  1.1082 +      do_log_info("observe(" + aSubject + ", " + aTopic + ", " + aData + ")");
  1.1083 +      do_check_true(aSubject instanceof Ci.nsIURI);
  1.1084 +      do_check_true(aSubject.equals(place.uri));
  1.1085 +
  1.1086 +      Services.obs.removeObserver(observer, URI_VISIT_SAVED);
  1.1087 +      finisher();
  1.1088 +    };
  1.1089 +    Services.obs.addObserver(observer, URI_VISIT_SAVED, false);
  1.1090 +    PlacesUtils.asyncHistory.updatePlaces(place);
  1.1091 +    return deferred.promise;
  1.1092 +  }
  1.1093 +
  1.1094 +  yield promiseVisitObserver(place);
  1.1095 +  yield promiseAsyncUpdates();
  1.1096 +}
  1.1097 +
  1.1098 +// test with empty mozIVisitInfoCallback object
  1.1099 +function test_callbacks_not_supplied()
  1.1100 +{
  1.1101 +  const URLS = [
  1.1102 +    "imap://cyrus.andrew.cmu.edu/archive.imap",  // bad URI
  1.1103 +    "http://mozilla.org/" // valid URI
  1.1104 +  ];
  1.1105 +  let places = [];
  1.1106 +  URLS.forEach(function(url) {
  1.1107 +    try {
  1.1108 +      let place = {
  1.1109 +        uri: NetUtil.newURI(url),
  1.1110 +        title: "test for " + url,
  1.1111 +        visits: [
  1.1112 +          new VisitInfo(),
  1.1113 +        ],
  1.1114 +      };
  1.1115 +      places.push(place);
  1.1116 +    }
  1.1117 +    catch (e if e.result === Cr.NS_ERROR_FAILURE) {
  1.1118 +      // NetUtil.newURI() can throw if e.g. our app knows about imap://
  1.1119 +      // but the account is not set up and so the URL is invalid for us.
  1.1120 +      // Note this in the log but ignore as it's not the subject of this test.
  1.1121 +      do_log_info("Could not construct URI for '" + url + "'; ignoring");
  1.1122 +    }
  1.1123 +  });
  1.1124 +
  1.1125 +  PlacesUtils.asyncHistory.updatePlaces(places, {});
  1.1126 +  yield promiseAsyncUpdates();
  1.1127 +}
  1.1128 +
  1.1129 +////////////////////////////////////////////////////////////////////////////////
  1.1130 +//// Test Runner
  1.1131 +
  1.1132 +[
  1.1133 +  test_interface_exists,
  1.1134 +  test_invalid_uri_throws,
  1.1135 +  test_invalid_places_throws,
  1.1136 +  test_invalid_guid_throws,
  1.1137 +  test_no_visits_throws,
  1.1138 +  test_add_visit_no_date_throws,
  1.1139 +  test_add_visit_no_transitionType_throws,
  1.1140 +  test_add_visit_invalid_transitionType_throws,
  1.1141 +  // Note: all asynchronous tests (every test below this point) should wait for
  1.1142 +  // async updates before calling run_next_test.
  1.1143 +  test_non_addable_uri_errors,
  1.1144 +  test_duplicate_guid_errors,
  1.1145 +  test_invalid_referrerURI_ignored,
  1.1146 +  test_nonnsIURI_referrerURI_ignored,
  1.1147 +  test_old_referrer_ignored,
  1.1148 +  test_place_id_ignored,
  1.1149 +  test_handleCompletion_called_when_complete,
  1.1150 +  test_add_visit,
  1.1151 +  test_properties_saved,
  1.1152 +  test_guid_saved,
  1.1153 +  test_referrer_saved,
  1.1154 +  test_guid_change_saved,
  1.1155 +  test_title_change_saved,
  1.1156 +  test_no_title_does_not_clear_title,
  1.1157 +  test_title_change_notifies,
  1.1158 +  test_visit_notifies,
  1.1159 +  test_callbacks_not_supplied,
  1.1160 +].forEach(add_task);
  1.1161 +
  1.1162 +function run_test()
  1.1163 +{
  1.1164 +  run_next_test();
  1.1165 +}

mercurial