head.js (22773B)
1 ChromeUtils.defineESModuleGetters(this, { 2 Downloads: "resource://gre/modules/Downloads.sys.mjs", 3 FormHistory: "resource://gre/modules/FormHistory.sys.mjs", 4 PermissionTestUtils: "resource://testing-common/PermissionTestUtils.sys.mjs", 5 PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs", 6 PlacesTestUtils: "resource://testing-common/PlacesTestUtils.sys.mjs", 7 FileTestUtils: "resource://testing-common/FileTestUtils.sys.mjs", 8 Sanitizer: "resource:///modules/Sanitizer.sys.mjs", 9 SiteDataTestUtils: "resource://testing-common/SiteDataTestUtils.sys.mjs", 10 }); 11 12 const kMsecPerMin = 60 * 1000; 13 const kUsecPerMin = kMsecPerMin * 1000; 14 let today = Date.now() - new Date().setHours(0, 0, 0, 0); 15 let nowMSec = Date.now(); 16 let nowUSec = nowMSec * 1000; 17 const TEST_TARGET_FILE_NAME = "test-download.txt"; 18 const TEST_QUOTA_USAGE_HOST = "example.com"; 19 const TEST_QUOTA_USAGE_ORIGIN = "https://" + TEST_QUOTA_USAGE_HOST; 20 const TEST_QUOTA_USAGE_URL = 21 getRootDirectory(gTestPath).replace( 22 "chrome://mochitests/content", 23 TEST_QUOTA_USAGE_ORIGIN 24 ) + "site_data_test.html"; 25 const SITE_ORIGINS = [ 26 "https://www.example.com", 27 "https://example.org", 28 "http://localhost:8000", 29 "http://localhost:3000", 30 ]; 31 32 let fileURL; 33 34 function createIndexedDB(host, originAttributes) { 35 let uri = Services.io.newURI("https://" + host); 36 let principal = Services.scriptSecurityManager.createContentPrincipal( 37 uri, 38 originAttributes 39 ); 40 return SiteDataTestUtils.addToIndexedDB(principal.origin); 41 } 42 43 function checkIndexedDB(host, originAttributes) { 44 return new Promise(resolve => { 45 let data = true; 46 let uri = Services.io.newURI("https://" + host); 47 let principal = Services.scriptSecurityManager.createContentPrincipal( 48 uri, 49 originAttributes 50 ); 51 let request = indexedDB.openForPrincipal(principal, "TestDatabase", 1); 52 request.onupgradeneeded = function () { 53 data = false; 54 }; 55 request.onsuccess = function () { 56 resolve(data); 57 }; 58 }); 59 } 60 61 function createHostCookie(host, originAttributes) { 62 const cv = Services.cookies.add( 63 host, 64 "/test", 65 "foo", 66 "bar", 67 false, 68 false, 69 false, 70 Date.now() + 24000 * 60 * 60, 71 originAttributes, 72 Ci.nsICookie.SAMESITE_UNSET, 73 Ci.nsICookie.SCHEME_HTTPS 74 ); 75 is(cv.result, Ci.nsICookieValidation.eOK, "Valid cookie"); 76 } 77 78 function createDomainCookie(host, originAttributes) { 79 const cv = Services.cookies.add( 80 "." + host, 81 "/test", 82 "foo", 83 "bar", 84 false, 85 false, 86 false, 87 Date.now() + 24000 * 60 * 60, 88 originAttributes, 89 Ci.nsICookie.SAMESITE_UNSET, 90 Ci.nsICookie.SCHEME_HTTPS 91 ); 92 is(cv.result, Ci.nsICookieValidation.eOK, "Valid cookie"); 93 } 94 95 function checkCookie(host, originAttributes) { 96 for (let cookie of Services.cookies.cookies) { 97 if ( 98 ChromeUtils.isOriginAttributesEqual( 99 originAttributes, 100 cookie.originAttributes 101 ) && 102 cookie.host.includes(host) 103 ) { 104 return true; 105 } 106 } 107 return false; 108 } 109 110 async function deleteOnShutdown(opt) { 111 // Let's clean up all the data. 112 await new Promise(resolve => { 113 Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, resolve); 114 }); 115 116 await SpecialPowers.pushPrefEnv({ 117 set: [ 118 ["privacy.sanitize.sanitizeOnShutdown", opt.sanitize], 119 ["privacy.clearOnShutdown.cookies", opt.sanitize], 120 ["privacy.clearOnShutdown.offlineApps", opt.sanitize], 121 ["browser.sanitizer.loglevel", "All"], 122 ], 123 }); 124 125 // Custom permission without considering OriginAttributes 126 if (opt.cookiePermission !== undefined) { 127 let uri = Services.io.newURI("https://www.example.com"); 128 PermissionTestUtils.add(uri, "cookie", opt.cookiePermission); 129 } 130 131 // Let's create a tab with some data. 132 await opt.createData( 133 (opt.fullHost ? "www." : "") + "example.org", 134 opt.originAttributes 135 ); 136 ok( 137 await opt.checkData( 138 (opt.fullHost ? "www." : "") + "example.org", 139 opt.originAttributes 140 ), 141 "We have data for www.example.org" 142 ); 143 await opt.createData( 144 (opt.fullHost ? "www." : "") + "example.com", 145 opt.originAttributes 146 ); 147 ok( 148 await opt.checkData( 149 (opt.fullHost ? "www." : "") + "example.com", 150 opt.originAttributes 151 ), 152 "We have data for www.example.com" 153 ); 154 155 // Cleaning up. 156 await Sanitizer.runSanitizeOnShutdown(); 157 158 // All gone! 159 is( 160 !!(await opt.checkData( 161 (opt.fullHost ? "www." : "") + "example.org", 162 opt.originAttributes 163 )), 164 opt.expectedForOrg, 165 "Do we have data for www.example.org?" 166 ); 167 is( 168 !!(await opt.checkData( 169 (opt.fullHost ? "www." : "") + "example.com", 170 opt.originAttributes 171 )), 172 opt.expectedForCom, 173 "Do we have data for www.example.com?" 174 ); 175 176 // Clean up. 177 await Sanitizer.sanitize(["cookies", "offlineApps"]); 178 179 if (opt.cookiePermission !== undefined) { 180 let uri = Services.io.newURI("https://www.example.com"); 181 PermissionTestUtils.remove(uri, "cookie"); 182 } 183 } 184 185 function runAllCookiePermissionTests(originAttributes) { 186 let tests = [ 187 { name: "IDB", createData: createIndexedDB, checkData: checkIndexedDB }, 188 { 189 name: "Host Cookie", 190 createData: createHostCookie, 191 checkData: checkCookie, 192 }, 193 { 194 name: "Domain Cookie", 195 createData: createDomainCookie, 196 checkData: checkCookie, 197 }, 198 ]; 199 200 // Delete all, no custom permission, data in example.com, cookie permission set 201 // for www.example.com 202 tests.forEach(methods => { 203 add_task(async function deleteStorageOnShutdown() { 204 info( 205 methods.name + 206 ": Delete all, no custom permission, data in example.com, cookie permission set for www.example.com - OA: " + 207 originAttributes.name 208 ); 209 await deleteOnShutdown({ 210 sanitize: true, 211 createData: methods.createData, 212 checkData: methods.checkData, 213 originAttributes: originAttributes.oa, 214 cookiePermission: undefined, 215 expectedForOrg: false, 216 expectedForCom: false, 217 fullHost: false, 218 }); 219 }); 220 }); 221 222 // Delete all, no custom permission, data in www.example.com, cookie permission 223 // set for www.example.com 224 tests.forEach(methods => { 225 add_task(async function deleteStorageOnShutdown() { 226 info( 227 methods.name + 228 ": Delete all, no custom permission, data in www.example.com, cookie permission set for www.example.com - OA: " + 229 originAttributes.name 230 ); 231 await deleteOnShutdown({ 232 sanitize: true, 233 createData: methods.createData, 234 checkData: methods.checkData, 235 originAttributes: originAttributes.oa, 236 cookiePermission: undefined, 237 expectedForOrg: false, 238 expectedForCom: false, 239 fullHost: true, 240 }); 241 }); 242 }); 243 244 // All is session, but with ALLOW custom permission, data in example.com, 245 // cookie permission set for www.example.com 246 tests.forEach(methods => { 247 add_task(async function deleteStorageWithCustomPermission() { 248 info( 249 methods.name + 250 ": All is session, but with ALLOW custom permission, data in example.com, cookie permission set for www.example.com - OA: " + 251 originAttributes.name 252 ); 253 await deleteOnShutdown({ 254 sanitize: true, 255 createData: methods.createData, 256 checkData: methods.checkData, 257 originAttributes: originAttributes.oa, 258 cookiePermission: Ci.nsICookiePermission.ACCESS_ALLOW, 259 expectedForOrg: false, 260 expectedForCom: true, 261 fullHost: false, 262 }); 263 }); 264 }); 265 266 // All is session, but with ALLOW custom permission, data in www.example.com, 267 // cookie permission set for www.example.com 268 tests.forEach(methods => { 269 add_task(async function deleteStorageWithCustomPermission() { 270 info( 271 methods.name + 272 ": All is session, but with ALLOW custom permission, data in www.example.com, cookie permission set for www.example.com - OA: " + 273 originAttributes.name 274 ); 275 await deleteOnShutdown({ 276 sanitize: true, 277 createData: methods.createData, 278 checkData: methods.checkData, 279 originAttributes: originAttributes.oa, 280 cookiePermission: Ci.nsICookiePermission.ACCESS_ALLOW, 281 expectedForOrg: false, 282 expectedForCom: true, 283 fullHost: true, 284 }); 285 }); 286 }); 287 288 // All is default, but with SESSION custom permission, data in example.com, 289 // cookie permission set for www.example.com 290 tests.forEach(methods => { 291 add_task(async function deleteStorageOnlyCustomPermission() { 292 info( 293 methods.name + 294 ": All is default, but with SESSION custom permission, data in example.com, cookie permission set for www.example.com - OA: " + 295 originAttributes.name 296 ); 297 await deleteOnShutdown({ 298 sanitize: false, 299 createData: methods.createData, 300 checkData: methods.checkData, 301 originAttributes: originAttributes.oa, 302 cookiePermission: Ci.nsICookiePermission.ACCESS_SESSION, 303 expectedForOrg: true, 304 // expected data just for example.com when using indexedDB because 305 // QuotaManager deletes for principal. 306 expectedForCom: false, 307 fullHost: false, 308 }); 309 }); 310 }); 311 312 // All is default, but with SESSION custom permission, data in www.example.com, 313 // cookie permission set for www.example.com 314 tests.forEach(methods => { 315 add_task(async function deleteStorageOnlyCustomPermission() { 316 info( 317 methods.name + 318 ": All is default, but with SESSION custom permission, data in www.example.com, cookie permission set for www.example.com - OA: " + 319 originAttributes.name 320 ); 321 await deleteOnShutdown({ 322 sanitize: false, 323 createData: methods.createData, 324 checkData: methods.checkData, 325 originAttributes: originAttributes.oa, 326 cookiePermission: Ci.nsICookiePermission.ACCESS_SESSION, 327 expectedForOrg: true, 328 expectedForCom: false, 329 fullHost: true, 330 }); 331 }); 332 }); 333 334 // Session mode, but with unsupported custom permission, data in 335 // www.example.com, cookie permission set for www.example.com 336 tests.forEach(methods => { 337 add_task(async function deleteStorageOnlyCustomPermission() { 338 info( 339 methods.name + 340 ": All is session only, but with unsupported custom custom permission, data in www.example.com, cookie permission set for www.example.com - OA: " + 341 originAttributes.name 342 ); 343 await deleteOnShutdown({ 344 sanitize: true, 345 createData: methods.createData, 346 checkData: methods.checkData, 347 originAttributes: originAttributes.oa, 348 cookiePermission: 123, // invalid cookie permission 349 expectedForOrg: false, 350 expectedForCom: false, 351 fullHost: true, 352 }); 353 }); 354 }); 355 } 356 357 function openPreferencesViaOpenPreferencesAPI(aPane, aOptions) { 358 return new Promise(resolve => { 359 let finalPrefPaneLoaded = TestUtils.topicObserved( 360 "sync-pane-loaded", 361 () => true 362 ); 363 gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, "about:blank"); 364 openPreferences(aPane); 365 let newTabBrowser = gBrowser.selectedBrowser; 366 367 newTabBrowser.addEventListener( 368 "Initialized", 369 function () { 370 newTabBrowser.contentWindow.addEventListener( 371 "load", 372 async function () { 373 let win = gBrowser.contentWindow; 374 let selectedPane = win.history.state; 375 await finalPrefPaneLoaded; 376 if (!aOptions || !aOptions.leaveOpen) { 377 gBrowser.removeCurrentTab(); 378 } 379 resolve({ selectedPane }); 380 }, 381 { once: true } 382 ); 383 }, 384 { capture: true, once: true } 385 ); 386 }); 387 } 388 389 async function createDummyDataForHost(host) { 390 let origin = "https://" + host; 391 let dummySWURL = 392 getRootDirectory(gTestPath).replace("chrome://mochitests/content", origin) + 393 "dummy.js"; 394 395 await SiteDataTestUtils.addToIndexedDB(origin); 396 await SiteDataTestUtils.addServiceWorker({ win: window, path: dummySWURL }); 397 } 398 399 /** 400 * Helper function to create file URL to open 401 * 402 * @returns {object} a file URL 403 */ 404 function createFileURL() { 405 if (!fileURL) { 406 let file = Services.dirsvc.get("TmpD", Ci.nsIFile); 407 file.append("foo.txt"); 408 file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600); 409 410 fileURL = Services.io.newFileURI(file); 411 } 412 return fileURL; 413 } 414 415 /** 416 * Removes all history visits, downloads, and form entries. 417 */ 418 async function blankSlate() { 419 let publicList = await Downloads.getList(Downloads.PUBLIC); 420 let downloads = await publicList.getAll(); 421 for (let download of downloads) { 422 await publicList.remove(download); 423 await download.finalize(true); 424 } 425 426 await FormHistory.update({ op: "remove" }); 427 await PlacesUtils.history.clear(); 428 } 429 430 /** 431 * Adds multiple downloads to the PUBLIC download list 432 */ 433 async function addToDownloadList() { 434 const url = createFileURL(); 435 const downloadsList = await Downloads.getList(Downloads.PUBLIC); 436 let timeOptions = [1, 2, 4, 24, 128, 128]; 437 let buffer = 100000; 438 439 for (let i = 0; i < timeOptions.length; i++) { 440 let timeDownloaded = 60 * kMsecPerMin * timeOptions[i]; 441 if (timeOptions[i] === 24) { 442 timeDownloaded = today; 443 } 444 445 let download = await Downloads.createDownload({ 446 source: { url: url.spec, isPrivate: false }, 447 target: { path: FileTestUtils.getTempFile(TEST_TARGET_FILE_NAME).path }, 448 startTime: { 449 getTime: _ => { 450 return nowMSec - timeDownloaded + buffer; 451 }, 452 }, 453 }); 454 455 Assert.ok(!!download); 456 downloadsList.add(download); 457 } 458 let items = await downloadsList.getAll(); 459 Assert.equal(items.length, 6, "Items were added to the list"); 460 } 461 462 async function addToSiteUsage() { 463 // Fill indexedDB with test data. 464 // Don't wait for the page to load, to register the content event handler as quickly as possible. 465 // If this test goes intermittent, we might have to tell the page to wait longer before 466 // firing the event. 467 BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_QUOTA_USAGE_URL, false); 468 await BrowserTestUtils.waitForContentEvent( 469 gBrowser.selectedBrowser, 470 "test-indexedDB-done", 471 false, 472 null, 473 true 474 ); 475 BrowserTestUtils.removeTab(gBrowser.selectedTab); 476 477 let siteLastAccessed = [1, 2, 4, 24]; 478 479 let staticUsage = 4096 * 6; 480 // Add a time buffer so the site access falls within the time range 481 const buffer = 10000; 482 483 // Change lastAccessed of sites 484 for (let index = 0; index < siteLastAccessed.length; index++) { 485 let lastAccessedTime = 60 * kMsecPerMin * siteLastAccessed[index]; 486 if (siteLastAccessed[index] === 24) { 487 lastAccessedTime = today; 488 } 489 490 let site = SiteDataManager._testInsertSite(SITE_ORIGINS[index], { 491 quotaUsage: staticUsage, 492 lastAccessed: (nowMSec - lastAccessedTime + buffer) * 1000, 493 }); 494 Assert.ok(site, "Site added successfully"); 495 } 496 } 497 498 function promiseSanitizationComplete() { 499 return TestUtils.topicObserved("sanitizer-sanitization-complete"); 500 } 501 502 function settingsRedesignHistoryEnabled() { 503 return ( 504 Services.prefs.getBoolPref( 505 "browser.settings-redesign.history2.enabled", 506 false 507 ) || Services.prefs.getBoolPref("browser.settings-redesign.enabled", false) 508 ); 509 } 510 511 /** 512 * This wraps the dialog and provides some convenience methods for interacting 513 * with it. 514 * 515 * @param {Window} browserWin (optional) 516 * The browser window that the dialog is expected to open in. If not 517 * supplied, the initial browser window of the test run is used. 518 * @param {object} {mode, checkingDataSizes} 519 * mode: context to open the dialog in 520 * One of 521 * clear on shutdown settings context ("clearOnShutdown"), 522 * clear site data settings context ("clearSiteData"), 523 * clear history context ("clearHistory"), 524 * browser context ("browser") 525 * "browser" by default 526 * checkingDataSizes: boolean check if we should wait for the data sizes 527 * to load 528 */ 529 function ClearHistoryDialogHelper({ 530 mode = "browser", 531 checkingDataSizes = false, 532 } = {}) { 533 this._browserWin = window; 534 this.win = null; 535 this._mode = mode; 536 this._checkingDataSizes = checkingDataSizes; 537 this.promiseClosed = new Promise(resolve => { 538 this._resolveClosed = resolve; 539 }); 540 } 541 542 ClearHistoryDialogHelper.prototype = { 543 /** 544 * "Presses" the dialog's OK button. 545 */ 546 acceptDialog() { 547 let dialogEl = this.win.document.querySelector("dialog"); 548 is( 549 dialogEl.getButton("accept").disabled, 550 false, 551 "Dialog's OK button should not be disabled" 552 ); 553 dialogEl.acceptDialog(); 554 }, 555 556 /** 557 * "Presses" the dialog's Cancel button. 558 */ 559 cancelDialog() { 560 this.win.document.querySelector("dialog").cancelDialog(); 561 }, 562 563 /** 564 * (Un)checks a history scope checkbox (browser & download history, 565 * form history, etc.). 566 * 567 * @param aPrefName 568 * The final portion of the checkbox's privacy.cpd.* preference name 569 * @param aCheckState 570 * True if the checkbox should be checked, false otherwise 571 */ 572 checkPrefCheckbox(aPrefName, aCheckState) { 573 var cb = this.win.document.querySelectorAll( 574 "checkbox[id='" + aPrefName + "']" 575 ); 576 is(cb.length, 1, "found checkbox for " + aPrefName + " id"); 577 if (cb[0].checked != aCheckState) { 578 cb[0].click(); 579 } 580 }, 581 582 /** 583 * @param {string} aCheckboxId 584 * The checkbox id name 585 * @param {boolean} aCheckState 586 * True if the checkbox should be checked, false otherwise 587 */ 588 validateCheckbox(aCheckboxId, aCheckState) { 589 let cb = this.win.document.querySelectorAll( 590 "checkbox[id='" + aCheckboxId + "']" 591 ); 592 is(cb.length, 1, `found checkbox for id=${aCheckboxId}`); 593 is( 594 cb[0].checked, 595 aCheckState, 596 `checkbox for ${aCheckboxId} is ${aCheckState}` 597 ); 598 }, 599 600 /** 601 * Makes sure all the checkboxes are checked. 602 */ 603 _checkAllCheckboxesCustom(check) { 604 var cb = this.win.document.querySelectorAll(".clearingItemCheckbox"); 605 ok(cb.length, "found checkboxes for ids"); 606 for (var i = 0; i < cb.length; ++i) { 607 if (cb[i].checked != check) { 608 cb[i].click(); 609 } 610 } 611 }, 612 613 checkAllCheckboxes() { 614 this._checkAllCheckboxesCustom(true); 615 }, 616 617 uncheckAllCheckboxes() { 618 this._checkAllCheckboxesCustom(false); 619 }, 620 621 /** 622 * @return The dialog's duration dropdown 623 */ 624 getDurationDropdown() { 625 return this.win.document.getElementById("sanitizeDurationChoice"); 626 }, 627 628 /** 629 * @return The clear-everything warning box 630 */ 631 getWarningPanel() { 632 return this.win.document.getElementById("sanitizeEverythingWarningBox"); 633 }, 634 635 /** 636 * @return True if the "Everything" warning panel is visible (as opposed to 637 * the tree) 638 */ 639 isWarningPanelVisible() { 640 return !this.getWarningPanel().hidden; 641 }, 642 643 /** 644 * Opens the clear recent history dialog. Before calling this, set 645 * this.onload to a function to execute onload. It should close the dialog 646 * when done so that the tests may continue. Set this.onunload to a function 647 * to execute onunload. this.onunload is optional. If it returns true, the 648 * caller is expected to call promiseAsyncUpdates at some point; if false is 649 * returned, promiseAsyncUpdates is called automatically. 650 */ 651 async open() { 652 let dialogPromise = BrowserTestUtils.promiseAlertDialogOpen( 653 null, 654 "chrome://browser/content/sanitize_v2.xhtml", 655 { 656 isSubDialog: true, 657 } 658 ); 659 660 // We want to simulate opening the dialog inside preferences for clear history 661 // and clear site data 662 if (this._mode != "browser") { 663 if (this._mode == "clearOnShutdown" && settingsRedesignHistoryEnabled()) { 664 await openPreferencesViaOpenPreferencesAPI("history", { 665 leaveOpen: true, 666 }); 667 } else { 668 await openPreferencesViaOpenPreferencesAPI("privacy", { 669 leaveOpen: true, 670 }); 671 } 672 let tabWindow = gBrowser.selectedBrowser.contentWindow; 673 let clearDialogOpenButtonId = this._mode + "Button"; 674 // the id for clear on shutdown is of a different format 675 if (this._mode == "clearOnShutdown") { 676 // set always clear to true to enable the clear on shutdown dialog 677 let enableSettingsCheckbox = 678 tabWindow.document.getElementById("alwaysClear"); 679 if (!enableSettingsCheckbox.checked) { 680 enableSettingsCheckbox.click(); 681 } 682 clearDialogOpenButtonId = "clearDataSettings"; 683 } 684 // open dialog 685 // Wait a tick for the button to be initialized.œ 686 await new Promise(resolve => requestAnimationFrame(resolve)); 687 tabWindow.document.getElementById(clearDialogOpenButtonId).click(); 688 } 689 // We open the dialog in the chrome context in other cases 690 else { 691 executeSoon(() => { 692 Sanitizer.showUI(this._browserWin, this._mode); 693 }); 694 } 695 696 this.win = await dialogPromise; 697 this.win.addEventListener( 698 "load", 699 () => { 700 // Run onload on next tick so that gSanitizePromptDialog.init can run first. 701 executeSoon(async () => { 702 if (this._checkingDataSizes) { 703 // we wait for the data sizes to load to avoid async errors when validating sizes 704 await this.win.gSanitizePromptDialog 705 .dataSizesFinishedUpdatingPromise; 706 } 707 this.onload(); 708 }); 709 }, 710 { once: true } 711 ); 712 this.win.addEventListener( 713 "unload", 714 () => { 715 // Some exceptions that reach here don't reach the test harness, but 716 // ok()/is() do... 717 (async () => { 718 if (this.onunload) { 719 await this.onunload(); 720 } 721 if (this._mode != "browser") { 722 BrowserTestUtils.removeTab(gBrowser.selectedTab); 723 } 724 await PlacesTestUtils.promiseAsyncUpdates(); 725 this._resolveClosed(); 726 this.win = null; 727 })(); 728 }, 729 { once: true } 730 ); 731 }, 732 733 /** 734 * Selects a duration in the duration dropdown. 735 * 736 * @param aDurVal 737 * One of the Sanitizer.TIMESPAN_* values 738 */ 739 selectDuration(aDurVal) { 740 this.getDurationDropdown().value = aDurVal; 741 if (aDurVal === Sanitizer.TIMESPAN_EVERYTHING) { 742 is( 743 this.isWarningPanelVisible(), 744 true, 745 "Warning panel should be visible for TIMESPAN_EVERYTHING" 746 ); 747 } else { 748 is( 749 this.isWarningPanelVisible(), 750 false, 751 "Warning panel should not be visible for non-TIMESPAN_EVERYTHING" 752 ); 753 } 754 }, 755 };