1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 GEN('#include "chrome/browser/ui/webui/options/options_browsertest.h"'); 6 7 /** @const */ var MANAGED_USERS_PREF = 'profile.managed_users'; 8 9 /** 10 * Wait for the method specified by |methodName|, on the |object| object, to be 11 * called, then execute |afterFunction|. 12 * @param {*} object Object with callable property named |methodName|. 13 * @param {string} methodName The name of the property on |object| to use as a 14 * callback. 15 * @param {!Function} afterFunction A function to call after object.methodName() 16 * is called. 17 */ 18 function waitForResponse(object, methodName, afterFunction) { 19 var originalCallback = object[methodName]; 20 21 // Install a wrapper that temporarily replaces the original function. 22 object[methodName] = function() { 23 object[methodName] = originalCallback; 24 originalCallback.apply(this, arguments); 25 afterFunction(); 26 }; 27 } 28 29 /** 30 * Wait for the global window.onpopstate callback to be called (after a tab 31 * history navigation), then execute |afterFunction|. 32 * @param {!Function} afterFunction A function to call after pop state events. 33 */ 34 function waitForPopstate(afterFunction) { 35 waitForResponse(window, 'onpopstate', afterFunction); 36 } 37 38 /** 39 * TestFixture for OptionsPage WebUI testing. 40 * @extends {testing.Test} 41 * @constructor 42 */ 43 function OptionsWebUITest() {} 44 45 OptionsWebUITest.prototype = { 46 __proto__: testing.Test.prototype, 47 48 /** @override */ 49 accessibilityIssuesAreErrors: true, 50 51 /** @override */ 52 setUp: function() { 53 // user-image-stream is a streaming video element used for capturing a 54 // user image during OOBE. 55 this.accessibilityAuditConfig.ignoreSelectors('videoWithoutCaptions', 56 '.user-image-stream'); 57 }, 58 59 /** 60 * Browse to the options page & call our preLoad(). 61 */ 62 browsePreload: 'chrome://settings-frame', 63 64 isAsync: true, 65 66 /** 67 * Register a mock handler to ensure expectations are met and options pages 68 * behave correctly. 69 */ 70 preLoad: function() { 71 this.makeAndRegisterMockHandler( 72 ['defaultZoomFactorAction', 73 'fetchPrefs', 74 'observePrefs', 75 'setBooleanPref', 76 'setIntegerPref', 77 'setDoublePref', 78 'setStringPref', 79 'setObjectPref', 80 'clearPref', 81 'coreOptionsUserMetricsAction', 82 ]); 83 84 // Register stubs for methods expected to be called before/during tests. 85 // Specific expectations can be made in the tests themselves. 86 this.mockHandler.stubs().fetchPrefs(ANYTHING); 87 this.mockHandler.stubs().observePrefs(ANYTHING); 88 this.mockHandler.stubs().coreOptionsUserMetricsAction(ANYTHING); 89 }, 90 }; 91 92 // Crashes on Mac only. See http://crbug.com/79181 93 GEN('#if defined(OS_MACOSX)'); 94 GEN('#define MAYBE_testSetBooleanPrefTriggers ' + 95 'DISABLED_testSetBooleanPrefTriggers'); 96 GEN('#else'); 97 GEN('#define MAYBE_testSetBooleanPrefTriggers testSetBooleanPrefTriggers'); 98 GEN('#endif // defined(OS_MACOSX)'); 99 100 TEST_F('OptionsWebUITest', 'MAYBE_testSetBooleanPrefTriggers', function() { 101 // TODO(dtseng): make generic to click all buttons. 102 var showHomeButton = $('show-home-button'); 103 var trueListValue = [ 104 'browser.show_home_button', 105 true, 106 'Options_Homepage_HomeButton', 107 ]; 108 // Note: this expectation is checked in testing::Test::tearDown. 109 this.mockHandler.expects(once()).setBooleanPref(trueListValue); 110 111 // Cause the handler to be called. 112 showHomeButton.click(); 113 showHomeButton.blur(); 114 testDone(); 115 }); 116 117 // Not meant to run on ChromeOS at this time. 118 // Not finishing in windows. http://crbug.com/81723 119 TEST_F('OptionsWebUITest', 'DISABLED_testRefreshStaysOnCurrentPage', 120 function() { 121 assertTrue($('search-engine-manager-page').hidden); 122 var item = $('manage-default-search-engines'); 123 item.click(); 124 125 assertFalse($('search-engine-manager-page').hidden); 126 127 window.location.reload(); 128 129 assertEquals('chrome://settings-frame/searchEngines', document.location.href); 130 assertFalse($('search-engine-manager-page').hidden); 131 testDone(); 132 }); 133 134 /** 135 * Test the default zoom factor select element. 136 */ 137 TEST_F('OptionsWebUITest', 'testDefaultZoomFactor', function() { 138 // The expected minimum length of the |defaultZoomFactor| element. 139 var defaultZoomFactorMinimumLength = 10; 140 // Verify that the zoom factor element exists. 141 var defaultZoomFactor = $('defaultZoomFactor'); 142 assertNotEquals(defaultZoomFactor, null); 143 144 // Verify that the zoom factor element has a reasonable number of choices. 145 expectGE(defaultZoomFactor.options.length, defaultZoomFactorMinimumLength); 146 147 // Simulate a change event, selecting the highest zoom value. Verify that 148 // the javascript handler was invoked once. 149 this.mockHandler.expects(once()).defaultZoomFactorAction(NOT_NULL). 150 will(callFunction(function() { })); 151 defaultZoomFactor.selectedIndex = defaultZoomFactor.options.length - 1; 152 var event = {target: defaultZoomFactor}; 153 if (defaultZoomFactor.onchange) defaultZoomFactor.onchange(event); 154 testDone(); 155 }); 156 157 /** 158 * If |confirmInterstitial| is true, the OK button of the Do Not Track 159 * interstitial is pressed, otherwise the abort button is pressed. 160 * @param {boolean} confirmInterstitial Whether to confirm the Do Not Track 161 * interstitial. 162 */ 163 OptionsWebUITest.prototype.testDoNotTrackInterstitial = 164 function(confirmInterstitial) { 165 Preferences.prefsFetchedCallback({'enable_do_not_track': {'value': false}}); 166 var buttonToClick = confirmInterstitial ? $('do-not-track-confirm-ok') : 167 $('do-not-track-confirm-cancel'); 168 var dntCheckbox = $('do-not-track-enabled'); 169 var dntOverlay = OptionsPage.registeredOverlayPages['donottrackconfirm']; 170 assertFalse(dntCheckbox.checked); 171 172 var visibleChangeCounter = 0; 173 var visibleChangeHandler = function() { 174 ++visibleChangeCounter; 175 switch (visibleChangeCounter) { 176 case 1: 177 window.setTimeout(function() { 178 assertTrue(dntOverlay.visible); 179 buttonToClick.click(); 180 }, 0); 181 break; 182 case 2: 183 window.setTimeout(function() { 184 assertFalse(dntOverlay.visible); 185 assertEquals(confirmInterstitial, dntCheckbox.checked); 186 dntOverlay.removeEventListener(visibleChangeHandler); 187 testDone(); 188 }, 0); 189 break; 190 default: 191 assertTrue(false); 192 } 193 }; 194 dntOverlay.addEventListener('visibleChange', visibleChangeHandler); 195 196 if (confirmInterstitial) { 197 this.mockHandler.expects(once()).setBooleanPref( 198 ['enable_do_not_track', true, 'Options_DoNotTrackCheckbox']); 199 } else { 200 // The mock handler complains if setBooleanPref is called even though 201 // it should not be. 202 } 203 204 dntCheckbox.click(); 205 }; 206 207 TEST_F('OptionsWebUITest', 'EnableDoNotTrackAndConfirmInterstitial', 208 function() { 209 this.testDoNotTrackInterstitial(true); 210 }); 211 212 TEST_F('OptionsWebUITest', 'EnableDoNotTrackAndCancelInterstitial', 213 function() { 214 this.testDoNotTrackInterstitial(false); 215 }); 216 217 // Check that the "Do not Track" preference can be correctly disabled. 218 // In order to do that, we need to enable it first. 219 TEST_F('OptionsWebUITest', 'EnableAndDisableDoNotTrack', function() { 220 Preferences.prefsFetchedCallback({'enable_do_not_track': {'value': false}}); 221 var dntCheckbox = $('do-not-track-enabled'); 222 var dntOverlay = OptionsPage.registeredOverlayPages.donottrackconfirm; 223 assertFalse(dntCheckbox.checked); 224 225 var visibleChangeCounter = 0; 226 var visibleChangeHandler = function() { 227 ++visibleChangeCounter; 228 switch (visibleChangeCounter) { 229 case 1: 230 window.setTimeout(function() { 231 assertTrue(dntOverlay.visible); 232 $('do-not-track-confirm-ok').click(); 233 }, 0); 234 break; 235 case 2: 236 window.setTimeout(function() { 237 assertFalse(dntOverlay.visible); 238 assertTrue(dntCheckbox.checked); 239 dntOverlay.removeEventListener(visibleChangeHandler); 240 dntCheckbox.click(); 241 }, 0); 242 break; 243 default: 244 assertNotReached(); 245 } 246 } 247 dntOverlay.addEventListener('visibleChange', visibleChangeHandler); 248 249 this.mockHandler.expects(once()).setBooleanPref( 250 eq(['enable_do_not_track', true, 'Options_DoNotTrackCheckbox'])); 251 252 var verifyCorrectEndState = function() { 253 window.setTimeout(function() { 254 assertFalse(dntOverlay.visible); 255 assertFalse(dntCheckbox.checked); 256 testDone(); 257 }, 0); 258 } 259 this.mockHandler.expects(once()).setBooleanPref( 260 eq(['enable_do_not_track', false, 'Options_DoNotTrackCheckbox'])).will( 261 callFunction(verifyCorrectEndState)); 262 263 dntCheckbox.click(); 264 }); 265 266 // Verify that preventDefault() is called on 'Enter' keydown events that trigger 267 // the default button. If this doesn't happen, other elements that may get 268 // focus (by the overlay closing for instance), will execute in addition to the 269 // default button. See crbug.com/268336. 270 TEST_F('OptionsWebUITest', 'EnterPreventsDefault', function() { 271 var page = HomePageOverlay.getInstance(); 272 OptionsPage.showPageByName(page.name); 273 var event = new KeyboardEvent('keydown', { 274 'bubbles': true, 275 'cancelable': true, 276 'keyIdentifier': 'Enter' 277 }); 278 assertFalse(event.defaultPrevented); 279 page.pageDiv.dispatchEvent(event); 280 assertTrue(event.defaultPrevented); 281 testDone(); 282 }); 283 284 // Verifies that sending an empty list of indexes to move doesn't crash chrome. 285 TEST_F('OptionsWebUITest', 'emptySelectedIndexesDoesntCrash', function() { 286 chrome.send('dragDropStartupPage', [0, []]); 287 setTimeout(testDone); 288 }); 289 290 // This test turns out to be flaky on all platforms. 291 // See http://crbug.com/315250. 292 293 // An overlay's position should remain the same as it shows. 294 TEST_F('OptionsWebUITest', 'DISABLED_OverlayShowDoesntShift', function() { 295 var overlayName = 'startup'; 296 var overlay = $('startup-overlay'); 297 var frozenPages = document.getElementsByClassName('frozen'); // Gets updated. 298 expectEquals(0, frozenPages.length); 299 300 document.addEventListener('webkitTransitionEnd', function(e) { 301 if (e.target != overlay) 302 return; 303 304 assertFalse(overlay.classList.contains('transparent')); 305 expectEquals(numFrozenPages, frozenPages.length); 306 testDone(); 307 }); 308 309 OptionsPage.navigateToPage(overlayName); 310 var numFrozenPages = frozenPages.length; 311 expectGT(numFrozenPages, 0); 312 }); 313 314 /** 315 * TestFixture for OptionsPage WebUI testing including tab history and support 316 * for preference manipulation. If you don't need the features in the C++ 317 * fixture, use the simpler OptionsWebUITest (above) instead. 318 * @extends {testing.Test} 319 * @constructor 320 */ 321 function OptionsWebUIExtendedTest() {} 322 323 OptionsWebUIExtendedTest.prototype = { 324 __proto__: testing.Test.prototype, 325 326 /** @override */ 327 browsePreload: 'chrome://settings-frame', 328 329 /** @override */ 330 typedefCppFixture: 'OptionsBrowserTest', 331 332 testGenPreamble: function() { 333 // Start with no supervised users managed by this profile. 334 GEN(' ClearPref("' + MANAGED_USERS_PREF + '");'); 335 }, 336 337 /** @override */ 338 isAsync: true, 339 340 /** @override */ 341 setUp: function() { 342 // user-image-stream is a streaming video element used for capturing a 343 // user image during OOBE. 344 this.accessibilityAuditConfig.ignoreSelectors('videoWithoutCaptions', 345 '.user-image-stream'); 346 }, 347 348 /** 349 * Asserts that two non-nested arrays are equal. The arrays must contain only 350 * plain data types, no nested arrays or other objects. 351 * @param {Array} expected An array of expected values. 352 * @param {Array} result An array of actual values. 353 * @param {boolean} doSort If true, the arrays will be sorted before being 354 * compared. 355 * @param {string} description A brief description for the array of actual 356 * values, to use in an error message if the arrays differ. 357 * @private 358 */ 359 compareArrays_: function(expected, result, doSort, description) { 360 var errorMessage = '\n' + description + ': ' + result + 361 '\nExpected: ' + expected; 362 assertEquals(expected.length, result.length, errorMessage); 363 364 var expectedSorted = expected.slice(); 365 var resultSorted = result.slice(); 366 if (doSort) { 367 expectedSorted.sort(); 368 resultSorted.sort(); 369 } 370 371 for (var i = 0; i < expectedSorted.length; ++i) { 372 assertEquals(expectedSorted[i], resultSorted[i], errorMessage); 373 } 374 }, 375 376 /** 377 * Verifies that the correct pages are currently open/visible. 378 * @param {!Array.<string>} expectedPages An array of page names expected to 379 * be open, with the topmost listed last. 380 * @param {string=} opt_expectedUrl The URL path, including hash, expected to 381 * be open. If undefined, the topmost (last) page name in |expectedPages| 382 * will be used. In either case, 'chrome://settings-frame/' will be 383 * prepended. 384 * @private 385 */ 386 verifyOpenPages_: function(expectedPages, opt_expectedUrl) { 387 // Check the topmost page. 388 expectEquals(null, OptionsPage.getVisibleBubble()); 389 var currentPage = OptionsPage.getTopmostVisiblePage(); 390 391 var lastExpected = expectedPages[expectedPages.length - 1]; 392 expectEquals(lastExpected, currentPage.name); 393 // We'd like to check the title too, but we have to load the settings-frame 394 // instead of the outer settings page in order to have access to 395 // OptionsPage, and setting the title from within the settings-frame fails 396 // because of cross-origin access restrictions. 397 // TODO(pamg): Add a test fixture that loads chrome://settings and uses 398 // UI elements to access sub-pages, so we can test the titles and 399 // search-page URLs. 400 var expectedUrl = (typeof opt_expectedUrl == 'undefined') ? 401 lastExpected : opt_expectedUrl; 402 var fullExpectedUrl = 'chrome://settings-frame/' + expectedUrl; 403 expectEquals(fullExpectedUrl, window.location.href); 404 405 // Collect open pages. 406 var allPageNames = Object.keys(OptionsPage.registeredPages).concat( 407 Object.keys(OptionsPage.registeredOverlayPages)); 408 var openPages = []; 409 for (var i = 0; i < allPageNames.length; ++i) { 410 var name = allPageNames[i]; 411 var page = OptionsPage.registeredPages[name] || 412 OptionsPage.registeredOverlayPages[name]; 413 if (page.visible) 414 openPages.push(page.name); 415 } 416 417 this.compareArrays_(expectedPages, openPages, true, 'Open pages'); 418 }, 419 420 /* 421 * Verifies that the correct URLs are listed in the history. Asynchronous. 422 * @param {!Array.<string>} expectedHistory An array of URL paths expected to 423 * be in the tab navigation history, sorted by visit time, including the 424 * current page as the last entry. The base URL (chrome://settings-frame/) 425 * will be prepended to each. An initial 'about:blank' history entry is 426 * assumed and should not be included in this list. 427 * @param {Function=} callback A function to be called after the history has 428 * been verified successfully. May be undefined. 429 * @private 430 */ 431 verifyHistory_: function(expectedHistory, callback) { 432 var self = this; 433 OptionsWebUIExtendedTest.verifyHistoryCallback = function(results) { 434 // The history always starts with a blank page. 435 assertEquals('about:blank', results.shift()); 436 var fullExpectedHistory = []; 437 for (var i = 0; i < expectedHistory.length; ++i) { 438 fullExpectedHistory.push( 439 'chrome://settings-frame/' + expectedHistory[i]); 440 } 441 self.compareArrays_(fullExpectedHistory, results, false, 'History'); 442 callback(); 443 }; 444 445 // The C++ fixture will call verifyHistoryCallback with the results. 446 chrome.send('optionsTestReportHistory'); 447 }, 448 449 /** 450 * Overrides the page callbacks for the given OptionsPage overlay to verify 451 * that they are not called. 452 * @param {Object} overlay The singleton instance of the overlay. 453 * @private 454 */ 455 prohibitChangesToOverlay_: function(overlay) { 456 overlay.initializePage = 457 overlay.didShowPage = 458 overlay.didClosePage = function() { 459 assertTrue(false, 460 'Overlay was affected when changes were prohibited.'); 461 }; 462 }, 463 }; 464 465 /** 466 * Set by verifyHistory_ to incorporate a followup callback, then called by the 467 * C++ fixture with the navigation history to be verified. 468 * @type {Function} 469 */ 470 OptionsWebUIExtendedTest.verifyHistoryCallback = null; 471 472 // Show the search page with no query string, to fall back to the settings page. 473 // Test disabled because it's flaky. crbug.com/303841 474 TEST_F('OptionsWebUIExtendedTest', 'DISABLED_ShowSearchPageNoQuery', 475 function() { 476 OptionsPage.showPageByName('search'); 477 this.verifyOpenPages_(['settings']); 478 this.verifyHistory_(['settings'], testDone); 479 }); 480 481 // Show a page without updating history. 482 TEST_F('OptionsWebUIExtendedTest', 'ShowPageNoHistory', function() { 483 this.verifyOpenPages_(['settings'], ''); 484 // There are only two main pages, 'settings' and 'search'. It's not possible 485 // to show the search page using OptionsPage.showPageByName, because it 486 // reverts to the settings page if it has no search text set. So we show the 487 // search page by performing a search, then test showPageByName. 488 $('search-field').onsearch({currentTarget: {value: 'query'}}); 489 490 // The settings page is also still "open" (i.e., visible), in order to show 491 // the search results. Furthermore, the URL hasn't been updated in the parent 492 // page, because we've loaded the chrome-settings frame instead of the whole 493 // settings page, so the cross-origin call to set the URL fails. 494 this.verifyOpenPages_(['settings', 'search'], 'search#query'); 495 var self = this; 496 this.verifyHistory_(['', 'search#query'], function() { 497 OptionsPage.showPageByName('settings', false); 498 self.verifyOpenPages_(['settings'], 'search#query'); 499 self.verifyHistory_(['', 'search#query'], testDone); 500 }); 501 }); 502 503 TEST_F('OptionsWebUIExtendedTest', 'ShowPageWithHistory', function() { 504 // See comments for ShowPageNoHistory. 505 $('search-field').onsearch({currentTarget: {value: 'query'}}); 506 var self = this; 507 this.verifyHistory_(['', 'search#query'], function() { 508 OptionsPage.showPageByName('settings', true); 509 self.verifyOpenPages_(['settings'], '#query'); 510 self.verifyHistory_(['', 'search#query', '#query'], 511 testDone); 512 }); 513 }); 514 515 TEST_F('OptionsWebUIExtendedTest', 'ShowPageReplaceHistory', function() { 516 // See comments for ShowPageNoHistory. 517 $('search-field').onsearch({currentTarget: {value: 'query'}}); 518 var self = this; 519 this.verifyHistory_(['', 'search#query'], function() { 520 OptionsPage.showPageByName('settings', true, {'replaceState': true}); 521 self.verifyOpenPages_(['settings'], '#query'); 522 self.verifyHistory_(['', '#query'], testDone); 523 }); 524 }); 525 526 // This should be identical to ShowPageWithHisory. 527 TEST_F('OptionsWebUIExtendedTest', 'NavigateToPage', function() { 528 // See comments for ShowPageNoHistory. 529 $('search-field').onsearch({currentTarget: {value: 'query'}}); 530 var self = this; 531 this.verifyHistory_(['', 'search#query'], function() { 532 OptionsPage.navigateToPage('settings'); 533 self.verifyOpenPages_(['settings'], '#query'); 534 self.verifyHistory_(['', 'search#query', '#query'], 535 testDone); 536 }); 537 }); 538 539 // Settings overlays are much more straightforward than settings pages, opening 540 // normally with none of the latter's quirks in the expected history or URL. 541 TEST_F('OptionsWebUIExtendedTest', 'ShowOverlayNoHistory', function() { 542 // Open a layer-1 overlay, not updating history. 543 OptionsPage.showPageByName('languages', false); 544 this.verifyOpenPages_(['settings', 'languages'], ''); 545 546 var self = this; 547 this.verifyHistory_([''], function() { 548 // Open a layer-2 overlay for which the layer-1 is a parent, not updating 549 // history. 550 OptionsPage.showPageByName('addLanguage', false); 551 self.verifyOpenPages_(['settings', 'languages', 'addLanguage'], 552 ''); 553 self.verifyHistory_([''], testDone); 554 }); 555 }); 556 557 TEST_F('OptionsWebUIExtendedTest', 'ShowOverlayWithHistory', function() { 558 // Open a layer-1 overlay, updating history. 559 OptionsPage.showPageByName('languages', true); 560 this.verifyOpenPages_(['settings', 'languages']); 561 562 var self = this; 563 this.verifyHistory_(['', 'languages'], function() { 564 // Open a layer-2 overlay, updating history. 565 OptionsPage.showPageByName('addLanguage', true); 566 self.verifyOpenPages_(['settings', 'languages', 'addLanguage']); 567 self.verifyHistory_(['', 'languages', 'addLanguage'], testDone); 568 }); 569 }); 570 571 TEST_F('OptionsWebUIExtendedTest', 'ShowOverlayReplaceHistory', function() { 572 // Open a layer-1 overlay, updating history. 573 OptionsPage.showPageByName('languages', true); 574 var self = this; 575 this.verifyHistory_(['', 'languages'], function() { 576 // Open a layer-2 overlay, replacing history. 577 OptionsPage.showPageByName('addLanguage', true, {'replaceState': true}); 578 self.verifyOpenPages_(['settings', 'languages', 'addLanguage']); 579 self.verifyHistory_(['', 'addLanguage'], testDone); 580 }); 581 }); 582 583 // Directly show an overlay further above this page, i.e. one for which the 584 // current page is an ancestor but not a parent. 585 TEST_F('OptionsWebUIExtendedTest', 'ShowOverlayFurtherAbove', function() { 586 // Open a layer-2 overlay directly. 587 OptionsPage.showPageByName('addLanguage', true); 588 this.verifyOpenPages_(['settings', 'languages', 'addLanguage']); 589 var self = this; 590 this.verifyHistory_(['', 'addLanguage'], testDone); 591 }); 592 593 // Directly show a layer-2 overlay for which the layer-1 overlay is not a 594 // parent. 595 TEST_F('OptionsWebUIExtendedTest', 'ShowUnrelatedOverlay', function() { 596 // Open a layer-1 overlay. 597 OptionsPage.showPageByName('languages', true); 598 this.verifyOpenPages_(['settings', 'languages']); 599 600 var self = this; 601 this.verifyHistory_(['', 'languages'], function() { 602 // Open an unrelated layer-2 overlay. 603 OptionsPage.showPageByName('cookies', true); 604 self.verifyOpenPages_(['settings', 'content', 'cookies']); 605 self.verifyHistory_(['', 'languages', 'cookies'], testDone); 606 }); 607 }); 608 609 // Close an overlay. 610 TEST_F('OptionsWebUIExtendedTest', 'CloseOverlay', function() { 611 // Open a layer-1 overlay, then a layer-2 overlay on top of it. 612 OptionsPage.showPageByName('languages', true); 613 this.verifyOpenPages_(['settings', 'languages']); 614 OptionsPage.showPageByName('addLanguage', true); 615 this.verifyOpenPages_(['settings', 'languages', 'addLanguage']); 616 617 var self = this; 618 this.verifyHistory_(['', 'languages', 'addLanguage'], function() { 619 // Close the layer-2 overlay. 620 OptionsPage.closeOverlay(); 621 self.verifyOpenPages_(['settings', 'languages']); 622 self.verifyHistory_( 623 ['', 'languages', 'addLanguage', 'languages'], 624 function() { 625 // Close the layer-1 overlay. 626 OptionsPage.closeOverlay(); 627 self.verifyOpenPages_(['settings'], ''); 628 self.verifyHistory_( 629 ['', 'languages', 'addLanguage', 'languages', ''], 630 testDone); 631 }); 632 }); 633 }); 634 635 // Test that closing an overlay that did not push history when opening does not 636 // again push history. 637 TEST_F('OptionsWebUIExtendedTest', 'CloseOverlayNoHistory', function() { 638 // Open the do not track confirmation prompt. 639 OptionsPage.showPageByName('doNotTrackConfirm', false); 640 641 // Opening the prompt does not add to the history. 642 this.verifyHistory_([''], function() { 643 // Close the overlay. 644 OptionsPage.closeOverlay(); 645 // Still no history changes. 646 this.verifyHistory_([''], testDone); 647 }.bind(this)); 648 }); 649 650 // Make sure an overlay isn't closed (even temporarily) when another overlay is 651 // opened on top. 652 TEST_F('OptionsWebUIExtendedTest', 'OverlayAboveNoReset', function() { 653 // Open a layer-1 overlay. 654 OptionsPage.showPageByName('languages', true); 655 this.verifyOpenPages_(['settings', 'languages']); 656 657 // Open a layer-2 overlay on top. This should not close 'languages'. 658 this.prohibitChangesToOverlay_(options.LanguageOptions.getInstance()); 659 OptionsPage.showPageByName('addLanguage', true); 660 this.verifyOpenPages_(['settings', 'languages', 'addLanguage']); 661 testDone(); 662 }); 663 664 TEST_F('OptionsWebUIExtendedTest', 'OverlayTabNavigation', function() { 665 // Open a layer-1 overlay, then a layer-2 overlay on top of it. 666 OptionsPage.showPageByName('languages', true); 667 OptionsPage.showPageByName('addLanguage', true); 668 var self = this; 669 670 // Go back twice, then forward twice. 671 self.verifyOpenPages_(['settings', 'languages', 'addLanguage']); 672 self.verifyHistory_(['', 'languages', 'addLanguage'], function() { 673 window.history.back(); 674 waitForPopstate(function() { 675 self.verifyOpenPages_(['settings', 'languages']); 676 self.verifyHistory_(['', 'languages'], function() { 677 window.history.back(); 678 waitForPopstate(function() { 679 self.verifyOpenPages_(['settings'], ''); 680 self.verifyHistory_([''], function() { 681 window.history.forward(); 682 waitForPopstate(function() { 683 self.verifyOpenPages_(['settings', 'languages']); 684 self.verifyHistory_(['', 'languages'], function() { 685 window.history.forward(); 686 waitForPopstate(function() { 687 self.verifyOpenPages_( 688 ['settings', 'languages', 'addLanguage']); 689 self.verifyHistory_( 690 ['', 'languages', 'addLanguage'], testDone); 691 }); 692 }); 693 }); 694 }); 695 }); 696 }); 697 }); 698 }); 699 }); 700 701 // Going "back" to an overlay that's a child of the current overlay shouldn't 702 // close the current one. 703 TEST_F('OptionsWebUIExtendedTest', 'OverlayBackToChild', function() { 704 // Open a layer-1 overlay, then a layer-2 overlay on top of it. 705 OptionsPage.showPageByName('languages', true); 706 OptionsPage.showPageByName('addLanguage', true); 707 var self = this; 708 709 self.verifyOpenPages_(['settings', 'languages', 'addLanguage']); 710 self.verifyHistory_(['', 'languages', 'addLanguage'], function() { 711 // Close the top overlay, then go back to it. 712 OptionsPage.closeOverlay(); 713 self.verifyOpenPages_(['settings', 'languages']); 714 self.verifyHistory_( 715 ['', 'languages', 'addLanguage', 'languages'], 716 function() { 717 // Going back to the 'addLanguage' page should not close 'languages'. 718 self.prohibitChangesToOverlay_(options.LanguageOptions.getInstance()); 719 window.history.back(); 720 waitForPopstate(function() { 721 self.verifyOpenPages_(['settings', 'languages', 'addLanguage']); 722 self.verifyHistory_(['', 'languages', 'addLanguage'], 723 testDone); 724 }); 725 }); 726 }); 727 }); 728 729 // Going back to an unrelated overlay should close the overlay and its parent. 730 TEST_F('OptionsWebUIExtendedTest', 'OverlayBackToUnrelated', function() { 731 // Open a layer-1 overlay, then an unrelated layer-2 overlay. 732 OptionsPage.showPageByName('languages', true); 733 OptionsPage.showPageByName('cookies', true); 734 var self = this; 735 self.verifyOpenPages_(['settings', 'content', 'cookies']); 736 self.verifyHistory_(['', 'languages', 'cookies'], function() { 737 window.history.back(); 738 waitForPopstate(function() { 739 self.verifyOpenPages_(['settings', 'languages']); 740 testDone(); 741 }); 742 }); 743 }); 744 745 // Verify history changes properly while the page is loading. 746 TEST_F('OptionsWebUIExtendedTest', 'HistoryUpdatedAfterLoading', function() { 747 var loc = location.href; 748 749 document.documentElement.classList.add('loading'); 750 assertTrue(OptionsPage.isLoading()); 751 OptionsPage.navigateToPage('searchEngines'); 752 expectNotEquals(loc, location.href); 753 754 document.documentElement.classList.remove('loading'); 755 assertFalse(OptionsPage.isLoading()); 756 OptionsPage.showDefaultPage(); 757 expectEquals(loc, location.href); 758 759 testDone(); 760 }); 761 762 // A tip should be shown or hidden depending on whether this profile manages any 763 // supervised users. 764 TEST_F('OptionsWebUIExtendedTest', 'SupervisingUsers', function() { 765 // We start managing no supervised users. 766 assertTrue($('profiles-supervised-dashboard-tip').hidden); 767 768 // Remove all supervised users, then add some, watching for the pref change 769 // notifications and UI updates in each case. Any non-empty pref dictionary 770 // is interpreted as having supervised users. 771 chrome.send('optionsTestSetPref', [MANAGED_USERS_PREF, {key: 'value'}]); 772 waitForResponse(BrowserOptions, 'updateManagesSupervisedUsers', function() { 773 assertFalse($('profiles-supervised-dashboard-tip').hidden); 774 chrome.send('optionsTestSetPref', [MANAGED_USERS_PREF, {}]); 775 waitForResponse(BrowserOptions, 'updateManagesSupervisedUsers', function() { 776 assertTrue($('profiles-supervised-dashboard-tip').hidden); 777 testDone(); 778 }); 779 }); 780 }); 781 782 /** 783 * TestFixture that loads the options page at a bogus URL. 784 * @extends {OptionsWebUIExtendedTest} 785 * @constructor 786 */ 787 function OptionsWebUIRedirectTest() { 788 OptionsWebUIExtendedTest.call(this); 789 } 790 791 OptionsWebUIRedirectTest.prototype = { 792 __proto__: OptionsWebUIExtendedTest.prototype, 793 794 /** @override */ 795 browsePreload: 'chrome://settings-frame/nonexistantPage', 796 }; 797 798 TEST_F('OptionsWebUIRedirectTest', 'TestURL', function() { 799 assertEquals('chrome://settings-frame/', document.location.href); 800 this.verifyHistory_([''], testDone); 801 }); 802