browser/base/content/test/general/browser_sanitizeDialog.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 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim:set ts=2 sw=2 sts=2 et: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 /**
michael@0 8 * Tests the sanitize dialog (a.k.a. the clear recent history dialog).
michael@0 9 * See bug 480169.
michael@0 10 *
michael@0 11 * The purpose of this test is not to fully flex the sanitize timespan code;
michael@0 12 * browser/base/content/test/general/browser_sanitize-timespans.js does that. This
michael@0 13 * test checks the UI of the dialog and makes sure it's correctly connected to
michael@0 14 * the sanitize timespan code.
michael@0 15 *
michael@0 16 * Some of this code, especially the history creation parts, was taken from
michael@0 17 * browser/base/content/test/general/browser_sanitize-timespans.js.
michael@0 18 */
michael@0 19
michael@0 20 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 21
michael@0 22 XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
michael@0 23 "resource://gre/modules/FormHistory.jsm");
michael@0 24 XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
michael@0 25 "resource://gre/modules/Downloads.jsm");
michael@0 26
michael@0 27 let tempScope = {};
michael@0 28 Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader)
michael@0 29 .loadSubScript("chrome://browser/content/sanitize.js", tempScope);
michael@0 30 let Sanitizer = tempScope.Sanitizer;
michael@0 31
michael@0 32 const kMsecPerMin = 60 * 1000;
michael@0 33 const kUsecPerMin = 60 * 1000000;
michael@0 34
michael@0 35 let formEntries, downloadIDs, olderDownloadIDs;
michael@0 36
michael@0 37 // Add tests here. Each is a function that's called by doNextTest().
michael@0 38 var gAllTests = [
michael@0 39
michael@0 40 /**
michael@0 41 * Initializes the dialog to its default state.
michael@0 42 */
michael@0 43 function () {
michael@0 44 let wh = new WindowHelper();
michael@0 45 wh.onload = function () {
michael@0 46 // Select "Last Hour"
michael@0 47 this.selectDuration(Sanitizer.TIMESPAN_HOUR);
michael@0 48 // Hide details
michael@0 49 if (!this.getItemList().collapsed)
michael@0 50 this.toggleDetails();
michael@0 51 this.acceptDialog();
michael@0 52 };
michael@0 53 wh.open();
michael@0 54 },
michael@0 55
michael@0 56 /**
michael@0 57 * Cancels the dialog, makes sure history not cleared.
michael@0 58 */
michael@0 59 function () {
michael@0 60 // Add history (within the past hour)
michael@0 61 let uris = [];
michael@0 62 let places = [];
michael@0 63 let pURI;
michael@0 64 for (let i = 0; i < 30; i++) {
michael@0 65 pURI = makeURI("http://" + i + "-minutes-ago.com/");
michael@0 66 places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(i)});
michael@0 67 uris.push(pURI);
michael@0 68 }
michael@0 69
michael@0 70 addVisits(places, function() {
michael@0 71 let wh = new WindowHelper();
michael@0 72 wh.onload = function () {
michael@0 73 this.selectDuration(Sanitizer.TIMESPAN_HOUR);
michael@0 74 this.checkPrefCheckbox("history", false);
michael@0 75 this.checkDetails(false);
michael@0 76
michael@0 77 // Show details
michael@0 78 this.toggleDetails();
michael@0 79 this.checkDetails(true);
michael@0 80
michael@0 81 // Hide details
michael@0 82 this.toggleDetails();
michael@0 83 this.checkDetails(false);
michael@0 84 this.cancelDialog();
michael@0 85 };
michael@0 86 wh.onunload = function () {
michael@0 87 yield promiseHistoryClearedState(uris, false);
michael@0 88 yield blankSlate();
michael@0 89 yield promiseHistoryClearedState(uris, true);
michael@0 90 };
michael@0 91 wh.open();
michael@0 92 });
michael@0 93 },
michael@0 94
michael@0 95 function () {
michael@0 96 // Add downloads (within the past hour).
michael@0 97 Task.spawn(function () {
michael@0 98 downloadIDs = [];
michael@0 99 for (let i = 0; i < 5; i++) {
michael@0 100 yield addDownloadWithMinutesAgo(downloadIDs, i);
michael@0 101 }
michael@0 102 // Add downloads (over an hour ago).
michael@0 103 olderDownloadIDs = [];
michael@0 104 for (let i = 0; i < 5; i++) {
michael@0 105 yield addDownloadWithMinutesAgo(olderDownloadIDs, 61 + i);
michael@0 106 }
michael@0 107
michael@0 108 doNextTest();
michael@0 109 }).then(null, Components.utils.reportError);
michael@0 110 },
michael@0 111
michael@0 112 /**
michael@0 113 * Ensures that the combined history-downloads checkbox clears both history
michael@0 114 * visits and downloads when checked; the dialog respects simple timespan.
michael@0 115 */
michael@0 116 function () {
michael@0 117 // Add history (within the past hour).
michael@0 118 let uris = [];
michael@0 119 let places = [];
michael@0 120 let pURI;
michael@0 121 for (let i = 0; i < 30; i++) {
michael@0 122 pURI = makeURI("http://" + i + "-minutes-ago.com/");
michael@0 123 places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(i)});
michael@0 124 uris.push(pURI);
michael@0 125 }
michael@0 126 // Add history (over an hour ago).
michael@0 127 let olderURIs = [];
michael@0 128 for (let i = 0; i < 5; i++) {
michael@0 129 pURI = makeURI("http://" + (61 + i) + "-minutes-ago.com/");
michael@0 130 places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(61 + i)});
michael@0 131 olderURIs.push(pURI);
michael@0 132 }
michael@0 133
michael@0 134 addVisits(places, function() {
michael@0 135 let totalHistoryVisits = uris.length + olderURIs.length;
michael@0 136
michael@0 137 let wh = new WindowHelper();
michael@0 138 wh.onload = function () {
michael@0 139 this.selectDuration(Sanitizer.TIMESPAN_HOUR);
michael@0 140 this.checkPrefCheckbox("history", true);
michael@0 141 this.acceptDialog();
michael@0 142 };
michael@0 143 wh.onunload = function () {
michael@0 144 intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_HOUR,
michael@0 145 "timeSpan pref should be hour after accepting dialog with " +
michael@0 146 "hour selected");
michael@0 147 boolPrefIs("cpd.history", true,
michael@0 148 "history pref should be true after accepting dialog with " +
michael@0 149 "history checkbox checked");
michael@0 150 boolPrefIs("cpd.downloads", true,
michael@0 151 "downloads pref should be true after accepting dialog with " +
michael@0 152 "history checkbox checked");
michael@0 153
michael@0 154 // History visits and downloads within one hour should be cleared.
michael@0 155 yield promiseHistoryClearedState(uris, true);
michael@0 156 yield ensureDownloadsClearedState(downloadIDs, true);
michael@0 157
michael@0 158 // Visits and downloads > 1 hour should still exist.
michael@0 159 yield promiseHistoryClearedState(olderURIs, false);
michael@0 160 yield ensureDownloadsClearedState(olderDownloadIDs, false);
michael@0 161
michael@0 162 // OK, done, cleanup after ourselves.
michael@0 163 yield blankSlate();
michael@0 164 yield promiseHistoryClearedState(olderURIs, true);
michael@0 165 yield ensureDownloadsClearedState(olderDownloadIDs, true);
michael@0 166 };
michael@0 167 wh.open();
michael@0 168 });
michael@0 169 },
michael@0 170
michael@0 171 /**
michael@0 172 * Add form history entries for the next test.
michael@0 173 */
michael@0 174 function () {
michael@0 175 formEntries = [];
michael@0 176
michael@0 177 let iter = function() {
michael@0 178 for (let i = 0; i < 5; i++) {
michael@0 179 formEntries.push(addFormEntryWithMinutesAgo(iter, i));
michael@0 180 yield undefined;
michael@0 181 }
michael@0 182 doNextTest();
michael@0 183 }();
michael@0 184
michael@0 185 iter.next();
michael@0 186 },
michael@0 187
michael@0 188 function () {
michael@0 189 // Add downloads (within the past hour).
michael@0 190 Task.spawn(function () {
michael@0 191 downloadIDs = [];
michael@0 192 for (let i = 0; i < 5; i++) {
michael@0 193 yield addDownloadWithMinutesAgo(downloadIDs, i);
michael@0 194 }
michael@0 195
michael@0 196 doNextTest();
michael@0 197 }).then(null, Components.utils.reportError);
michael@0 198 },
michael@0 199
michael@0 200 /**
michael@0 201 * Ensures that the combined history-downloads checkbox removes neither
michael@0 202 * history visits nor downloads when not checked.
michael@0 203 */
michael@0 204 function () {
michael@0 205 // Add history, downloads, form entries (within the past hour).
michael@0 206 let uris = [];
michael@0 207 let places = [];
michael@0 208 let pURI;
michael@0 209 for (let i = 0; i < 5; i++) {
michael@0 210 pURI = makeURI("http://" + i + "-minutes-ago.com/");
michael@0 211 places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(i)});
michael@0 212 uris.push(pURI);
michael@0 213 }
michael@0 214
michael@0 215 addVisits(places, function() {
michael@0 216 let wh = new WindowHelper();
michael@0 217 wh.onload = function () {
michael@0 218 is(this.isWarningPanelVisible(), false,
michael@0 219 "Warning panel should be hidden after previously accepting dialog " +
michael@0 220 "with a predefined timespan");
michael@0 221 this.selectDuration(Sanitizer.TIMESPAN_HOUR);
michael@0 222
michael@0 223 // Remove only form entries, leave history (including downloads).
michael@0 224 this.checkPrefCheckbox("history", false);
michael@0 225 this.checkPrefCheckbox("formdata", true);
michael@0 226 this.acceptDialog();
michael@0 227 };
michael@0 228 wh.onunload = function () {
michael@0 229 intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_HOUR,
michael@0 230 "timeSpan pref should be hour after accepting dialog with " +
michael@0 231 "hour selected");
michael@0 232 boolPrefIs("cpd.history", false,
michael@0 233 "history pref should be false after accepting dialog with " +
michael@0 234 "history checkbox unchecked");
michael@0 235 boolPrefIs("cpd.downloads", false,
michael@0 236 "downloads pref should be false after accepting dialog with " +
michael@0 237 "history checkbox unchecked");
michael@0 238
michael@0 239 // Of the three only form entries should be cleared.
michael@0 240 yield promiseHistoryClearedState(uris, false);
michael@0 241 yield ensureDownloadsClearedState(downloadIDs, false);
michael@0 242
michael@0 243 formEntries.forEach(function (entry) {
michael@0 244 let exists = yield formNameExists(entry);
michael@0 245 is(exists, false, "form entry " + entry + " should no longer exist");
michael@0 246 });
michael@0 247
michael@0 248 // OK, done, cleanup after ourselves.
michael@0 249 yield blankSlate();
michael@0 250 yield promiseHistoryClearedState(uris, true);
michael@0 251 yield ensureDownloadsClearedState(downloadIDs, true);
michael@0 252 };
michael@0 253 wh.open();
michael@0 254 });
michael@0 255 },
michael@0 256
michael@0 257 /**
michael@0 258 * Ensures that the "Everything" duration option works.
michael@0 259 */
michael@0 260 function () {
michael@0 261 // Add history.
michael@0 262 let uris = [];
michael@0 263 let places = [];
michael@0 264 let pURI;
michael@0 265 // within past hour, within past two hours, within past four hours and
michael@0 266 // outside past four hours
michael@0 267 [10, 70, 130, 250].forEach(function(aValue) {
michael@0 268 pURI = makeURI("http://" + aValue + "-minutes-ago.com/");
michael@0 269 places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(aValue)});
michael@0 270 uris.push(pURI);
michael@0 271 });
michael@0 272 addVisits(places, function() {
michael@0 273 let wh = new WindowHelper();
michael@0 274 wh.onload = function () {
michael@0 275 is(this.isWarningPanelVisible(), false,
michael@0 276 "Warning panel should be hidden after previously accepting dialog " +
michael@0 277 "with a predefined timespan");
michael@0 278 this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
michael@0 279 this.checkPrefCheckbox("history", true);
michael@0 280 this.checkDetails(true);
michael@0 281
michael@0 282 // Hide details
michael@0 283 this.toggleDetails();
michael@0 284 this.checkDetails(false);
michael@0 285
michael@0 286 // Show details
michael@0 287 this.toggleDetails();
michael@0 288 this.checkDetails(true);
michael@0 289
michael@0 290 this.acceptDialog();
michael@0 291 };
michael@0 292 wh.onunload = function () {
michael@0 293 intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_EVERYTHING,
michael@0 294 "timeSpan pref should be everything after accepting dialog " +
michael@0 295 "with everything selected");
michael@0 296
michael@0 297 yield promiseHistoryClearedState(uris, true);
michael@0 298 };
michael@0 299 wh.open();
michael@0 300 });
michael@0 301 },
michael@0 302
michael@0 303 /**
michael@0 304 * Ensures that the "Everything" warning is visible on dialog open after
michael@0 305 * the previous test.
michael@0 306 */
michael@0 307 function () {
michael@0 308 // Add history.
michael@0 309 let uris = [];
michael@0 310 let places = [];
michael@0 311 let pURI;
michael@0 312 // within past hour, within past two hours, within past four hours and
michael@0 313 // outside past four hours
michael@0 314 [10, 70, 130, 250].forEach(function(aValue) {
michael@0 315 pURI = makeURI("http://" + aValue + "-minutes-ago.com/");
michael@0 316 places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(aValue)});
michael@0 317 uris.push(pURI);
michael@0 318 });
michael@0 319 addVisits(places, function() {
michael@0 320 let wh = new WindowHelper();
michael@0 321 wh.onload = function () {
michael@0 322 is(this.isWarningPanelVisible(), true,
michael@0 323 "Warning panel should be visible after previously accepting dialog " +
michael@0 324 "with clearing everything");
michael@0 325 this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
michael@0 326 this.checkPrefCheckbox("history", true);
michael@0 327 this.acceptDialog();
michael@0 328 };
michael@0 329 wh.onunload = function () {
michael@0 330 intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_EVERYTHING,
michael@0 331 "timeSpan pref should be everything after accepting dialog " +
michael@0 332 "with everything selected");
michael@0 333
michael@0 334 yield promiseHistoryClearedState(uris, true);
michael@0 335 };
michael@0 336 wh.open();
michael@0 337 });
michael@0 338 },
michael@0 339
michael@0 340 /**
michael@0 341 * Add form history entry for the next test.
michael@0 342 */
michael@0 343 function () {
michael@0 344 let iter = function() {
michael@0 345 formEntries = [ addFormEntryWithMinutesAgo(iter, 10) ];
michael@0 346 yield undefined;
michael@0 347 doNextTest();
michael@0 348 }();
michael@0 349
michael@0 350 iter.next();
michael@0 351 },
michael@0 352
michael@0 353 /**
michael@0 354 * The next three tests checks that when a certain history item cannot be
michael@0 355 * cleared then the checkbox should be both disabled and unchecked.
michael@0 356 * In addition, we ensure that this behavior does not modify the preferences.
michael@0 357 */
michael@0 358 function () {
michael@0 359 // Add history.
michael@0 360 let pURI = makeURI("http://" + 10 + "-minutes-ago.com/");
michael@0 361 addVisits({uri: pURI, visitDate: visitTimeForMinutesAgo(10)}, function() {
michael@0 362 let uris = [ pURI ];
michael@0 363
michael@0 364 let wh = new WindowHelper();
michael@0 365 wh.onload = function() {
michael@0 366 // Check that the relevant checkboxes are enabled
michael@0 367 var cb = this.win.document.querySelectorAll(
michael@0 368 "#itemList > [preference='privacy.cpd.formdata']");
michael@0 369 ok(cb.length == 1 && !cb[0].disabled, "There is formdata, checkbox to " +
michael@0 370 "clear formdata should be enabled.");
michael@0 371
michael@0 372 var cb = this.win.document.querySelectorAll(
michael@0 373 "#itemList > [preference='privacy.cpd.history']");
michael@0 374 ok(cb.length == 1 && !cb[0].disabled, "There is history, checkbox to " +
michael@0 375 "clear history should be enabled.");
michael@0 376
michael@0 377 this.checkAllCheckboxes();
michael@0 378 this.acceptDialog();
michael@0 379 };
michael@0 380 wh.onunload = function () {
michael@0 381 yield promiseHistoryClearedState(uris, true);
michael@0 382
michael@0 383 let exists = yield formNameExists(formEntries[0]);
michael@0 384 is(exists, false, "form entry " + formEntries[0] + " should no longer exist");
michael@0 385 };
michael@0 386 wh.open();
michael@0 387 });
michael@0 388 },
michael@0 389 function () {
michael@0 390 let wh = new WindowHelper();
michael@0 391 wh.onload = function() {
michael@0 392 boolPrefIs("cpd.history", true,
michael@0 393 "history pref should be true after accepting dialog with " +
michael@0 394 "history checkbox checked");
michael@0 395 boolPrefIs("cpd.formdata", true,
michael@0 396 "formdata pref should be true after accepting dialog with " +
michael@0 397 "formdata checkbox checked");
michael@0 398
michael@0 399
michael@0 400 // Even though the formdata pref is true, because there is no history
michael@0 401 // left to clear, the checkbox will be disabled.
michael@0 402 var cb = this.win.document.querySelectorAll(
michael@0 403 "#itemList > [preference='privacy.cpd.formdata']");
michael@0 404 ok(cb.length == 1 && cb[0].disabled && !cb[0].checked,
michael@0 405 "There is no formdata history, checkbox should be disabled and be " +
michael@0 406 "cleared to reduce user confusion (bug 497664).");
michael@0 407
michael@0 408 var cb = this.win.document.querySelectorAll(
michael@0 409 "#itemList > [preference='privacy.cpd.history']");
michael@0 410 ok(cb.length == 1 && !cb[0].disabled && cb[0].checked,
michael@0 411 "There is no history, but history checkbox should always be enabled " +
michael@0 412 "and will be checked from previous preference.");
michael@0 413
michael@0 414 this.acceptDialog();
michael@0 415 }
michael@0 416 wh.open();
michael@0 417 },
michael@0 418
michael@0 419 /**
michael@0 420 * Add form history entry for the next test.
michael@0 421 */
michael@0 422 function () {
michael@0 423 let iter = function() {
michael@0 424 formEntries = [ addFormEntryWithMinutesAgo(iter, 10) ];
michael@0 425 yield undefined;
michael@0 426 doNextTest();
michael@0 427 }();
michael@0 428
michael@0 429 iter.next();
michael@0 430 },
michael@0 431
michael@0 432 function () {
michael@0 433 let wh = new WindowHelper();
michael@0 434 wh.onload = function() {
michael@0 435 boolPrefIs("cpd.formdata", true,
michael@0 436 "formdata pref should persist previous value after accepting " +
michael@0 437 "dialog where you could not clear formdata.");
michael@0 438
michael@0 439 var cb = this.win.document.querySelectorAll(
michael@0 440 "#itemList > [preference='privacy.cpd.formdata']");
michael@0 441 ok(cb.length == 1 && !cb[0].disabled && cb[0].checked,
michael@0 442 "There exists formEntries so the checkbox should be in sync with " +
michael@0 443 "the pref.");
michael@0 444
michael@0 445 this.acceptDialog();
michael@0 446 };
michael@0 447 wh.onunload = function () {
michael@0 448 let exists = yield formNameExists(formEntries[0]);
michael@0 449 is(exists, false, "form entry " + formEntries[0] + " should no longer exist");
michael@0 450 };
michael@0 451 wh.open();
michael@0 452 },
michael@0 453
michael@0 454
michael@0 455 /**
michael@0 456 * These next six tests together ensure that toggling details persists
michael@0 457 * across dialog openings.
michael@0 458 */
michael@0 459 function () {
michael@0 460 let wh = new WindowHelper();
michael@0 461 wh.onload = function () {
michael@0 462 // Check all items and select "Everything"
michael@0 463 this.checkAllCheckboxes();
michael@0 464 this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
michael@0 465
michael@0 466 // Hide details
michael@0 467 this.toggleDetails();
michael@0 468 this.checkDetails(false);
michael@0 469 this.acceptDialog();
michael@0 470 };
michael@0 471 wh.open();
michael@0 472 },
michael@0 473 function () {
michael@0 474 let wh = new WindowHelper();
michael@0 475 wh.onload = function () {
michael@0 476 // Details should remain closed because all items are checked.
michael@0 477 this.checkDetails(false);
michael@0 478
michael@0 479 // Uncheck history.
michael@0 480 this.checkPrefCheckbox("history", false);
michael@0 481 this.acceptDialog();
michael@0 482 };
michael@0 483 wh.open();
michael@0 484 },
michael@0 485 function () {
michael@0 486 let wh = new WindowHelper();
michael@0 487 wh.onload = function () {
michael@0 488 // Details should be open because not all items are checked.
michael@0 489 this.checkDetails(true);
michael@0 490
michael@0 491 // Modify the Site Preferences item state (bug 527820)
michael@0 492 this.checkAllCheckboxes();
michael@0 493 this.checkPrefCheckbox("siteSettings", false);
michael@0 494 this.acceptDialog();
michael@0 495 };
michael@0 496 wh.open();
michael@0 497 },
michael@0 498 function () {
michael@0 499 let wh = new WindowHelper();
michael@0 500 wh.onload = function () {
michael@0 501 // Details should be open because not all items are checked.
michael@0 502 this.checkDetails(true);
michael@0 503
michael@0 504 // Hide details
michael@0 505 this.toggleDetails();
michael@0 506 this.checkDetails(false);
michael@0 507 this.cancelDialog();
michael@0 508 };
michael@0 509 wh.open();
michael@0 510 },
michael@0 511 function () {
michael@0 512 let wh = new WindowHelper();
michael@0 513 wh.onload = function () {
michael@0 514 // Details should be open because not all items are checked.
michael@0 515 this.checkDetails(true);
michael@0 516
michael@0 517 // Select another duration
michael@0 518 this.selectDuration(Sanitizer.TIMESPAN_HOUR);
michael@0 519 // Hide details
michael@0 520 this.toggleDetails();
michael@0 521 this.checkDetails(false);
michael@0 522 this.acceptDialog();
michael@0 523 };
michael@0 524 wh.open();
michael@0 525 },
michael@0 526 function () {
michael@0 527 let wh = new WindowHelper();
michael@0 528 wh.onload = function () {
michael@0 529 // Details should not be open because "Last Hour" is selected
michael@0 530 this.checkDetails(false);
michael@0 531
michael@0 532 this.cancelDialog();
michael@0 533 };
michael@0 534 wh.open();
michael@0 535 },
michael@0 536 function () {
michael@0 537 let wh = new WindowHelper();
michael@0 538 wh.onload = function () {
michael@0 539 // Details should have remained closed
michael@0 540 this.checkDetails(false);
michael@0 541
michael@0 542 // Show details
michael@0 543 this.toggleDetails();
michael@0 544 this.checkDetails(true);
michael@0 545 this.cancelDialog();
michael@0 546 };
michael@0 547 wh.open();
michael@0 548 },
michael@0 549 function () {
michael@0 550 // Test for offline cache deletion
michael@0 551
michael@0 552 // Prepare stuff, we will work with www.example.com
michael@0 553 var URL = "http://www.example.com";
michael@0 554
michael@0 555 var ios = Cc["@mozilla.org/network/io-service;1"]
michael@0 556 .getService(Ci.nsIIOService);
michael@0 557 var URI = ios.newURI(URL, null, null);
michael@0 558
michael@0 559 var sm = Cc["@mozilla.org/scriptsecuritymanager;1"]
michael@0 560 .getService(Ci.nsIScriptSecurityManager);
michael@0 561 var principal = sm.getNoAppCodebasePrincipal(URI);
michael@0 562
michael@0 563 // Give www.example.com privileges to store offline data
michael@0 564 var pm = Cc["@mozilla.org/permissionmanager;1"]
michael@0 565 .getService(Ci.nsIPermissionManager);
michael@0 566 pm.addFromPrincipal(principal, "offline-app", Ci.nsIPermissionManager.ALLOW_ACTION);
michael@0 567 pm.addFromPrincipal(principal, "offline-app", Ci.nsIOfflineCacheUpdateService.ALLOW_NO_WARN);
michael@0 568
michael@0 569 // Store something to the offline cache
michael@0 570 const nsICache = Components.interfaces.nsICache;
michael@0 571 var cs = Components.classes["@mozilla.org/network/cache-service;1"]
michael@0 572 .getService(Components.interfaces.nsICacheService);
michael@0 573 var session = cs.createSession(URL + "/manifest", nsICache.STORE_OFFLINE, nsICache.STREAM_BASED);
michael@0 574
michael@0 575 // Open the dialog
michael@0 576 let wh = new WindowHelper();
michael@0 577 wh.onload = function () {
michael@0 578 this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
michael@0 579 // Show details
michael@0 580 this.toggleDetails();
michael@0 581 // Clear only offlineApps
michael@0 582 this.uncheckAllCheckboxes();
michael@0 583 this.checkPrefCheckbox("offlineApps", true);
michael@0 584 this.acceptDialog();
michael@0 585 };
michael@0 586 wh.onunload = function () {
michael@0 587 // Check if the cache has been deleted
michael@0 588 var size = -1;
michael@0 589 var visitor = {
michael@0 590 visitDevice: function (deviceID, deviceInfo)
michael@0 591 {
michael@0 592 if (deviceID == "offline")
michael@0 593 size = deviceInfo.totalSize;
michael@0 594
michael@0 595 // Do not enumerate entries
michael@0 596 return false;
michael@0 597 },
michael@0 598
michael@0 599 visitEntry: function (deviceID, entryInfo)
michael@0 600 {
michael@0 601 // Do not enumerate entries.
michael@0 602 return false;
michael@0 603 }
michael@0 604 };
michael@0 605 cs.visitEntries(visitor);
michael@0 606 is(size, 0, "offline application cache entries evicted");
michael@0 607 };
michael@0 608
michael@0 609 var cacheListener = {
michael@0 610 onCacheEntryAvailable: function (entry, access, status) {
michael@0 611 is(status, Cr.NS_OK);
michael@0 612 var stream = entry.openOutputStream(0);
michael@0 613 var content = "content";
michael@0 614 stream.write(content, content.length);
michael@0 615 stream.close();
michael@0 616 entry.close();
michael@0 617 wh.open();
michael@0 618 }
michael@0 619 };
michael@0 620
michael@0 621 session.asyncOpenCacheEntry(URL, nsICache.ACCESS_READ_WRITE, cacheListener);
michael@0 622 },
michael@0 623 function () {
michael@0 624 // Test for offline apps permission deletion
michael@0 625
michael@0 626 // Prepare stuff, we will work with www.example.com
michael@0 627 var URL = "http://www.example.com";
michael@0 628
michael@0 629 var ios = Cc["@mozilla.org/network/io-service;1"]
michael@0 630 .getService(Ci.nsIIOService);
michael@0 631 var URI = ios.newURI(URL, null, null);
michael@0 632
michael@0 633 var sm = Cc["@mozilla.org/scriptsecuritymanager;1"]
michael@0 634 .getService(Ci.nsIScriptSecurityManager);
michael@0 635 var principal = sm.getNoAppCodebasePrincipal(URI);
michael@0 636
michael@0 637 // Open the dialog
michael@0 638 let wh = new WindowHelper();
michael@0 639 wh.onload = function () {
michael@0 640 this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
michael@0 641 // Show details
michael@0 642 this.toggleDetails();
michael@0 643 // Clear only offlineApps
michael@0 644 this.uncheckAllCheckboxes();
michael@0 645 this.checkPrefCheckbox("siteSettings", true);
michael@0 646 this.acceptDialog();
michael@0 647 };
michael@0 648 wh.onunload = function () {
michael@0 649 // Check all has been deleted (privileges, data, cache)
michael@0 650 var pm = Cc["@mozilla.org/permissionmanager;1"]
michael@0 651 .getService(Ci.nsIPermissionManager);
michael@0 652 is(pm.testPermissionFromPrincipal(principal, "offline-app"), 0, "offline-app permissions removed");
michael@0 653 };
michael@0 654 wh.open();
michael@0 655 }
michael@0 656 ];
michael@0 657
michael@0 658 // Index in gAllTests of the test currently being run. Incremented for each
michael@0 659 // test run. See doNextTest().
michael@0 660 var gCurrTest = 0;
michael@0 661
michael@0 662 let now_mSec = Date.now();
michael@0 663 let now_uSec = now_mSec * 1000;
michael@0 664
michael@0 665 ///////////////////////////////////////////////////////////////////////////////
michael@0 666
michael@0 667 /**
michael@0 668 * This wraps the dialog and provides some convenience methods for interacting
michael@0 669 * with it.
michael@0 670 *
michael@0 671 * @param aWin
michael@0 672 * The dialog's nsIDOMWindow
michael@0 673 */
michael@0 674 function WindowHelper(aWin) {
michael@0 675 this.win = aWin;
michael@0 676 }
michael@0 677
michael@0 678 WindowHelper.prototype = {
michael@0 679 /**
michael@0 680 * "Presses" the dialog's OK button.
michael@0 681 */
michael@0 682 acceptDialog: function () {
michael@0 683 is(this.win.document.documentElement.getButton("accept").disabled, false,
michael@0 684 "Dialog's OK button should not be disabled");
michael@0 685 this.win.document.documentElement.acceptDialog();
michael@0 686 },
michael@0 687
michael@0 688 /**
michael@0 689 * "Presses" the dialog's Cancel button.
michael@0 690 */
michael@0 691 cancelDialog: function () {
michael@0 692 this.win.document.documentElement.cancelDialog();
michael@0 693 },
michael@0 694
michael@0 695 /**
michael@0 696 * Ensures that the details progressive disclosure button and the item list
michael@0 697 * hidden by it match up. Also makes sure the height of the dialog is
michael@0 698 * sufficient for the item list and warning panel.
michael@0 699 *
michael@0 700 * @param aShouldBeShown
michael@0 701 * True if you expect the details to be shown and false if hidden
michael@0 702 */
michael@0 703 checkDetails: function (aShouldBeShown) {
michael@0 704 let button = this.getDetailsButton();
michael@0 705 let list = this.getItemList();
michael@0 706 let hidden = list.hidden || list.collapsed;
michael@0 707 is(hidden, !aShouldBeShown,
michael@0 708 "Details should be " + (aShouldBeShown ? "shown" : "hidden") +
michael@0 709 " but were actually " + (hidden ? "hidden" : "shown"));
michael@0 710 let dir = hidden ? "down" : "up";
michael@0 711 is(button.className, "expander-" + dir,
michael@0 712 "Details button should be " + dir + " because item list is " +
michael@0 713 (hidden ? "" : "not ") + "hidden");
michael@0 714 let height = 0;
michael@0 715 if (!hidden) {
michael@0 716 ok(list.boxObject.height > 30, "listbox has sufficient size")
michael@0 717 height += list.boxObject.height;
michael@0 718 }
michael@0 719 if (this.isWarningPanelVisible())
michael@0 720 height += this.getWarningPanel().boxObject.height;
michael@0 721 ok(height < this.win.innerHeight,
michael@0 722 "Window should be tall enough to fit warning panel and item list");
michael@0 723 },
michael@0 724
michael@0 725 /**
michael@0 726 * (Un)checks a history scope checkbox (browser & download history,
michael@0 727 * form history, etc.).
michael@0 728 *
michael@0 729 * @param aPrefName
michael@0 730 * The final portion of the checkbox's privacy.cpd.* preference name
michael@0 731 * @param aCheckState
michael@0 732 * True if the checkbox should be checked, false otherwise
michael@0 733 */
michael@0 734 checkPrefCheckbox: function (aPrefName, aCheckState) {
michael@0 735 var pref = "privacy.cpd." + aPrefName;
michael@0 736 var cb = this.win.document.querySelectorAll(
michael@0 737 "#itemList > [preference='" + pref + "']");
michael@0 738 is(cb.length, 1, "found checkbox for " + pref + " preference");
michael@0 739 if (cb[0].checked != aCheckState)
michael@0 740 cb[0].click();
michael@0 741 },
michael@0 742
michael@0 743 /**
michael@0 744 * Makes sure all the checkboxes are checked.
michael@0 745 */
michael@0 746 _checkAllCheckboxesCustom: function (check) {
michael@0 747 var cb = this.win.document.querySelectorAll("#itemList > [preference]");
michael@0 748 ok(cb.length > 1, "found checkboxes for preferences");
michael@0 749 for (var i = 0; i < cb.length; ++i) {
michael@0 750 var pref = this.win.document.getElementById(cb[i].getAttribute("preference"));
michael@0 751 if (!!pref.value ^ check)
michael@0 752 cb[i].click();
michael@0 753 }
michael@0 754 },
michael@0 755
michael@0 756 checkAllCheckboxes: function () {
michael@0 757 this._checkAllCheckboxesCustom(true);
michael@0 758 },
michael@0 759
michael@0 760 uncheckAllCheckboxes: function () {
michael@0 761 this._checkAllCheckboxesCustom(false);
michael@0 762 },
michael@0 763
michael@0 764 /**
michael@0 765 * @return The details progressive disclosure button
michael@0 766 */
michael@0 767 getDetailsButton: function () {
michael@0 768 return this.win.document.getElementById("detailsExpander");
michael@0 769 },
michael@0 770
michael@0 771 /**
michael@0 772 * @return The dialog's duration dropdown
michael@0 773 */
michael@0 774 getDurationDropdown: function () {
michael@0 775 return this.win.document.getElementById("sanitizeDurationChoice");
michael@0 776 },
michael@0 777
michael@0 778 /**
michael@0 779 * @return The item list hidden by the details progressive disclosure button
michael@0 780 */
michael@0 781 getItemList: function () {
michael@0 782 return this.win.document.getElementById("itemList");
michael@0 783 },
michael@0 784
michael@0 785 /**
michael@0 786 * @return The clear-everything warning box
michael@0 787 */
michael@0 788 getWarningPanel: function () {
michael@0 789 return this.win.document.getElementById("sanitizeEverythingWarningBox");
michael@0 790 },
michael@0 791
michael@0 792 /**
michael@0 793 * @return True if the "Everything" warning panel is visible (as opposed to
michael@0 794 * the tree)
michael@0 795 */
michael@0 796 isWarningPanelVisible: function () {
michael@0 797 return !this.getWarningPanel().hidden;
michael@0 798 },
michael@0 799
michael@0 800 /**
michael@0 801 * Opens the clear recent history dialog. Before calling this, set
michael@0 802 * this.onload to a function to execute onload. It should close the dialog
michael@0 803 * when done so that the tests may continue. Set this.onunload to a function
michael@0 804 * to execute onunload. this.onunload is optional. If it returns true, the
michael@0 805 * caller is expected to call waitForAsyncUpdates at some point; if false is
michael@0 806 * returned, waitForAsyncUpdates is called automatically.
michael@0 807 */
michael@0 808 open: function () {
michael@0 809 let wh = this;
michael@0 810
michael@0 811 function windowObserver(aSubject, aTopic, aData) {
michael@0 812 if (aTopic != "domwindowopened")
michael@0 813 return;
michael@0 814
michael@0 815 Services.ww.unregisterNotification(windowObserver);
michael@0 816
michael@0 817 var loaded = false;
michael@0 818 let win = aSubject.QueryInterface(Ci.nsIDOMWindow);
michael@0 819
michael@0 820 win.addEventListener("load", function onload(event) {
michael@0 821 win.removeEventListener("load", onload, false);
michael@0 822
michael@0 823 if (win.name !== "SanitizeDialog")
michael@0 824 return;
michael@0 825
michael@0 826 wh.win = win;
michael@0 827 loaded = true;
michael@0 828
michael@0 829 executeSoon(function () {
michael@0 830 // Some exceptions that reach here don't reach the test harness, but
michael@0 831 // ok()/is() do...
michael@0 832 try {
michael@0 833 wh.onload();
michael@0 834 }
michael@0 835 catch (exc) {
michael@0 836 win.close();
michael@0 837 ok(false, "Unexpected exception: " + exc + "\n" + exc.stack);
michael@0 838 finish();
michael@0 839 }
michael@0 840 });
michael@0 841 }, false);
michael@0 842
michael@0 843 win.addEventListener("unload", function onunload(event) {
michael@0 844 if (win.name !== "SanitizeDialog") {
michael@0 845 win.removeEventListener("unload", onunload, false);
michael@0 846 return;
michael@0 847 }
michael@0 848
michael@0 849 // Why is unload fired before load?
michael@0 850 if (!loaded)
michael@0 851 return;
michael@0 852
michael@0 853 win.removeEventListener("unload", onunload, false);
michael@0 854 wh.win = win;
michael@0 855
michael@0 856 executeSoon(function () {
michael@0 857 // Some exceptions that reach here don't reach the test harness, but
michael@0 858 // ok()/is() do...
michael@0 859 try {
michael@0 860 if (wh.onunload) {
michael@0 861 Task.spawn(wh.onunload).then(function() {
michael@0 862 waitForAsyncUpdates(doNextTest);
michael@0 863 }).then(null, Components.utils.reportError);
michael@0 864 } else {
michael@0 865 waitForAsyncUpdates(doNextTest);
michael@0 866 }
michael@0 867 }
michael@0 868 catch (exc) {
michael@0 869 win.close();
michael@0 870 ok(false, "Unexpected exception: " + exc + "\n" + exc.stack);
michael@0 871 finish();
michael@0 872 }
michael@0 873 });
michael@0 874 }, false);
michael@0 875 }
michael@0 876 Services.ww.registerNotification(windowObserver);
michael@0 877 Services.ww.openWindow(null,
michael@0 878 "chrome://browser/content/sanitize.xul",
michael@0 879 "SanitizeDialog",
michael@0 880 "chrome,titlebar,dialog,centerscreen,modal",
michael@0 881 null);
michael@0 882 },
michael@0 883
michael@0 884 /**
michael@0 885 * Selects a duration in the duration dropdown.
michael@0 886 *
michael@0 887 * @param aDurVal
michael@0 888 * One of the Sanitizer.TIMESPAN_* values
michael@0 889 */
michael@0 890 selectDuration: function (aDurVal) {
michael@0 891 this.getDurationDropdown().value = aDurVal;
michael@0 892 if (aDurVal === Sanitizer.TIMESPAN_EVERYTHING) {
michael@0 893 is(this.isWarningPanelVisible(), true,
michael@0 894 "Warning panel should be visible for TIMESPAN_EVERYTHING");
michael@0 895 }
michael@0 896 else {
michael@0 897 is(this.isWarningPanelVisible(), false,
michael@0 898 "Warning panel should not be visible for non-TIMESPAN_EVERYTHING");
michael@0 899 }
michael@0 900 },
michael@0 901
michael@0 902 /**
michael@0 903 * Toggles the details progressive disclosure button.
michael@0 904 */
michael@0 905 toggleDetails: function () {
michael@0 906 this.getDetailsButton().click();
michael@0 907 }
michael@0 908 };
michael@0 909
michael@0 910 /**
michael@0 911 * Adds a download to history.
michael@0 912 *
michael@0 913 * @param aMinutesAgo
michael@0 914 * The download will be downloaded this many minutes ago
michael@0 915 */
michael@0 916 function addDownloadWithMinutesAgo(aExpectedPathList, aMinutesAgo) {
michael@0 917 let publicList = yield Downloads.getList(Downloads.PUBLIC);
michael@0 918
michael@0 919 let name = "fakefile-" + aMinutesAgo + "-minutes-ago";
michael@0 920 let download = yield Downloads.createDownload({
michael@0 921 source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169",
michael@0 922 target: name
michael@0 923 });
michael@0 924 download.startTime = new Date(now_mSec - (aMinutesAgo * kMsecPerMin));
michael@0 925 download.canceled = true;
michael@0 926 publicList.add(download);
michael@0 927
michael@0 928 ok((yield downloadExists(name)),
michael@0 929 "Sanity check: download " + name +
michael@0 930 " should exist after creating it");
michael@0 931
michael@0 932 aExpectedPathList.push(name);
michael@0 933 }
michael@0 934
michael@0 935 /**
michael@0 936 * Adds a form entry to history.
michael@0 937 *
michael@0 938 * @param aMinutesAgo
michael@0 939 * The entry will be added this many minutes ago
michael@0 940 */
michael@0 941 function addFormEntryWithMinutesAgo(then, aMinutesAgo) {
michael@0 942 let name = aMinutesAgo + "-minutes-ago";
michael@0 943
michael@0 944 // Artifically age the entry to the proper vintage.
michael@0 945 let timestamp = now_uSec - (aMinutesAgo * kUsecPerMin);
michael@0 946
michael@0 947 FormHistory.update({ op: "add", fieldname: name, value: "dummy", firstUsed: timestamp },
michael@0 948 { handleError: function (error) {
michael@0 949 do_throw("Error occurred updating form history: " + error);
michael@0 950 },
michael@0 951 handleCompletion: function (reason) { then.next(); }
michael@0 952 });
michael@0 953 return name;
michael@0 954 }
michael@0 955
michael@0 956 /**
michael@0 957 * Checks if a form entry exists.
michael@0 958 */
michael@0 959 function formNameExists(name)
michael@0 960 {
michael@0 961 let deferred = Promise.defer();
michael@0 962
michael@0 963 let count = 0;
michael@0 964 FormHistory.count({ fieldname: name },
michael@0 965 { handleResult: function (result) count = result,
michael@0 966 handleError: function (error) {
michael@0 967 do_throw("Error occurred searching form history: " + error);
michael@0 968 deferred.reject(error);
michael@0 969 },
michael@0 970 handleCompletion: function (reason) {
michael@0 971 if (!reason) deferred.resolve(count);
michael@0 972 }
michael@0 973 });
michael@0 974
michael@0 975 return deferred.promise;
michael@0 976 }
michael@0 977
michael@0 978 /**
michael@0 979 * Removes all history visits, downloads, and form entries.
michael@0 980 */
michael@0 981 function blankSlate() {
michael@0 982 PlacesUtils.bhistory.removeAllPages();
michael@0 983
michael@0 984 // The promise is resolved only when removing both downloads and form history are done.
michael@0 985 let deferred = Promise.defer();
michael@0 986 let formHistoryDone = false, downloadsDone = false;
michael@0 987
michael@0 988 Task.spawn(function deleteAllDownloads() {
michael@0 989 let publicList = yield Downloads.getList(Downloads.PUBLIC);
michael@0 990 let downloads = yield publicList.getAll();
michael@0 991 for (let download of downloads) {
michael@0 992 yield publicList.remove(download);
michael@0 993 yield download.finalize(true);
michael@0 994 }
michael@0 995 downloadsDone = true;
michael@0 996 if (formHistoryDone) {
michael@0 997 deferred.resolve();
michael@0 998 }
michael@0 999 }).then(null, Components.utils.reportError);
michael@0 1000
michael@0 1001 FormHistory.update({ op: "remove" },
michael@0 1002 { handleError: function (error) {
michael@0 1003 do_throw("Error occurred updating form history: " + error);
michael@0 1004 deferred.reject(error);
michael@0 1005 },
michael@0 1006 handleCompletion: function (reason) {
michael@0 1007 if (!reason) {
michael@0 1008 formHistoryDone = true;
michael@0 1009 if (downloadsDone) {
michael@0 1010 deferred.resolve();
michael@0 1011 }
michael@0 1012 }
michael@0 1013 }
michael@0 1014 });
michael@0 1015 return deferred.promise;
michael@0 1016 }
michael@0 1017
michael@0 1018 /**
michael@0 1019 * Ensures that the given pref is the expected value.
michael@0 1020 *
michael@0 1021 * @param aPrefName
michael@0 1022 * The pref's sub-branch under the privacy branch
michael@0 1023 * @param aExpectedVal
michael@0 1024 * The pref's expected value
michael@0 1025 * @param aMsg
michael@0 1026 * Passed to is()
michael@0 1027 */
michael@0 1028 function boolPrefIs(aPrefName, aExpectedVal, aMsg) {
michael@0 1029 is(gPrefService.getBoolPref("privacy." + aPrefName), aExpectedVal, aMsg);
michael@0 1030 }
michael@0 1031
michael@0 1032 /**
michael@0 1033 * Checks to see if the download with the specified path exists.
michael@0 1034 *
michael@0 1035 * @param aPath
michael@0 1036 * The path of the download to check
michael@0 1037 * @return True if the download exists, false otherwise
michael@0 1038 */
michael@0 1039 function downloadExists(aPath)
michael@0 1040 {
michael@0 1041 return Task.spawn(function() {
michael@0 1042 let publicList = yield Downloads.getList(Downloads.PUBLIC);
michael@0 1043 let listArray = yield publicList.getAll();
michael@0 1044 throw new Task.Result(listArray.some(i => i.target.path == aPath));
michael@0 1045 });
michael@0 1046 }
michael@0 1047
michael@0 1048 /**
michael@0 1049 * Runs the next test in the gAllTests array. If all tests have been run,
michael@0 1050 * finishes the entire suite.
michael@0 1051 */
michael@0 1052 function doNextTest() {
michael@0 1053 if (gAllTests.length <= gCurrTest) {
michael@0 1054 blankSlate();
michael@0 1055 waitForAsyncUpdates(finish);
michael@0 1056 }
michael@0 1057 else {
michael@0 1058 let ct = gCurrTest;
michael@0 1059 gCurrTest++;
michael@0 1060 gAllTests[ct]();
michael@0 1061 }
michael@0 1062 }
michael@0 1063
michael@0 1064 /**
michael@0 1065 * Ensures that the specified downloads are either cleared or not.
michael@0 1066 *
michael@0 1067 * @param aDownloadIDs
michael@0 1068 * Array of download database IDs
michael@0 1069 * @param aShouldBeCleared
michael@0 1070 * True if each download should be cleared, false otherwise
michael@0 1071 */
michael@0 1072 function ensureDownloadsClearedState(aDownloadIDs, aShouldBeCleared) {
michael@0 1073 let niceStr = aShouldBeCleared ? "no longer" : "still";
michael@0 1074 aDownloadIDs.forEach(function (id) {
michael@0 1075 is((yield downloadExists(id)), !aShouldBeCleared,
michael@0 1076 "download " + id + " should " + niceStr + " exist");
michael@0 1077 });
michael@0 1078 }
michael@0 1079
michael@0 1080 /**
michael@0 1081 * Ensures that the given pref is the expected value.
michael@0 1082 *
michael@0 1083 * @param aPrefName
michael@0 1084 * The pref's sub-branch under the privacy branch
michael@0 1085 * @param aExpectedVal
michael@0 1086 * The pref's expected value
michael@0 1087 * @param aMsg
michael@0 1088 * Passed to is()
michael@0 1089 */
michael@0 1090 function intPrefIs(aPrefName, aExpectedVal, aMsg) {
michael@0 1091 is(gPrefService.getIntPref("privacy." + aPrefName), aExpectedVal, aMsg);
michael@0 1092 }
michael@0 1093
michael@0 1094 /**
michael@0 1095 * Creates a visit time.
michael@0 1096 *
michael@0 1097 * @param aMinutesAgo
michael@0 1098 * The visit will be visited this many minutes ago
michael@0 1099 */
michael@0 1100 function visitTimeForMinutesAgo(aMinutesAgo) {
michael@0 1101 return now_uSec - aMinutesAgo * kUsecPerMin;
michael@0 1102 }
michael@0 1103
michael@0 1104 ///////////////////////////////////////////////////////////////////////////////
michael@0 1105
michael@0 1106 function test() {
michael@0 1107 requestLongerTimeout(2);
michael@0 1108 waitForExplicitFinish();
michael@0 1109 blankSlate();
michael@0 1110 // Kick off all the tests in the gAllTests array.
michael@0 1111 waitForAsyncUpdates(doNextTest);
michael@0 1112 }

mercurial