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