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