1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/base/content/test/general/browser_sanitizeDialog.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1112 @@ 1.4 +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim:set ts=2 sw=2 sts=2 et: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +/** 1.11 + * Tests the sanitize dialog (a.k.a. the clear recent history dialog). 1.12 + * See bug 480169. 1.13 + * 1.14 + * The purpose of this test is not to fully flex the sanitize timespan code; 1.15 + * browser/base/content/test/general/browser_sanitize-timespans.js does that. This 1.16 + * test checks the UI of the dialog and makes sure it's correctly connected to 1.17 + * the sanitize timespan code. 1.18 + * 1.19 + * Some of this code, especially the history creation parts, was taken from 1.20 + * browser/base/content/test/general/browser_sanitize-timespans.js. 1.21 + */ 1.22 + 1.23 +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); 1.24 + 1.25 +XPCOMUtils.defineLazyModuleGetter(this, "FormHistory", 1.26 + "resource://gre/modules/FormHistory.jsm"); 1.27 +XPCOMUtils.defineLazyModuleGetter(this, "Downloads", 1.28 + "resource://gre/modules/Downloads.jsm"); 1.29 + 1.30 +let tempScope = {}; 1.31 +Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader) 1.32 + .loadSubScript("chrome://browser/content/sanitize.js", tempScope); 1.33 +let Sanitizer = tempScope.Sanitizer; 1.34 + 1.35 +const kMsecPerMin = 60 * 1000; 1.36 +const kUsecPerMin = 60 * 1000000; 1.37 + 1.38 +let formEntries, downloadIDs, olderDownloadIDs; 1.39 + 1.40 +// Add tests here. Each is a function that's called by doNextTest(). 1.41 +var gAllTests = [ 1.42 + 1.43 + /** 1.44 + * Initializes the dialog to its default state. 1.45 + */ 1.46 + function () { 1.47 + let wh = new WindowHelper(); 1.48 + wh.onload = function () { 1.49 + // Select "Last Hour" 1.50 + this.selectDuration(Sanitizer.TIMESPAN_HOUR); 1.51 + // Hide details 1.52 + if (!this.getItemList().collapsed) 1.53 + this.toggleDetails(); 1.54 + this.acceptDialog(); 1.55 + }; 1.56 + wh.open(); 1.57 + }, 1.58 + 1.59 + /** 1.60 + * Cancels the dialog, makes sure history not cleared. 1.61 + */ 1.62 + function () { 1.63 + // Add history (within the past hour) 1.64 + let uris = []; 1.65 + let places = []; 1.66 + let pURI; 1.67 + for (let i = 0; i < 30; i++) { 1.68 + pURI = makeURI("http://" + i + "-minutes-ago.com/"); 1.69 + places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(i)}); 1.70 + uris.push(pURI); 1.71 + } 1.72 + 1.73 + addVisits(places, function() { 1.74 + let wh = new WindowHelper(); 1.75 + wh.onload = function () { 1.76 + this.selectDuration(Sanitizer.TIMESPAN_HOUR); 1.77 + this.checkPrefCheckbox("history", false); 1.78 + this.checkDetails(false); 1.79 + 1.80 + // Show details 1.81 + this.toggleDetails(); 1.82 + this.checkDetails(true); 1.83 + 1.84 + // Hide details 1.85 + this.toggleDetails(); 1.86 + this.checkDetails(false); 1.87 + this.cancelDialog(); 1.88 + }; 1.89 + wh.onunload = function () { 1.90 + yield promiseHistoryClearedState(uris, false); 1.91 + yield blankSlate(); 1.92 + yield promiseHistoryClearedState(uris, true); 1.93 + }; 1.94 + wh.open(); 1.95 + }); 1.96 + }, 1.97 + 1.98 + function () { 1.99 + // Add downloads (within the past hour). 1.100 + Task.spawn(function () { 1.101 + downloadIDs = []; 1.102 + for (let i = 0; i < 5; i++) { 1.103 + yield addDownloadWithMinutesAgo(downloadIDs, i); 1.104 + } 1.105 + // Add downloads (over an hour ago). 1.106 + olderDownloadIDs = []; 1.107 + for (let i = 0; i < 5; i++) { 1.108 + yield addDownloadWithMinutesAgo(olderDownloadIDs, 61 + i); 1.109 + } 1.110 + 1.111 + doNextTest(); 1.112 + }).then(null, Components.utils.reportError); 1.113 + }, 1.114 + 1.115 + /** 1.116 + * Ensures that the combined history-downloads checkbox clears both history 1.117 + * visits and downloads when checked; the dialog respects simple timespan. 1.118 + */ 1.119 + function () { 1.120 + // Add history (within the past hour). 1.121 + let uris = []; 1.122 + let places = []; 1.123 + let pURI; 1.124 + for (let i = 0; i < 30; i++) { 1.125 + pURI = makeURI("http://" + i + "-minutes-ago.com/"); 1.126 + places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(i)}); 1.127 + uris.push(pURI); 1.128 + } 1.129 + // Add history (over an hour ago). 1.130 + let olderURIs = []; 1.131 + for (let i = 0; i < 5; i++) { 1.132 + pURI = makeURI("http://" + (61 + i) + "-minutes-ago.com/"); 1.133 + places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(61 + i)}); 1.134 + olderURIs.push(pURI); 1.135 + } 1.136 + 1.137 + addVisits(places, function() { 1.138 + let totalHistoryVisits = uris.length + olderURIs.length; 1.139 + 1.140 + let wh = new WindowHelper(); 1.141 + wh.onload = function () { 1.142 + this.selectDuration(Sanitizer.TIMESPAN_HOUR); 1.143 + this.checkPrefCheckbox("history", true); 1.144 + this.acceptDialog(); 1.145 + }; 1.146 + wh.onunload = function () { 1.147 + intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_HOUR, 1.148 + "timeSpan pref should be hour after accepting dialog with " + 1.149 + "hour selected"); 1.150 + boolPrefIs("cpd.history", true, 1.151 + "history pref should be true after accepting dialog with " + 1.152 + "history checkbox checked"); 1.153 + boolPrefIs("cpd.downloads", true, 1.154 + "downloads pref should be true after accepting dialog with " + 1.155 + "history checkbox checked"); 1.156 + 1.157 + // History visits and downloads within one hour should be cleared. 1.158 + yield promiseHistoryClearedState(uris, true); 1.159 + yield ensureDownloadsClearedState(downloadIDs, true); 1.160 + 1.161 + // Visits and downloads > 1 hour should still exist. 1.162 + yield promiseHistoryClearedState(olderURIs, false); 1.163 + yield ensureDownloadsClearedState(olderDownloadIDs, false); 1.164 + 1.165 + // OK, done, cleanup after ourselves. 1.166 + yield blankSlate(); 1.167 + yield promiseHistoryClearedState(olderURIs, true); 1.168 + yield ensureDownloadsClearedState(olderDownloadIDs, true); 1.169 + }; 1.170 + wh.open(); 1.171 + }); 1.172 + }, 1.173 + 1.174 + /** 1.175 + * Add form history entries for the next test. 1.176 + */ 1.177 + function () { 1.178 + formEntries = []; 1.179 + 1.180 + let iter = function() { 1.181 + for (let i = 0; i < 5; i++) { 1.182 + formEntries.push(addFormEntryWithMinutesAgo(iter, i)); 1.183 + yield undefined; 1.184 + } 1.185 + doNextTest(); 1.186 + }(); 1.187 + 1.188 + iter.next(); 1.189 + }, 1.190 + 1.191 + function () { 1.192 + // Add downloads (within the past hour). 1.193 + Task.spawn(function () { 1.194 + downloadIDs = []; 1.195 + for (let i = 0; i < 5; i++) { 1.196 + yield addDownloadWithMinutesAgo(downloadIDs, i); 1.197 + } 1.198 + 1.199 + doNextTest(); 1.200 + }).then(null, Components.utils.reportError); 1.201 + }, 1.202 + 1.203 + /** 1.204 + * Ensures that the combined history-downloads checkbox removes neither 1.205 + * history visits nor downloads when not checked. 1.206 + */ 1.207 + function () { 1.208 + // Add history, downloads, form entries (within the past hour). 1.209 + let uris = []; 1.210 + let places = []; 1.211 + let pURI; 1.212 + for (let i = 0; i < 5; i++) { 1.213 + pURI = makeURI("http://" + i + "-minutes-ago.com/"); 1.214 + places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(i)}); 1.215 + uris.push(pURI); 1.216 + } 1.217 + 1.218 + addVisits(places, function() { 1.219 + let wh = new WindowHelper(); 1.220 + wh.onload = function () { 1.221 + is(this.isWarningPanelVisible(), false, 1.222 + "Warning panel should be hidden after previously accepting dialog " + 1.223 + "with a predefined timespan"); 1.224 + this.selectDuration(Sanitizer.TIMESPAN_HOUR); 1.225 + 1.226 + // Remove only form entries, leave history (including downloads). 1.227 + this.checkPrefCheckbox("history", false); 1.228 + this.checkPrefCheckbox("formdata", true); 1.229 + this.acceptDialog(); 1.230 + }; 1.231 + wh.onunload = function () { 1.232 + intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_HOUR, 1.233 + "timeSpan pref should be hour after accepting dialog with " + 1.234 + "hour selected"); 1.235 + boolPrefIs("cpd.history", false, 1.236 + "history pref should be false after accepting dialog with " + 1.237 + "history checkbox unchecked"); 1.238 + boolPrefIs("cpd.downloads", false, 1.239 + "downloads pref should be false after accepting dialog with " + 1.240 + "history checkbox unchecked"); 1.241 + 1.242 + // Of the three only form entries should be cleared. 1.243 + yield promiseHistoryClearedState(uris, false); 1.244 + yield ensureDownloadsClearedState(downloadIDs, false); 1.245 + 1.246 + formEntries.forEach(function (entry) { 1.247 + let exists = yield formNameExists(entry); 1.248 + is(exists, false, "form entry " + entry + " should no longer exist"); 1.249 + }); 1.250 + 1.251 + // OK, done, cleanup after ourselves. 1.252 + yield blankSlate(); 1.253 + yield promiseHistoryClearedState(uris, true); 1.254 + yield ensureDownloadsClearedState(downloadIDs, true); 1.255 + }; 1.256 + wh.open(); 1.257 + }); 1.258 + }, 1.259 + 1.260 + /** 1.261 + * Ensures that the "Everything" duration option works. 1.262 + */ 1.263 + function () { 1.264 + // Add history. 1.265 + let uris = []; 1.266 + let places = []; 1.267 + let pURI; 1.268 + // within past hour, within past two hours, within past four hours and 1.269 + // outside past four hours 1.270 + [10, 70, 130, 250].forEach(function(aValue) { 1.271 + pURI = makeURI("http://" + aValue + "-minutes-ago.com/"); 1.272 + places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(aValue)}); 1.273 + uris.push(pURI); 1.274 + }); 1.275 + addVisits(places, function() { 1.276 + let wh = new WindowHelper(); 1.277 + wh.onload = function () { 1.278 + is(this.isWarningPanelVisible(), false, 1.279 + "Warning panel should be hidden after previously accepting dialog " + 1.280 + "with a predefined timespan"); 1.281 + this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING); 1.282 + this.checkPrefCheckbox("history", true); 1.283 + this.checkDetails(true); 1.284 + 1.285 + // Hide details 1.286 + this.toggleDetails(); 1.287 + this.checkDetails(false); 1.288 + 1.289 + // Show details 1.290 + this.toggleDetails(); 1.291 + this.checkDetails(true); 1.292 + 1.293 + this.acceptDialog(); 1.294 + }; 1.295 + wh.onunload = function () { 1.296 + intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_EVERYTHING, 1.297 + "timeSpan pref should be everything after accepting dialog " + 1.298 + "with everything selected"); 1.299 + 1.300 + yield promiseHistoryClearedState(uris, true); 1.301 + }; 1.302 + wh.open(); 1.303 + }); 1.304 + }, 1.305 + 1.306 + /** 1.307 + * Ensures that the "Everything" warning is visible on dialog open after 1.308 + * the previous test. 1.309 + */ 1.310 + function () { 1.311 + // Add history. 1.312 + let uris = []; 1.313 + let places = []; 1.314 + let pURI; 1.315 + // within past hour, within past two hours, within past four hours and 1.316 + // outside past four hours 1.317 + [10, 70, 130, 250].forEach(function(aValue) { 1.318 + pURI = makeURI("http://" + aValue + "-minutes-ago.com/"); 1.319 + places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(aValue)}); 1.320 + uris.push(pURI); 1.321 + }); 1.322 + addVisits(places, function() { 1.323 + let wh = new WindowHelper(); 1.324 + wh.onload = function () { 1.325 + is(this.isWarningPanelVisible(), true, 1.326 + "Warning panel should be visible after previously accepting dialog " + 1.327 + "with clearing everything"); 1.328 + this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING); 1.329 + this.checkPrefCheckbox("history", true); 1.330 + this.acceptDialog(); 1.331 + }; 1.332 + wh.onunload = function () { 1.333 + intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_EVERYTHING, 1.334 + "timeSpan pref should be everything after accepting dialog " + 1.335 + "with everything selected"); 1.336 + 1.337 + yield promiseHistoryClearedState(uris, true); 1.338 + }; 1.339 + wh.open(); 1.340 + }); 1.341 + }, 1.342 + 1.343 + /** 1.344 + * Add form history entry for the next test. 1.345 + */ 1.346 + function () { 1.347 + let iter = function() { 1.348 + formEntries = [ addFormEntryWithMinutesAgo(iter, 10) ]; 1.349 + yield undefined; 1.350 + doNextTest(); 1.351 + }(); 1.352 + 1.353 + iter.next(); 1.354 + }, 1.355 + 1.356 + /** 1.357 + * The next three tests checks that when a certain history item cannot be 1.358 + * cleared then the checkbox should be both disabled and unchecked. 1.359 + * In addition, we ensure that this behavior does not modify the preferences. 1.360 + */ 1.361 + function () { 1.362 + // Add history. 1.363 + let pURI = makeURI("http://" + 10 + "-minutes-ago.com/"); 1.364 + addVisits({uri: pURI, visitDate: visitTimeForMinutesAgo(10)}, function() { 1.365 + let uris = [ pURI ]; 1.366 + 1.367 + let wh = new WindowHelper(); 1.368 + wh.onload = function() { 1.369 + // Check that the relevant checkboxes are enabled 1.370 + var cb = this.win.document.querySelectorAll( 1.371 + "#itemList > [preference='privacy.cpd.formdata']"); 1.372 + ok(cb.length == 1 && !cb[0].disabled, "There is formdata, checkbox to " + 1.373 + "clear formdata should be enabled."); 1.374 + 1.375 + var cb = this.win.document.querySelectorAll( 1.376 + "#itemList > [preference='privacy.cpd.history']"); 1.377 + ok(cb.length == 1 && !cb[0].disabled, "There is history, checkbox to " + 1.378 + "clear history should be enabled."); 1.379 + 1.380 + this.checkAllCheckboxes(); 1.381 + this.acceptDialog(); 1.382 + }; 1.383 + wh.onunload = function () { 1.384 + yield promiseHistoryClearedState(uris, true); 1.385 + 1.386 + let exists = yield formNameExists(formEntries[0]); 1.387 + is(exists, false, "form entry " + formEntries[0] + " should no longer exist"); 1.388 + }; 1.389 + wh.open(); 1.390 + }); 1.391 + }, 1.392 + function () { 1.393 + let wh = new WindowHelper(); 1.394 + wh.onload = function() { 1.395 + boolPrefIs("cpd.history", true, 1.396 + "history pref should be true after accepting dialog with " + 1.397 + "history checkbox checked"); 1.398 + boolPrefIs("cpd.formdata", true, 1.399 + "formdata pref should be true after accepting dialog with " + 1.400 + "formdata checkbox checked"); 1.401 + 1.402 + 1.403 + // Even though the formdata pref is true, because there is no history 1.404 + // left to clear, the checkbox will be disabled. 1.405 + var cb = this.win.document.querySelectorAll( 1.406 + "#itemList > [preference='privacy.cpd.formdata']"); 1.407 + ok(cb.length == 1 && cb[0].disabled && !cb[0].checked, 1.408 + "There is no formdata history, checkbox should be disabled and be " + 1.409 + "cleared to reduce user confusion (bug 497664)."); 1.410 + 1.411 + var cb = this.win.document.querySelectorAll( 1.412 + "#itemList > [preference='privacy.cpd.history']"); 1.413 + ok(cb.length == 1 && !cb[0].disabled && cb[0].checked, 1.414 + "There is no history, but history checkbox should always be enabled " + 1.415 + "and will be checked from previous preference."); 1.416 + 1.417 + this.acceptDialog(); 1.418 + } 1.419 + wh.open(); 1.420 + }, 1.421 + 1.422 + /** 1.423 + * Add form history entry for the next test. 1.424 + */ 1.425 + function () { 1.426 + let iter = function() { 1.427 + formEntries = [ addFormEntryWithMinutesAgo(iter, 10) ]; 1.428 + yield undefined; 1.429 + doNextTest(); 1.430 + }(); 1.431 + 1.432 + iter.next(); 1.433 + }, 1.434 + 1.435 + function () { 1.436 + let wh = new WindowHelper(); 1.437 + wh.onload = function() { 1.438 + boolPrefIs("cpd.formdata", true, 1.439 + "formdata pref should persist previous value after accepting " + 1.440 + "dialog where you could not clear formdata."); 1.441 + 1.442 + var cb = this.win.document.querySelectorAll( 1.443 + "#itemList > [preference='privacy.cpd.formdata']"); 1.444 + ok(cb.length == 1 && !cb[0].disabled && cb[0].checked, 1.445 + "There exists formEntries so the checkbox should be in sync with " + 1.446 + "the pref."); 1.447 + 1.448 + this.acceptDialog(); 1.449 + }; 1.450 + wh.onunload = function () { 1.451 + let exists = yield formNameExists(formEntries[0]); 1.452 + is(exists, false, "form entry " + formEntries[0] + " should no longer exist"); 1.453 + }; 1.454 + wh.open(); 1.455 + }, 1.456 + 1.457 + 1.458 + /** 1.459 + * These next six tests together ensure that toggling details persists 1.460 + * across dialog openings. 1.461 + */ 1.462 + function () { 1.463 + let wh = new WindowHelper(); 1.464 + wh.onload = function () { 1.465 + // Check all items and select "Everything" 1.466 + this.checkAllCheckboxes(); 1.467 + this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING); 1.468 + 1.469 + // Hide details 1.470 + this.toggleDetails(); 1.471 + this.checkDetails(false); 1.472 + this.acceptDialog(); 1.473 + }; 1.474 + wh.open(); 1.475 + }, 1.476 + function () { 1.477 + let wh = new WindowHelper(); 1.478 + wh.onload = function () { 1.479 + // Details should remain closed because all items are checked. 1.480 + this.checkDetails(false); 1.481 + 1.482 + // Uncheck history. 1.483 + this.checkPrefCheckbox("history", false); 1.484 + this.acceptDialog(); 1.485 + }; 1.486 + wh.open(); 1.487 + }, 1.488 + function () { 1.489 + let wh = new WindowHelper(); 1.490 + wh.onload = function () { 1.491 + // Details should be open because not all items are checked. 1.492 + this.checkDetails(true); 1.493 + 1.494 + // Modify the Site Preferences item state (bug 527820) 1.495 + this.checkAllCheckboxes(); 1.496 + this.checkPrefCheckbox("siteSettings", false); 1.497 + this.acceptDialog(); 1.498 + }; 1.499 + wh.open(); 1.500 + }, 1.501 + function () { 1.502 + let wh = new WindowHelper(); 1.503 + wh.onload = function () { 1.504 + // Details should be open because not all items are checked. 1.505 + this.checkDetails(true); 1.506 + 1.507 + // Hide details 1.508 + this.toggleDetails(); 1.509 + this.checkDetails(false); 1.510 + this.cancelDialog(); 1.511 + }; 1.512 + wh.open(); 1.513 + }, 1.514 + function () { 1.515 + let wh = new WindowHelper(); 1.516 + wh.onload = function () { 1.517 + // Details should be open because not all items are checked. 1.518 + this.checkDetails(true); 1.519 + 1.520 + // Select another duration 1.521 + this.selectDuration(Sanitizer.TIMESPAN_HOUR); 1.522 + // Hide details 1.523 + this.toggleDetails(); 1.524 + this.checkDetails(false); 1.525 + this.acceptDialog(); 1.526 + }; 1.527 + wh.open(); 1.528 + }, 1.529 + function () { 1.530 + let wh = new WindowHelper(); 1.531 + wh.onload = function () { 1.532 + // Details should not be open because "Last Hour" is selected 1.533 + this.checkDetails(false); 1.534 + 1.535 + this.cancelDialog(); 1.536 + }; 1.537 + wh.open(); 1.538 + }, 1.539 + function () { 1.540 + let wh = new WindowHelper(); 1.541 + wh.onload = function () { 1.542 + // Details should have remained closed 1.543 + this.checkDetails(false); 1.544 + 1.545 + // Show details 1.546 + this.toggleDetails(); 1.547 + this.checkDetails(true); 1.548 + this.cancelDialog(); 1.549 + }; 1.550 + wh.open(); 1.551 + }, 1.552 + function () { 1.553 + // Test for offline cache deletion 1.554 + 1.555 + // Prepare stuff, we will work with www.example.com 1.556 + var URL = "http://www.example.com"; 1.557 + 1.558 + var ios = Cc["@mozilla.org/network/io-service;1"] 1.559 + .getService(Ci.nsIIOService); 1.560 + var URI = ios.newURI(URL, null, null); 1.561 + 1.562 + var sm = Cc["@mozilla.org/scriptsecuritymanager;1"] 1.563 + .getService(Ci.nsIScriptSecurityManager); 1.564 + var principal = sm.getNoAppCodebasePrincipal(URI); 1.565 + 1.566 + // Give www.example.com privileges to store offline data 1.567 + var pm = Cc["@mozilla.org/permissionmanager;1"] 1.568 + .getService(Ci.nsIPermissionManager); 1.569 + pm.addFromPrincipal(principal, "offline-app", Ci.nsIPermissionManager.ALLOW_ACTION); 1.570 + pm.addFromPrincipal(principal, "offline-app", Ci.nsIOfflineCacheUpdateService.ALLOW_NO_WARN); 1.571 + 1.572 + // Store something to the offline cache 1.573 + const nsICache = Components.interfaces.nsICache; 1.574 + var cs = Components.classes["@mozilla.org/network/cache-service;1"] 1.575 + .getService(Components.interfaces.nsICacheService); 1.576 + var session = cs.createSession(URL + "/manifest", nsICache.STORE_OFFLINE, nsICache.STREAM_BASED); 1.577 + 1.578 + // Open the dialog 1.579 + let wh = new WindowHelper(); 1.580 + wh.onload = function () { 1.581 + this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING); 1.582 + // Show details 1.583 + this.toggleDetails(); 1.584 + // Clear only offlineApps 1.585 + this.uncheckAllCheckboxes(); 1.586 + this.checkPrefCheckbox("offlineApps", true); 1.587 + this.acceptDialog(); 1.588 + }; 1.589 + wh.onunload = function () { 1.590 + // Check if the cache has been deleted 1.591 + var size = -1; 1.592 + var visitor = { 1.593 + visitDevice: function (deviceID, deviceInfo) 1.594 + { 1.595 + if (deviceID == "offline") 1.596 + size = deviceInfo.totalSize; 1.597 + 1.598 + // Do not enumerate entries 1.599 + return false; 1.600 + }, 1.601 + 1.602 + visitEntry: function (deviceID, entryInfo) 1.603 + { 1.604 + // Do not enumerate entries. 1.605 + return false; 1.606 + } 1.607 + }; 1.608 + cs.visitEntries(visitor); 1.609 + is(size, 0, "offline application cache entries evicted"); 1.610 + }; 1.611 + 1.612 + var cacheListener = { 1.613 + onCacheEntryAvailable: function (entry, access, status) { 1.614 + is(status, Cr.NS_OK); 1.615 + var stream = entry.openOutputStream(0); 1.616 + var content = "content"; 1.617 + stream.write(content, content.length); 1.618 + stream.close(); 1.619 + entry.close(); 1.620 + wh.open(); 1.621 + } 1.622 + }; 1.623 + 1.624 + session.asyncOpenCacheEntry(URL, nsICache.ACCESS_READ_WRITE, cacheListener); 1.625 + }, 1.626 + function () { 1.627 + // Test for offline apps permission deletion 1.628 + 1.629 + // Prepare stuff, we will work with www.example.com 1.630 + var URL = "http://www.example.com"; 1.631 + 1.632 + var ios = Cc["@mozilla.org/network/io-service;1"] 1.633 + .getService(Ci.nsIIOService); 1.634 + var URI = ios.newURI(URL, null, null); 1.635 + 1.636 + var sm = Cc["@mozilla.org/scriptsecuritymanager;1"] 1.637 + .getService(Ci.nsIScriptSecurityManager); 1.638 + var principal = sm.getNoAppCodebasePrincipal(URI); 1.639 + 1.640 + // Open the dialog 1.641 + let wh = new WindowHelper(); 1.642 + wh.onload = function () { 1.643 + this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING); 1.644 + // Show details 1.645 + this.toggleDetails(); 1.646 + // Clear only offlineApps 1.647 + this.uncheckAllCheckboxes(); 1.648 + this.checkPrefCheckbox("siteSettings", true); 1.649 + this.acceptDialog(); 1.650 + }; 1.651 + wh.onunload = function () { 1.652 + // Check all has been deleted (privileges, data, cache) 1.653 + var pm = Cc["@mozilla.org/permissionmanager;1"] 1.654 + .getService(Ci.nsIPermissionManager); 1.655 + is(pm.testPermissionFromPrincipal(principal, "offline-app"), 0, "offline-app permissions removed"); 1.656 + }; 1.657 + wh.open(); 1.658 + } 1.659 +]; 1.660 + 1.661 +// Index in gAllTests of the test currently being run. Incremented for each 1.662 +// test run. See doNextTest(). 1.663 +var gCurrTest = 0; 1.664 + 1.665 +let now_mSec = Date.now(); 1.666 +let now_uSec = now_mSec * 1000; 1.667 + 1.668 +/////////////////////////////////////////////////////////////////////////////// 1.669 + 1.670 +/** 1.671 + * This wraps the dialog and provides some convenience methods for interacting 1.672 + * with it. 1.673 + * 1.674 + * @param aWin 1.675 + * The dialog's nsIDOMWindow 1.676 + */ 1.677 +function WindowHelper(aWin) { 1.678 + this.win = aWin; 1.679 +} 1.680 + 1.681 +WindowHelper.prototype = { 1.682 + /** 1.683 + * "Presses" the dialog's OK button. 1.684 + */ 1.685 + acceptDialog: function () { 1.686 + is(this.win.document.documentElement.getButton("accept").disabled, false, 1.687 + "Dialog's OK button should not be disabled"); 1.688 + this.win.document.documentElement.acceptDialog(); 1.689 + }, 1.690 + 1.691 + /** 1.692 + * "Presses" the dialog's Cancel button. 1.693 + */ 1.694 + cancelDialog: function () { 1.695 + this.win.document.documentElement.cancelDialog(); 1.696 + }, 1.697 + 1.698 + /** 1.699 + * Ensures that the details progressive disclosure button and the item list 1.700 + * hidden by it match up. Also makes sure the height of the dialog is 1.701 + * sufficient for the item list and warning panel. 1.702 + * 1.703 + * @param aShouldBeShown 1.704 + * True if you expect the details to be shown and false if hidden 1.705 + */ 1.706 + checkDetails: function (aShouldBeShown) { 1.707 + let button = this.getDetailsButton(); 1.708 + let list = this.getItemList(); 1.709 + let hidden = list.hidden || list.collapsed; 1.710 + is(hidden, !aShouldBeShown, 1.711 + "Details should be " + (aShouldBeShown ? "shown" : "hidden") + 1.712 + " but were actually " + (hidden ? "hidden" : "shown")); 1.713 + let dir = hidden ? "down" : "up"; 1.714 + is(button.className, "expander-" + dir, 1.715 + "Details button should be " + dir + " because item list is " + 1.716 + (hidden ? "" : "not ") + "hidden"); 1.717 + let height = 0; 1.718 + if (!hidden) { 1.719 + ok(list.boxObject.height > 30, "listbox has sufficient size") 1.720 + height += list.boxObject.height; 1.721 + } 1.722 + if (this.isWarningPanelVisible()) 1.723 + height += this.getWarningPanel().boxObject.height; 1.724 + ok(height < this.win.innerHeight, 1.725 + "Window should be tall enough to fit warning panel and item list"); 1.726 + }, 1.727 + 1.728 + /** 1.729 + * (Un)checks a history scope checkbox (browser & download history, 1.730 + * form history, etc.). 1.731 + * 1.732 + * @param aPrefName 1.733 + * The final portion of the checkbox's privacy.cpd.* preference name 1.734 + * @param aCheckState 1.735 + * True if the checkbox should be checked, false otherwise 1.736 + */ 1.737 + checkPrefCheckbox: function (aPrefName, aCheckState) { 1.738 + var pref = "privacy.cpd." + aPrefName; 1.739 + var cb = this.win.document.querySelectorAll( 1.740 + "#itemList > [preference='" + pref + "']"); 1.741 + is(cb.length, 1, "found checkbox for " + pref + " preference"); 1.742 + if (cb[0].checked != aCheckState) 1.743 + cb[0].click(); 1.744 + }, 1.745 + 1.746 + /** 1.747 + * Makes sure all the checkboxes are checked. 1.748 + */ 1.749 + _checkAllCheckboxesCustom: function (check) { 1.750 + var cb = this.win.document.querySelectorAll("#itemList > [preference]"); 1.751 + ok(cb.length > 1, "found checkboxes for preferences"); 1.752 + for (var i = 0; i < cb.length; ++i) { 1.753 + var pref = this.win.document.getElementById(cb[i].getAttribute("preference")); 1.754 + if (!!pref.value ^ check) 1.755 + cb[i].click(); 1.756 + } 1.757 + }, 1.758 + 1.759 + checkAllCheckboxes: function () { 1.760 + this._checkAllCheckboxesCustom(true); 1.761 + }, 1.762 + 1.763 + uncheckAllCheckboxes: function () { 1.764 + this._checkAllCheckboxesCustom(false); 1.765 + }, 1.766 + 1.767 + /** 1.768 + * @return The details progressive disclosure button 1.769 + */ 1.770 + getDetailsButton: function () { 1.771 + return this.win.document.getElementById("detailsExpander"); 1.772 + }, 1.773 + 1.774 + /** 1.775 + * @return The dialog's duration dropdown 1.776 + */ 1.777 + getDurationDropdown: function () { 1.778 + return this.win.document.getElementById("sanitizeDurationChoice"); 1.779 + }, 1.780 + 1.781 + /** 1.782 + * @return The item list hidden by the details progressive disclosure button 1.783 + */ 1.784 + getItemList: function () { 1.785 + return this.win.document.getElementById("itemList"); 1.786 + }, 1.787 + 1.788 + /** 1.789 + * @return The clear-everything warning box 1.790 + */ 1.791 + getWarningPanel: function () { 1.792 + return this.win.document.getElementById("sanitizeEverythingWarningBox"); 1.793 + }, 1.794 + 1.795 + /** 1.796 + * @return True if the "Everything" warning panel is visible (as opposed to 1.797 + * the tree) 1.798 + */ 1.799 + isWarningPanelVisible: function () { 1.800 + return !this.getWarningPanel().hidden; 1.801 + }, 1.802 + 1.803 + /** 1.804 + * Opens the clear recent history dialog. Before calling this, set 1.805 + * this.onload to a function to execute onload. It should close the dialog 1.806 + * when done so that the tests may continue. Set this.onunload to a function 1.807 + * to execute onunload. this.onunload is optional. If it returns true, the 1.808 + * caller is expected to call waitForAsyncUpdates at some point; if false is 1.809 + * returned, waitForAsyncUpdates is called automatically. 1.810 + */ 1.811 + open: function () { 1.812 + let wh = this; 1.813 + 1.814 + function windowObserver(aSubject, aTopic, aData) { 1.815 + if (aTopic != "domwindowopened") 1.816 + return; 1.817 + 1.818 + Services.ww.unregisterNotification(windowObserver); 1.819 + 1.820 + var loaded = false; 1.821 + let win = aSubject.QueryInterface(Ci.nsIDOMWindow); 1.822 + 1.823 + win.addEventListener("load", function onload(event) { 1.824 + win.removeEventListener("load", onload, false); 1.825 + 1.826 + if (win.name !== "SanitizeDialog") 1.827 + return; 1.828 + 1.829 + wh.win = win; 1.830 + loaded = true; 1.831 + 1.832 + executeSoon(function () { 1.833 + // Some exceptions that reach here don't reach the test harness, but 1.834 + // ok()/is() do... 1.835 + try { 1.836 + wh.onload(); 1.837 + } 1.838 + catch (exc) { 1.839 + win.close(); 1.840 + ok(false, "Unexpected exception: " + exc + "\n" + exc.stack); 1.841 + finish(); 1.842 + } 1.843 + }); 1.844 + }, false); 1.845 + 1.846 + win.addEventListener("unload", function onunload(event) { 1.847 + if (win.name !== "SanitizeDialog") { 1.848 + win.removeEventListener("unload", onunload, false); 1.849 + return; 1.850 + } 1.851 + 1.852 + // Why is unload fired before load? 1.853 + if (!loaded) 1.854 + return; 1.855 + 1.856 + win.removeEventListener("unload", onunload, false); 1.857 + wh.win = win; 1.858 + 1.859 + executeSoon(function () { 1.860 + // Some exceptions that reach here don't reach the test harness, but 1.861 + // ok()/is() do... 1.862 + try { 1.863 + if (wh.onunload) { 1.864 + Task.spawn(wh.onunload).then(function() { 1.865 + waitForAsyncUpdates(doNextTest); 1.866 + }).then(null, Components.utils.reportError); 1.867 + } else { 1.868 + waitForAsyncUpdates(doNextTest); 1.869 + } 1.870 + } 1.871 + catch (exc) { 1.872 + win.close(); 1.873 + ok(false, "Unexpected exception: " + exc + "\n" + exc.stack); 1.874 + finish(); 1.875 + } 1.876 + }); 1.877 + }, false); 1.878 + } 1.879 + Services.ww.registerNotification(windowObserver); 1.880 + Services.ww.openWindow(null, 1.881 + "chrome://browser/content/sanitize.xul", 1.882 + "SanitizeDialog", 1.883 + "chrome,titlebar,dialog,centerscreen,modal", 1.884 + null); 1.885 + }, 1.886 + 1.887 + /** 1.888 + * Selects a duration in the duration dropdown. 1.889 + * 1.890 + * @param aDurVal 1.891 + * One of the Sanitizer.TIMESPAN_* values 1.892 + */ 1.893 + selectDuration: function (aDurVal) { 1.894 + this.getDurationDropdown().value = aDurVal; 1.895 + if (aDurVal === Sanitizer.TIMESPAN_EVERYTHING) { 1.896 + is(this.isWarningPanelVisible(), true, 1.897 + "Warning panel should be visible for TIMESPAN_EVERYTHING"); 1.898 + } 1.899 + else { 1.900 + is(this.isWarningPanelVisible(), false, 1.901 + "Warning panel should not be visible for non-TIMESPAN_EVERYTHING"); 1.902 + } 1.903 + }, 1.904 + 1.905 + /** 1.906 + * Toggles the details progressive disclosure button. 1.907 + */ 1.908 + toggleDetails: function () { 1.909 + this.getDetailsButton().click(); 1.910 + } 1.911 +}; 1.912 + 1.913 +/** 1.914 + * Adds a download to history. 1.915 + * 1.916 + * @param aMinutesAgo 1.917 + * The download will be downloaded this many minutes ago 1.918 + */ 1.919 +function addDownloadWithMinutesAgo(aExpectedPathList, aMinutesAgo) { 1.920 + let publicList = yield Downloads.getList(Downloads.PUBLIC); 1.921 + 1.922 + let name = "fakefile-" + aMinutesAgo + "-minutes-ago"; 1.923 + let download = yield Downloads.createDownload({ 1.924 + source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169", 1.925 + target: name 1.926 + }); 1.927 + download.startTime = new Date(now_mSec - (aMinutesAgo * kMsecPerMin)); 1.928 + download.canceled = true; 1.929 + publicList.add(download); 1.930 + 1.931 + ok((yield downloadExists(name)), 1.932 + "Sanity check: download " + name + 1.933 + " should exist after creating it"); 1.934 + 1.935 + aExpectedPathList.push(name); 1.936 +} 1.937 + 1.938 +/** 1.939 + * Adds a form entry to history. 1.940 + * 1.941 + * @param aMinutesAgo 1.942 + * The entry will be added this many minutes ago 1.943 + */ 1.944 +function addFormEntryWithMinutesAgo(then, aMinutesAgo) { 1.945 + let name = aMinutesAgo + "-minutes-ago"; 1.946 + 1.947 + // Artifically age the entry to the proper vintage. 1.948 + let timestamp = now_uSec - (aMinutesAgo * kUsecPerMin); 1.949 + 1.950 + FormHistory.update({ op: "add", fieldname: name, value: "dummy", firstUsed: timestamp }, 1.951 + { handleError: function (error) { 1.952 + do_throw("Error occurred updating form history: " + error); 1.953 + }, 1.954 + handleCompletion: function (reason) { then.next(); } 1.955 + }); 1.956 + return name; 1.957 +} 1.958 + 1.959 +/** 1.960 + * Checks if a form entry exists. 1.961 + */ 1.962 +function formNameExists(name) 1.963 +{ 1.964 + let deferred = Promise.defer(); 1.965 + 1.966 + let count = 0; 1.967 + FormHistory.count({ fieldname: name }, 1.968 + { handleResult: function (result) count = result, 1.969 + handleError: function (error) { 1.970 + do_throw("Error occurred searching form history: " + error); 1.971 + deferred.reject(error); 1.972 + }, 1.973 + handleCompletion: function (reason) { 1.974 + if (!reason) deferred.resolve(count); 1.975 + } 1.976 + }); 1.977 + 1.978 + return deferred.promise; 1.979 +} 1.980 + 1.981 +/** 1.982 + * Removes all history visits, downloads, and form entries. 1.983 + */ 1.984 +function blankSlate() { 1.985 + PlacesUtils.bhistory.removeAllPages(); 1.986 + 1.987 + // The promise is resolved only when removing both downloads and form history are done. 1.988 + let deferred = Promise.defer(); 1.989 + let formHistoryDone = false, downloadsDone = false; 1.990 + 1.991 + Task.spawn(function deleteAllDownloads() { 1.992 + let publicList = yield Downloads.getList(Downloads.PUBLIC); 1.993 + let downloads = yield publicList.getAll(); 1.994 + for (let download of downloads) { 1.995 + yield publicList.remove(download); 1.996 + yield download.finalize(true); 1.997 + } 1.998 + downloadsDone = true; 1.999 + if (formHistoryDone) { 1.1000 + deferred.resolve(); 1.1001 + } 1.1002 + }).then(null, Components.utils.reportError); 1.1003 + 1.1004 + FormHistory.update({ op: "remove" }, 1.1005 + { handleError: function (error) { 1.1006 + do_throw("Error occurred updating form history: " + error); 1.1007 + deferred.reject(error); 1.1008 + }, 1.1009 + handleCompletion: function (reason) { 1.1010 + if (!reason) { 1.1011 + formHistoryDone = true; 1.1012 + if (downloadsDone) { 1.1013 + deferred.resolve(); 1.1014 + } 1.1015 + } 1.1016 + } 1.1017 + }); 1.1018 + return deferred.promise; 1.1019 +} 1.1020 + 1.1021 +/** 1.1022 + * Ensures that the given pref is the expected value. 1.1023 + * 1.1024 + * @param aPrefName 1.1025 + * The pref's sub-branch under the privacy branch 1.1026 + * @param aExpectedVal 1.1027 + * The pref's expected value 1.1028 + * @param aMsg 1.1029 + * Passed to is() 1.1030 + */ 1.1031 +function boolPrefIs(aPrefName, aExpectedVal, aMsg) { 1.1032 + is(gPrefService.getBoolPref("privacy." + aPrefName), aExpectedVal, aMsg); 1.1033 +} 1.1034 + 1.1035 +/** 1.1036 + * Checks to see if the download with the specified path exists. 1.1037 + * 1.1038 + * @param aPath 1.1039 + * The path of the download to check 1.1040 + * @return True if the download exists, false otherwise 1.1041 + */ 1.1042 +function downloadExists(aPath) 1.1043 +{ 1.1044 + return Task.spawn(function() { 1.1045 + let publicList = yield Downloads.getList(Downloads.PUBLIC); 1.1046 + let listArray = yield publicList.getAll(); 1.1047 + throw new Task.Result(listArray.some(i => i.target.path == aPath)); 1.1048 + }); 1.1049 +} 1.1050 + 1.1051 +/** 1.1052 + * Runs the next test in the gAllTests array. If all tests have been run, 1.1053 + * finishes the entire suite. 1.1054 + */ 1.1055 +function doNextTest() { 1.1056 + if (gAllTests.length <= gCurrTest) { 1.1057 + blankSlate(); 1.1058 + waitForAsyncUpdates(finish); 1.1059 + } 1.1060 + else { 1.1061 + let ct = gCurrTest; 1.1062 + gCurrTest++; 1.1063 + gAllTests[ct](); 1.1064 + } 1.1065 +} 1.1066 + 1.1067 +/** 1.1068 + * Ensures that the specified downloads are either cleared or not. 1.1069 + * 1.1070 + * @param aDownloadIDs 1.1071 + * Array of download database IDs 1.1072 + * @param aShouldBeCleared 1.1073 + * True if each download should be cleared, false otherwise 1.1074 + */ 1.1075 +function ensureDownloadsClearedState(aDownloadIDs, aShouldBeCleared) { 1.1076 + let niceStr = aShouldBeCleared ? "no longer" : "still"; 1.1077 + aDownloadIDs.forEach(function (id) { 1.1078 + is((yield downloadExists(id)), !aShouldBeCleared, 1.1079 + "download " + id + " should " + niceStr + " exist"); 1.1080 + }); 1.1081 +} 1.1082 + 1.1083 +/** 1.1084 + * Ensures that the given pref is the expected value. 1.1085 + * 1.1086 + * @param aPrefName 1.1087 + * The pref's sub-branch under the privacy branch 1.1088 + * @param aExpectedVal 1.1089 + * The pref's expected value 1.1090 + * @param aMsg 1.1091 + * Passed to is() 1.1092 + */ 1.1093 +function intPrefIs(aPrefName, aExpectedVal, aMsg) { 1.1094 + is(gPrefService.getIntPref("privacy." + aPrefName), aExpectedVal, aMsg); 1.1095 +} 1.1096 + 1.1097 +/** 1.1098 + * Creates a visit time. 1.1099 + * 1.1100 + * @param aMinutesAgo 1.1101 + * The visit will be visited this many minutes ago 1.1102 + */ 1.1103 +function visitTimeForMinutesAgo(aMinutesAgo) { 1.1104 + return now_uSec - aMinutesAgo * kUsecPerMin; 1.1105 +} 1.1106 + 1.1107 +/////////////////////////////////////////////////////////////////////////////// 1.1108 + 1.1109 +function test() { 1.1110 + requestLongerTimeout(2); 1.1111 + waitForExplicitFinish(); 1.1112 + blankSlate(); 1.1113 + // Kick off all the tests in the gAllTests array. 1.1114 + waitForAsyncUpdates(doNextTest); 1.1115 +}