toolkit/components/places/tests/browser/head.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* Any copyright is dedicated to the Public Domain.
michael@0 2 http://creativecommons.org/publicdomain/zero/1.0/ */
michael@0 3 const TRANSITION_LINK = Ci.nsINavHistoryService.TRANSITION_LINK;
michael@0 4 const TRANSITION_TYPED = Ci.nsINavHistoryService.TRANSITION_TYPED;
michael@0 5 const TRANSITION_BOOKMARK = Ci.nsINavHistoryService.TRANSITION_BOOKMARK;
michael@0 6 const TRANSITION_REDIRECT_PERMANENT = Ci.nsINavHistoryService.TRANSITION_REDIRECT_PERMANENT;
michael@0 7 const TRANSITION_REDIRECT_TEMPORARY = Ci.nsINavHistoryService.TRANSITION_REDIRECT_TEMPORARY;
michael@0 8 const TRANSITION_EMBED = Ci.nsINavHistoryService.TRANSITION_EMBED;
michael@0 9 const TRANSITION_FRAMED_LINK = Ci.nsINavHistoryService.TRANSITION_FRAMED_LINK;
michael@0 10 const TRANSITION_DOWNLOAD = Ci.nsINavHistoryService.TRANSITION_DOWNLOAD;
michael@0 11
michael@0 12 Components.utils.import("resource://gre/modules/NetUtil.jsm");
michael@0 13
michael@0 14 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
michael@0 15 "resource://gre/modules/Promise.jsm");
michael@0 16 XPCOMUtils.defineLazyModuleGetter(this, "Task",
michael@0 17 "resource://gre/modules/Task.jsm");
michael@0 18
michael@0 19 /**
michael@0 20 * Allows waiting for an observer notification once.
michael@0 21 *
michael@0 22 * @param aTopic
michael@0 23 * Notification topic to observe.
michael@0 24 *
michael@0 25 * @return {Promise}
michael@0 26 * @resolves The array [aSubject, aData] from the observed notification.
michael@0 27 * @rejects Never.
michael@0 28 */
michael@0 29 function promiseTopicObserved(aTopic)
michael@0 30 {
michael@0 31 let deferred = Promise.defer();
michael@0 32
michael@0 33 Services.obs.addObserver(
michael@0 34 function PTO_observe(aSubject, aTopic, aData) {
michael@0 35 Services.obs.removeObserver(PTO_observe, aTopic);
michael@0 36 deferred.resolve([aSubject, aData]);
michael@0 37 }, aTopic, false);
michael@0 38
michael@0 39 return deferred.promise;
michael@0 40 }
michael@0 41
michael@0 42 /**
michael@0 43 * Clears history asynchronously.
michael@0 44 *
michael@0 45 * @return {Promise}
michael@0 46 * @resolves When history has been cleared.
michael@0 47 * @rejects Never.
michael@0 48 */
michael@0 49 function promiseClearHistory() {
michael@0 50 let promise = promiseTopicObserved(PlacesUtils.TOPIC_EXPIRATION_FINISHED);
michael@0 51 PlacesUtils.bhistory.removeAllPages();
michael@0 52 return promise;
michael@0 53 }
michael@0 54
michael@0 55 /**
michael@0 56 * Waits for all pending async statements on the default connection.
michael@0 57 *
michael@0 58 * @return {Promise}
michael@0 59 * @resolves When all pending async statements finished.
michael@0 60 * @rejects Never.
michael@0 61 *
michael@0 62 * @note The result is achieved by asynchronously executing a query requiring
michael@0 63 * a write lock. Since all statements on the same connection are
michael@0 64 * serialized, the end of this write operation means that all writes are
michael@0 65 * complete. Note that WAL makes so that writers don't block readers, but
michael@0 66 * this is a problem only across different connections.
michael@0 67 */
michael@0 68 function promiseAsyncUpdates()
michael@0 69 {
michael@0 70 let deferred = Promise.defer();
michael@0 71
michael@0 72 let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
michael@0 73 .DBConnection;
michael@0 74 let begin = db.createAsyncStatement("BEGIN EXCLUSIVE");
michael@0 75 begin.executeAsync();
michael@0 76 begin.finalize();
michael@0 77
michael@0 78 let commit = db.createAsyncStatement("COMMIT");
michael@0 79 commit.executeAsync({
michael@0 80 handleResult: function() {},
michael@0 81 handleError: function() {},
michael@0 82 handleCompletion: function(aReason)
michael@0 83 {
michael@0 84 deferred.resolve();
michael@0 85 }
michael@0 86 });
michael@0 87 commit.finalize();
michael@0 88
michael@0 89 return deferred.promise;
michael@0 90 }
michael@0 91
michael@0 92 /**
michael@0 93 * Returns a moz_places field value for a url.
michael@0 94 *
michael@0 95 * @param aURI
michael@0 96 * The URI or spec to get field for.
michael@0 97 * param aCallback
michael@0 98 * Callback function that will get the property value.
michael@0 99 */
michael@0 100 function fieldForUrl(aURI, aFieldName, aCallback)
michael@0 101 {
michael@0 102 let url = aURI instanceof Ci.nsIURI ? aURI.spec : aURI;
michael@0 103 let stmt = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
michael@0 104 .DBConnection.createAsyncStatement(
michael@0 105 "SELECT " + aFieldName + " FROM moz_places WHERE url = :page_url"
michael@0 106 );
michael@0 107 stmt.params.page_url = url;
michael@0 108 stmt.executeAsync({
michael@0 109 _value: -1,
michael@0 110 handleResult: function(aResultSet) {
michael@0 111 let row = aResultSet.getNextRow();
michael@0 112 if (!row)
michael@0 113 ok(false, "The page should exist in the database");
michael@0 114 this._value = row.getResultByName(aFieldName);
michael@0 115 },
michael@0 116 handleError: function() {},
michael@0 117 handleCompletion: function(aReason) {
michael@0 118 if (aReason != Ci.mozIStorageStatementCallback.REASON_FINISHED)
michael@0 119 ok(false, "The statement should properly succeed");
michael@0 120 aCallback(this._value);
michael@0 121 }
michael@0 122 });
michael@0 123 stmt.finalize();
michael@0 124 }
michael@0 125
michael@0 126 /**
michael@0 127 * Generic nsINavHistoryObserver that doesn't implement anything, but provides
michael@0 128 * dummy methods to prevent errors about an object not having a certain method.
michael@0 129 */
michael@0 130 function NavHistoryObserver() {}
michael@0 131
michael@0 132 NavHistoryObserver.prototype = {
michael@0 133 onBeginUpdateBatch: function () {},
michael@0 134 onEndUpdateBatch: function () {},
michael@0 135 onVisit: function () {},
michael@0 136 onTitleChanged: function () {},
michael@0 137 onDeleteURI: function () {},
michael@0 138 onClearHistory: function () {},
michael@0 139 onPageChanged: function () {},
michael@0 140 onDeleteVisits: function () {},
michael@0 141 QueryInterface: XPCOMUtils.generateQI([
michael@0 142 Ci.nsINavHistoryObserver,
michael@0 143 ])
michael@0 144 };
michael@0 145
michael@0 146 /**
michael@0 147 * Waits for the first OnPageChanged notification for ATTRIBUTE_FAVICON, and
michael@0 148 * verifies that it matches the expected page URI and associated favicon URI.
michael@0 149 *
michael@0 150 * This function also double-checks the GUID parameter of the notification.
michael@0 151 *
michael@0 152 * @param aExpectedPageURI
michael@0 153 * nsIURI object of the page whose favicon should change.
michael@0 154 * @param aExpectedFaviconURI
michael@0 155 * nsIURI object of the newly associated favicon.
michael@0 156 * @param aCallback
michael@0 157 * This function is called after the check finished.
michael@0 158 */
michael@0 159 function waitForFaviconChanged(aExpectedPageURI, aExpectedFaviconURI, aWindow,
michael@0 160 aCallback) {
michael@0 161 let historyObserver = {
michael@0 162 __proto__: NavHistoryObserver.prototype,
michael@0 163 onPageChanged: function WFFC_onPageChanged(aURI, aWhat, aValue, aGUID) {
michael@0 164 if (aWhat != Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON) {
michael@0 165 return;
michael@0 166 }
michael@0 167 aWindow.PlacesUtils.history.removeObserver(this);
michael@0 168
michael@0 169 ok(aURI.equals(aExpectedPageURI),
michael@0 170 "Check URIs are equal for the page which favicon changed");
michael@0 171 is(aValue, aExpectedFaviconURI.spec,
michael@0 172 "Check changed favicon URI is the expected");
michael@0 173 checkGuidForURI(aURI, aGUID);
michael@0 174
michael@0 175 if (aCallback) {
michael@0 176 aCallback();
michael@0 177 }
michael@0 178 }
michael@0 179 };
michael@0 180 aWindow.PlacesUtils.history.addObserver(historyObserver, false);
michael@0 181 }
michael@0 182
michael@0 183 /**
michael@0 184 * Asynchronously adds visits to a page, invoking a callback function when done.
michael@0 185 *
michael@0 186 * @param aPlaceInfo
michael@0 187 * Either an nsIURI, in such a case a single LINK visit will be added.
michael@0 188 * Or can be an object describing the visit to add, or an array
michael@0 189 * of these objects:
michael@0 190 * { uri: nsIURI of the page,
michael@0 191 * transition: one of the TRANSITION_* from nsINavHistoryService,
michael@0 192 * [optional] title: title of the page,
michael@0 193 * [optional] visitDate: visit date in microseconds from the epoch
michael@0 194 * [optional] referrer: nsIURI of the referrer for this visit
michael@0 195 * }
michael@0 196 * @param [optional] aCallback
michael@0 197 * Function to be invoked on completion.
michael@0 198 * @param [optional] aStack
michael@0 199 * The stack frame used to report errors.
michael@0 200 */
michael@0 201 function addVisits(aPlaceInfo, aWindow, aCallback, aStack) {
michael@0 202 let stack = aStack || Components.stack.caller;
michael@0 203 let places = [];
michael@0 204 if (aPlaceInfo instanceof Ci.nsIURI) {
michael@0 205 places.push({ uri: aPlaceInfo });
michael@0 206 }
michael@0 207 else if (Array.isArray(aPlaceInfo)) {
michael@0 208 places = places.concat(aPlaceInfo);
michael@0 209 } else {
michael@0 210 places.push(aPlaceInfo)
michael@0 211 }
michael@0 212
michael@0 213 // Create mozIVisitInfo for each entry.
michael@0 214 let now = Date.now();
michael@0 215 for (let place of places) {
michael@0 216 if (!place.title) {
michael@0 217 place.title = "test visit for " + place.uri.spec;
michael@0 218 }
michael@0 219 place.visits = [{
michael@0 220 transitionType: place.transition === undefined ? TRANSITION_LINK
michael@0 221 : place.transition,
michael@0 222 visitDate: place.visitDate || (now++) * 1000,
michael@0 223 referrerURI: place.referrer
michael@0 224 }];
michael@0 225 }
michael@0 226
michael@0 227 aWindow.PlacesUtils.asyncHistory.updatePlaces(
michael@0 228 places,
michael@0 229 {
michael@0 230 handleError: function AAV_handleError() {
michael@0 231 throw("Unexpected error in adding visit.");
michael@0 232 },
michael@0 233 handleResult: function () {},
michael@0 234 handleCompletion: function UP_handleCompletion() {
michael@0 235 if (aCallback)
michael@0 236 aCallback();
michael@0 237 }
michael@0 238 }
michael@0 239 );
michael@0 240 }
michael@0 241
michael@0 242 /**
michael@0 243 * Asynchronously adds visits to a page.
michael@0 244 *
michael@0 245 * @param aPlaceInfo
michael@0 246 * Can be an nsIURI, in such a case a single LINK visit will be added.
michael@0 247 * Otherwise can be an object describing the visit to add, or an array
michael@0 248 * of these objects:
michael@0 249 * { uri: nsIURI of the page,
michael@0 250 * transition: one of the TRANSITION_* from nsINavHistoryService,
michael@0 251 * [optional] title: title of the page,
michael@0 252 * [optional] visitDate: visit date in microseconds from the epoch
michael@0 253 * [optional] referrer: nsIURI of the referrer for this visit
michael@0 254 * }
michael@0 255 *
michael@0 256 * @return {Promise}
michael@0 257 * @resolves When all visits have been added successfully.
michael@0 258 * @rejects JavaScript exception.
michael@0 259 */
michael@0 260 function promiseAddVisits(aPlaceInfo)
michael@0 261 {
michael@0 262 let deferred = Promise.defer();
michael@0 263 let places = [];
michael@0 264 if (aPlaceInfo instanceof Ci.nsIURI) {
michael@0 265 places.push({ uri: aPlaceInfo });
michael@0 266 }
michael@0 267 else if (Array.isArray(aPlaceInfo)) {
michael@0 268 places = places.concat(aPlaceInfo);
michael@0 269 } else {
michael@0 270 places.push(aPlaceInfo)
michael@0 271 }
michael@0 272
michael@0 273 // Create mozIVisitInfo for each entry.
michael@0 274 let now = Date.now();
michael@0 275 for (let i = 0; i < places.length; i++) {
michael@0 276 if (!places[i].title) {
michael@0 277 places[i].title = "test visit for " + places[i].uri.spec;
michael@0 278 }
michael@0 279 places[i].visits = [{
michael@0 280 transitionType: places[i].transition === undefined ? TRANSITION_LINK
michael@0 281 : places[i].transition,
michael@0 282 visitDate: places[i].visitDate || (now++) * 1000,
michael@0 283 referrerURI: places[i].referrer
michael@0 284 }];
michael@0 285 }
michael@0 286
michael@0 287 PlacesUtils.asyncHistory.updatePlaces(
michael@0 288 places,
michael@0 289 {
michael@0 290 handleError: function AAV_handleError(aResultCode, aPlaceInfo) {
michael@0 291 let ex = new Components.Exception("Unexpected error in adding visits.",
michael@0 292 aResultCode);
michael@0 293 deferred.reject(ex);
michael@0 294 },
michael@0 295 handleResult: function () {},
michael@0 296 handleCompletion: function UP_handleCompletion() {
michael@0 297 deferred.resolve();
michael@0 298 }
michael@0 299 }
michael@0 300 );
michael@0 301
michael@0 302 return deferred.promise;
michael@0 303 }
michael@0 304
michael@0 305 /**
michael@0 306 * Checks that the favicon for the given page matches the provided data.
michael@0 307 *
michael@0 308 * @param aPageURI
michael@0 309 * nsIURI object for the page to check.
michael@0 310 * @param aExpectedMimeType
michael@0 311 * Expected MIME type of the icon, for example "image/png".
michael@0 312 * @param aExpectedData
michael@0 313 * Expected icon data, expressed as an array of byte values.
michael@0 314 * @param aCallback
michael@0 315 * This function is called after the check finished.
michael@0 316 */
michael@0 317 function checkFaviconDataForPage(aPageURI, aExpectedMimeType, aExpectedData,
michael@0 318 aWindow, aCallback) {
michael@0 319 aWindow.PlacesUtils.favicons.getFaviconDataForPage(aPageURI,
michael@0 320 function (aURI, aDataLen, aData, aMimeType) {
michael@0 321 is(aExpectedMimeType, aMimeType, "Check expected MimeType");
michael@0 322 is(aExpectedData.length, aData.length,
michael@0 323 "Check favicon data for the given page matches the provided data");
michael@0 324 checkGuidForURI(aPageURI);
michael@0 325 aCallback();
michael@0 326 });
michael@0 327 }
michael@0 328
michael@0 329 /**
michael@0 330 * Tests that a guid was set in moz_places for a given uri.
michael@0 331 *
michael@0 332 * @param aURI
michael@0 333 * The uri to check.
michael@0 334 * @param [optional] aGUID
michael@0 335 * The expected guid in the database.
michael@0 336 */
michael@0 337 function checkGuidForURI(aURI, aGUID) {
michael@0 338 let guid = doGetGuidForURI(aURI);
michael@0 339 if (aGUID) {
michael@0 340 doCheckValidPlacesGuid(aGUID);
michael@0 341 is(guid, aGUID, "Check equal guid for URIs");
michael@0 342 }
michael@0 343 }
michael@0 344
michael@0 345 /**
michael@0 346 * Retrieves the guid for a given uri.
michael@0 347 *
michael@0 348 * @param aURI
michael@0 349 * The uri to check.
michael@0 350 * @return the associated the guid.
michael@0 351 */
michael@0 352 function doGetGuidForURI(aURI) {
michael@0 353 let stmt = DBConn().createStatement(
michael@0 354 "SELECT guid "
michael@0 355 + "FROM moz_places "
michael@0 356 + "WHERE url = :url "
michael@0 357 );
michael@0 358 stmt.params.url = aURI.spec;
michael@0 359 ok(stmt.executeStep(), "Check get guid for uri from moz_places");
michael@0 360 let guid = stmt.row.guid;
michael@0 361 stmt.finalize();
michael@0 362 doCheckValidPlacesGuid(guid);
michael@0 363 return guid;
michael@0 364 }
michael@0 365
michael@0 366 /**
michael@0 367 * Tests if a given guid is valid for use in Places or not.
michael@0 368 *
michael@0 369 * @param aGuid
michael@0 370 * The guid to test.
michael@0 371 */
michael@0 372 function doCheckValidPlacesGuid(aGuid) {
michael@0 373 ok(/^[a-zA-Z0-9\-_]{12}$/.test(aGuid), "Check guid for valid places");
michael@0 374 }
michael@0 375
michael@0 376 /**
michael@0 377 * Gets the database connection. If the Places connection is invalid it will
michael@0 378 * try to create a new connection.
michael@0 379 *
michael@0 380 * @param [optional] aForceNewConnection
michael@0 381 * Forces creation of a new connection to the database. When a
michael@0 382 * connection is asyncClosed it cannot anymore schedule async statements,
michael@0 383 * though connectionReady will keep returning true (Bug 726990).
michael@0 384 *
michael@0 385 * @return The database connection or null if unable to get one.
michael@0 386 */
michael@0 387 function DBConn(aForceNewConnection) {
michael@0 388 let gDBConn;
michael@0 389 if (!aForceNewConnection) {
michael@0 390 let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
michael@0 391 .DBConnection;
michael@0 392 if (db.connectionReady)
michael@0 393 return db;
michael@0 394 }
michael@0 395
michael@0 396 // If the Places database connection has been closed, create a new connection.
michael@0 397 if (!gDBConn || aForceNewConnection) {
michael@0 398 let file = Services.dirsvc.get('ProfD', Ci.nsIFile);
michael@0 399 file.append("places.sqlite");
michael@0 400 let dbConn = gDBConn = Services.storage.openDatabase(file);
michael@0 401
michael@0 402 // Be sure to cleanly close this connection.
michael@0 403 Services.obs.addObserver(function DBCloseCallback(aSubject, aTopic, aData) {
michael@0 404 Services.obs.removeObserver(DBCloseCallback, aTopic);
michael@0 405 dbConn.asyncClose();
michael@0 406 }, "profile-before-change", false);
michael@0 407 }
michael@0 408
michael@0 409 return gDBConn.connectionReady ? gDBConn : null;
michael@0 410 }
michael@0 411
michael@0 412 function whenDelayedStartupFinished(aWindow, aCallback) {
michael@0 413 Services.obs.addObserver(function observer(aSubject, aTopic) {
michael@0 414 if (aWindow == aSubject) {
michael@0 415 Services.obs.removeObserver(observer, aTopic);
michael@0 416 executeSoon(function() { aCallback(aWindow); });
michael@0 417 }
michael@0 418 }, "browser-delayed-startup-finished", false);
michael@0 419 }
michael@0 420
michael@0 421 function whenNewWindowLoaded(aOptions, aCallback) {
michael@0 422 let win = OpenBrowserWindow(aOptions);
michael@0 423 whenDelayedStartupFinished(win, aCallback);
michael@0 424 }
michael@0 425
michael@0 426 /**
michael@0 427 * Asynchronously check a url is visited.
michael@0 428 *
michael@0 429 * @param aURI The URI.
michael@0 430 * @param aExpectedValue The expected value.
michael@0 431 * @return {Promise}
michael@0 432 * @resolves When the check has been added successfully.
michael@0 433 * @rejects JavaScript exception.
michael@0 434 */
michael@0 435 function promiseIsURIVisited(aURI, aExpectedValue) {
michael@0 436 let deferred = Promise.defer();
michael@0 437
michael@0 438 PlacesUtils.asyncHistory.isURIVisited(aURI, function(aURI, aIsVisited) {
michael@0 439 deferred.resolve(aIsVisited);
michael@0 440 });
michael@0 441
michael@0 442 return deferred.promise;
michael@0 443 }
michael@0 444
michael@0 445 function waitForCondition(condition, nextTest, errorMsg) {
michael@0 446 let tries = 0;
michael@0 447 let interval = setInterval(function() {
michael@0 448 if (tries >= 30) {
michael@0 449 ok(false, errorMsg);
michael@0 450 moveOn();
michael@0 451 }
michael@0 452 let conditionPassed;
michael@0 453 try {
michael@0 454 conditionPassed = condition();
michael@0 455 } catch (e) {
michael@0 456 ok(false, e + "\n" + e.stack);
michael@0 457 conditionPassed = false;
michael@0 458 }
michael@0 459 if (conditionPassed) {
michael@0 460 moveOn();
michael@0 461 }
michael@0 462 tries++;
michael@0 463 }, 200);
michael@0 464 function moveOn() {
michael@0 465 clearInterval(interval);
michael@0 466 nextTest();
michael@0 467 };
michael@0 468 }

mercurial