options_browsertest.js revision 5f1c94371a64b3196d4be9466099bb892df9b88e
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 5GEN('#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 */ 18function 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 */ 34function waitForPopstate(afterFunction) { 35 waitForResponse(window, 'onpopstate', afterFunction); 36} 37 38/** 39 * TestFixture for OptionsPage WebUI testing. 40 * @extends {testing.Test} 41 * @constructor 42 */ 43function OptionsWebUITest() {} 44 45OptionsWebUITest.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 93GEN('#if defined(OS_MACOSX)'); 94GEN('#define MAYBE_testSetBooleanPrefTriggers ' + 95 'DISABLED_testSetBooleanPrefTriggers'); 96GEN('#else'); 97GEN('#define MAYBE_testSetBooleanPrefTriggers testSetBooleanPrefTriggers'); 98GEN('#endif // defined(OS_MACOSX)'); 99 100TEST_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 119TEST_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 */ 137TEST_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 */ 163OptionsWebUITest.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 = PageManager.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 207TEST_F('OptionsWebUITest', 'EnableDoNotTrackAndConfirmInterstitial', 208 function() { 209 this.testDoNotTrackInterstitial(true); 210}); 211 212TEST_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. 219TEST_F('OptionsWebUITest', 'EnableAndDisableDoNotTrack', function() { 220 Preferences.prefsFetchedCallback({'enable_do_not_track': {'value': false}}); 221 var dntCheckbox = $('do-not-track-enabled'); 222 var dntOverlay = PageManager.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. 270TEST_F('OptionsWebUITest', 'EnterPreventsDefault', function() { 271 var page = HomePageOverlay.getInstance(); 272 PageManager.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. 285TEST_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. 294TEST_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 PageManager.showPageByName(overlayName); 310 var numFrozenPages = frozenPages.length; 311 expectGT(numFrozenPages, 0); 312}); 313 314GEN('#if defined(OS_CHROMEOS)'); 315// Verify that range inputs respond to touch events. Currently only Chrome OS 316// uses slider options. 317TEST_F('OptionsWebUITest', 'RangeInputHandlesTouchEvents', function() { 318 this.mockHandler.expects(once()).setIntegerPref([ 319 'settings.touchpad.sensitivity2', 1]); 320 321 var touchpadRange = $('touchpad-sensitivity-range'); 322 var event = document.createEvent('UIEvent'); 323 event.initUIEvent('touchstart', true, true, window); 324 touchpadRange.dispatchEvent(event); 325 326 event = document.createEvent('UIEvent'); 327 event.initUIEvent('touchmove', true, true, window); 328 touchpadRange.dispatchEvent(event); 329 330 touchpadRange.value = 1; 331 332 event = document.createEvent('UIEvent'); 333 event.initUIEvent('touchend', true, true, window); 334 touchpadRange.dispatchEvent(event); 335 336 // touchcancel should also trigger the handler, since it 337 // changes the slider position. 338 this.mockHandler.expects(once()).setIntegerPref([ 339 'settings.touchpad.sensitivity2', 2]); 340 341 event = document.createEvent('UIEvent'); 342 event.initUIEvent('touchstart', true, true, window); 343 touchpadRange.dispatchEvent(event); 344 345 touchpadRange.value = 2; 346 347 event = document.createEvent('UIEvent'); 348 event.initUIEvent('touchcancel', true, true, window); 349 touchpadRange.dispatchEvent(event); 350 351 testDone(); 352}); 353GEN('#endif'); // defined(OS_CHROMEOS) 354 355/** 356 * TestFixture for OptionsPage WebUI testing including tab history and support 357 * for preference manipulation. If you don't need the features in the C++ 358 * fixture, use the simpler OptionsWebUITest (above) instead. 359 * @extends {testing.Test} 360 * @constructor 361 */ 362function OptionsWebUIExtendedTest() {} 363 364OptionsWebUIExtendedTest.prototype = { 365 __proto__: testing.Test.prototype, 366 367 /** @override */ 368 browsePreload: 'chrome://settings-frame', 369 370 /** @override */ 371 typedefCppFixture: 'OptionsBrowserTest', 372 373 testGenPreamble: function() { 374 // Start with no supervised users managed by this profile. 375 GEN(' ClearPref("' + SUPERVISED_USERS_PREF + '");'); 376 }, 377 378 /** @override */ 379 isAsync: true, 380 381 /** @override */ 382 setUp: function() { 383 // user-image-stream is a streaming video element used for capturing a 384 // user image during OOBE. 385 this.accessibilityAuditConfig.ignoreSelectors('videoWithoutCaptions', 386 '.user-image-stream'); 387 }, 388 389 /** 390 * Asserts that two non-nested arrays are equal. The arrays must contain only 391 * plain data types, no nested arrays or other objects. 392 * @param {Array} expected An array of expected values. 393 * @param {Array} result An array of actual values. 394 * @param {boolean} doSort If true, the arrays will be sorted before being 395 * compared. 396 * @param {string} description A brief description for the array of actual 397 * values, to use in an error message if the arrays differ. 398 * @private 399 */ 400 compareArrays_: function(expected, result, doSort, description) { 401 var errorMessage = '\n' + description + ': ' + result + 402 '\nExpected: ' + expected; 403 assertEquals(expected.length, result.length, errorMessage); 404 405 var expectedSorted = expected.slice(); 406 var resultSorted = result.slice(); 407 if (doSort) { 408 expectedSorted.sort(); 409 resultSorted.sort(); 410 } 411 412 for (var i = 0; i < expectedSorted.length; ++i) { 413 assertEquals(expectedSorted[i], resultSorted[i], errorMessage); 414 } 415 }, 416 417 /** 418 * Verifies that the correct pages are currently open/visible. 419 * @param {!Array.<string>} expectedPages An array of page names expected to 420 * be open, with the topmost listed last. 421 * @param {string=} opt_expectedUrl The URL path, including hash, expected to 422 * be open. If undefined, the topmost (last) page name in |expectedPages| 423 * will be used. In either case, 'chrome://settings-frame/' will be 424 * prepended. 425 * @private 426 */ 427 verifyOpenPages_: function(expectedPages, opt_expectedUrl) { 428 // Check the topmost page. 429 expectEquals(null, PageManager.getVisibleBubble()); 430 var currentPage = PageManager.getTopmostVisiblePage(); 431 432 var lastExpected = expectedPages[expectedPages.length - 1]; 433 expectEquals(lastExpected, currentPage.name); 434 // We'd like to check the title too, but we have to load the settings-frame 435 // instead of the outer settings page in order to have access to 436 // OptionsPage, and setting the title from within the settings-frame fails 437 // because of cross-origin access restrictions. 438 // TODO(pamg): Add a test fixture that loads chrome://settings and uses 439 // UI elements to access sub-pages, so we can test the titles and 440 // search-page URLs. 441 var expectedUrl = (typeof opt_expectedUrl == 'undefined') ? 442 lastExpected : opt_expectedUrl; 443 var fullExpectedUrl = 'chrome://settings-frame/' + expectedUrl; 444 expectEquals(fullExpectedUrl, window.location.href); 445 446 // Collect open pages. 447 var allPageNames = Object.keys(PageManager.registeredPages).concat( 448 Object.keys(PageManager.registeredOverlayPages)); 449 var openPages = []; 450 for (var i = 0; i < allPageNames.length; ++i) { 451 var name = allPageNames[i]; 452 var page = PageManager.registeredPages[name] || 453 PageManager.registeredOverlayPages[name]; 454 if (page.visible) 455 openPages.push(page.name); 456 } 457 458 this.compareArrays_(expectedPages, openPages, true, 'Open pages'); 459 }, 460 461 /* 462 * Verifies that the correct URLs are listed in the history. Asynchronous. 463 * @param {!Array.<string>} expectedHistory An array of URL paths expected to 464 * be in the tab navigation history, sorted by visit time, including the 465 * current page as the last entry. The base URL (chrome://settings-frame/) 466 * will be prepended to each. An initial 'about:blank' history entry is 467 * assumed and should not be included in this list. 468 * @param {Function=} callback A function to be called after the history has 469 * been verified successfully. May be undefined. 470 * @private 471 */ 472 verifyHistory_: function(expectedHistory, callback) { 473 var self = this; 474 OptionsWebUIExtendedTest.verifyHistoryCallback = function(results) { 475 // The history always starts with a blank page. 476 assertEquals('about:blank', results.shift()); 477 var fullExpectedHistory = []; 478 for (var i = 0; i < expectedHistory.length; ++i) { 479 fullExpectedHistory.push( 480 'chrome://settings-frame/' + expectedHistory[i]); 481 } 482 self.compareArrays_(fullExpectedHistory, results, false, 'History'); 483 callback(); 484 }; 485 486 // The C++ fixture will call verifyHistoryCallback with the results. 487 chrome.send('optionsTestReportHistory'); 488 }, 489 490 /** 491 * Overrides the page callbacks for the given PageManager overlay to verify 492 * that they are not called. 493 * @param {Object} overlay The singleton instance of the overlay. 494 * @private 495 */ 496 prohibitChangesToOverlay_: function(overlay) { 497 overlay.initializePage = 498 overlay.didShowPage = 499 overlay.didClosePage = function() { 500 assertTrue(false, 501 'Overlay was affected when changes were prohibited.'); 502 }; 503 }, 504}; 505 506/** 507 * Set by verifyHistory_ to incorporate a followup callback, then called by the 508 * C++ fixture with the navigation history to be verified. 509 * @type {Function} 510 */ 511OptionsWebUIExtendedTest.verifyHistoryCallback = null; 512 513// Show the search page with no query string, to fall back to the settings page. 514// Test disabled because it's flaky. crbug.com/303841 515TEST_F('OptionsWebUIExtendedTest', 'DISABLED_ShowSearchPageNoQuery', 516 function() { 517 PageManager.showPageByName('search'); 518 this.verifyOpenPages_(['settings']); 519 this.verifyHistory_(['settings'], testDone); 520}); 521 522// Show a page without updating history. 523TEST_F('OptionsWebUIExtendedTest', 'ShowPageNoHistory', function() { 524 this.verifyOpenPages_(['settings'], ''); 525 // There are only two main pages, 'settings' and 'search'. It's not possible 526 // to show the search page using PageManager.showPageByName, because it 527 // reverts to the settings page if it has no search text set. So we show the 528 // search page by performing a search, then test showPageByName. 529 $('search-field').onsearch({currentTarget: {value: 'query'}}); 530 531 // The settings page is also still "open" (i.e., visible), in order to show 532 // the search results. Furthermore, the URL hasn't been updated in the parent 533 // page, because we've loaded the chrome-settings frame instead of the whole 534 // settings page, so the cross-origin call to set the URL fails. 535 this.verifyOpenPages_(['settings', 'search'], 'search#query'); 536 var self = this; 537 this.verifyHistory_(['', 'search#query'], function() { 538 PageManager.showPageByName('settings', false); 539 self.verifyOpenPages_(['settings'], 'search#query'); 540 self.verifyHistory_(['', 'search#query'], testDone); 541 }); 542}); 543 544TEST_F('OptionsWebUIExtendedTest', 'ShowPageWithHistory', function() { 545 // See comments for ShowPageNoHistory. 546 $('search-field').onsearch({currentTarget: {value: 'query'}}); 547 var self = this; 548 this.verifyHistory_(['', 'search#query'], function() { 549 PageManager.showPageByName('settings', true); 550 self.verifyOpenPages_(['settings'], '#query'); 551 self.verifyHistory_(['', 'search#query', '#query'], 552 testDone); 553 }); 554}); 555 556TEST_F('OptionsWebUIExtendedTest', 'ShowPageReplaceHistory', function() { 557 // See comments for ShowPageNoHistory. 558 $('search-field').onsearch({currentTarget: {value: 'query'}}); 559 var self = this; 560 this.verifyHistory_(['', 'search#query'], function() { 561 PageManager.showPageByName('settings', true, {'replaceState': true}); 562 self.verifyOpenPages_(['settings'], '#query'); 563 self.verifyHistory_(['', '#query'], testDone); 564 }); 565}); 566 567// This should be identical to ShowPageWithHisory. 568TEST_F('OptionsWebUIExtendedTest', 'NavigateToPage', function() { 569 // See comments for ShowPageNoHistory. 570 $('search-field').onsearch({currentTarget: {value: 'query'}}); 571 var self = this; 572 this.verifyHistory_(['', 'search#query'], function() { 573 PageManager.showPageByName('settings'); 574 self.verifyOpenPages_(['settings'], '#query'); 575 self.verifyHistory_(['', 'search#query', '#query'], 576 testDone); 577 }); 578}); 579 580// Settings overlays are much more straightforward than settings pages, opening 581// normally with none of the latter's quirks in the expected history or URL. 582TEST_F('OptionsWebUIExtendedTest', 'ShowOverlayNoHistory', function() { 583 // Open a layer-1 overlay, not updating history. 584 PageManager.showPageByName('languages', false); 585 this.verifyOpenPages_(['settings', 'languages'], ''); 586 587 var self = this; 588 this.verifyHistory_([''], function() { 589 // Open a layer-2 overlay for which the layer-1 is a parent, not updating 590 // history. 591 PageManager.showPageByName('addLanguage', false); 592 self.verifyOpenPages_(['settings', 'languages', 'addLanguage'], 593 ''); 594 self.verifyHistory_([''], testDone); 595 }); 596}); 597 598TEST_F('OptionsWebUIExtendedTest', 'ShowOverlayWithHistory', function() { 599 // Open a layer-1 overlay, updating history. 600 PageManager.showPageByName('languages', true); 601 this.verifyOpenPages_(['settings', 'languages']); 602 603 var self = this; 604 this.verifyHistory_(['', 'languages'], function() { 605 // Open a layer-2 overlay, updating history. 606 PageManager.showPageByName('addLanguage', true); 607 self.verifyOpenPages_(['settings', 'languages', 'addLanguage']); 608 self.verifyHistory_(['', 'languages', 'addLanguage'], testDone); 609 }); 610}); 611 612TEST_F('OptionsWebUIExtendedTest', 'ShowOverlayReplaceHistory', function() { 613 // Open a layer-1 overlay, updating history. 614 PageManager.showPageByName('languages', true); 615 var self = this; 616 this.verifyHistory_(['', 'languages'], function() { 617 // Open a layer-2 overlay, replacing history. 618 PageManager.showPageByName('addLanguage', true, {'replaceState': true}); 619 self.verifyOpenPages_(['settings', 'languages', 'addLanguage']); 620 self.verifyHistory_(['', 'addLanguage'], testDone); 621 }); 622}); 623 624// Directly show an overlay further above this page, i.e. one for which the 625// current page is an ancestor but not a parent. 626TEST_F('OptionsWebUIExtendedTest', 'ShowOverlayFurtherAbove', function() { 627 // Open a layer-2 overlay directly. 628 PageManager.showPageByName('addLanguage', true); 629 this.verifyOpenPages_(['settings', 'languages', 'addLanguage']); 630 var self = this; 631 this.verifyHistory_(['', 'addLanguage'], testDone); 632}); 633 634// Directly show a layer-2 overlay for which the layer-1 overlay is not a 635// parent. 636TEST_F('OptionsWebUIExtendedTest', 'ShowUnrelatedOverlay', function() { 637 // Open a layer-1 overlay. 638 PageManager.showPageByName('languages', true); 639 this.verifyOpenPages_(['settings', 'languages']); 640 641 var self = this; 642 this.verifyHistory_(['', 'languages'], function() { 643 // Open an unrelated layer-2 overlay. 644 PageManager.showPageByName('cookies', true); 645 self.verifyOpenPages_(['settings', 'content', 'cookies']); 646 self.verifyHistory_(['', 'languages', 'cookies'], testDone); 647 }); 648}); 649 650// Close an overlay. 651TEST_F('OptionsWebUIExtendedTest', 'CloseOverlay', function() { 652 // Open a layer-1 overlay, then a layer-2 overlay on top of it. 653 PageManager.showPageByName('languages', true); 654 this.verifyOpenPages_(['settings', 'languages']); 655 PageManager.showPageByName('addLanguage', true); 656 this.verifyOpenPages_(['settings', 'languages', 'addLanguage']); 657 658 var self = this; 659 this.verifyHistory_(['', 'languages', 'addLanguage'], function() { 660 // Close the layer-2 overlay. 661 PageManager.closeOverlay(); 662 self.verifyOpenPages_(['settings', 'languages']); 663 self.verifyHistory_( 664 ['', 'languages', 'addLanguage', 'languages'], 665 function() { 666 // Close the layer-1 overlay. 667 PageManager.closeOverlay(); 668 self.verifyOpenPages_(['settings'], ''); 669 self.verifyHistory_( 670 ['', 'languages', 'addLanguage', 'languages', ''], 671 testDone); 672 }); 673 }); 674}); 675 676// Test that closing an overlay that did not push history when opening does not 677// again push history. 678TEST_F('OptionsWebUIExtendedTest', 'CloseOverlayNoHistory', function() { 679 // Open the do not track confirmation prompt. 680 PageManager.showPageByName('doNotTrackConfirm', false); 681 682 // Opening the prompt does not add to the history. 683 this.verifyHistory_([''], function() { 684 // Close the overlay. 685 PageManager.closeOverlay(); 686 // Still no history changes. 687 this.verifyHistory_([''], testDone); 688 }.bind(this)); 689}); 690 691// Make sure an overlay isn't closed (even temporarily) when another overlay is 692// opened on top. 693TEST_F('OptionsWebUIExtendedTest', 'OverlayAboveNoReset', function() { 694 // Open a layer-1 overlay. 695 PageManager.showPageByName('languages', true); 696 this.verifyOpenPages_(['settings', 'languages']); 697 698 // Open a layer-2 overlay on top. This should not close 'languages'. 699 this.prohibitChangesToOverlay_(options.LanguageOptions.getInstance()); 700 PageManager.showPageByName('addLanguage', true); 701 this.verifyOpenPages_(['settings', 'languages', 'addLanguage']); 702 testDone(); 703}); 704 705TEST_F('OptionsWebUIExtendedTest', 'OverlayTabNavigation', function() { 706 // Open a layer-1 overlay, then a layer-2 overlay on top of it. 707 PageManager.showPageByName('languages', true); 708 PageManager.showPageByName('addLanguage', true); 709 var self = this; 710 711 // Go back twice, then forward twice. 712 self.verifyOpenPages_(['settings', 'languages', 'addLanguage']); 713 self.verifyHistory_(['', 'languages', 'addLanguage'], function() { 714 window.history.back(); 715 waitForPopstate(function() { 716 self.verifyOpenPages_(['settings', 'languages']); 717 self.verifyHistory_(['', 'languages'], function() { 718 window.history.back(); 719 waitForPopstate(function() { 720 self.verifyOpenPages_(['settings'], ''); 721 self.verifyHistory_([''], function() { 722 window.history.forward(); 723 waitForPopstate(function() { 724 self.verifyOpenPages_(['settings', 'languages']); 725 self.verifyHistory_(['', 'languages'], function() { 726 window.history.forward(); 727 waitForPopstate(function() { 728 self.verifyOpenPages_( 729 ['settings', 'languages', 'addLanguage']); 730 self.verifyHistory_( 731 ['', 'languages', 'addLanguage'], testDone); 732 }); 733 }); 734 }); 735 }); 736 }); 737 }); 738 }); 739 }); 740}); 741 742// Going "back" to an overlay that's a child of the current overlay shouldn't 743// close the current one. 744TEST_F('OptionsWebUIExtendedTest', 'OverlayBackToChild', function() { 745 // Open a layer-1 overlay, then a layer-2 overlay on top of it. 746 PageManager.showPageByName('languages', true); 747 PageManager.showPageByName('addLanguage', true); 748 var self = this; 749 750 self.verifyOpenPages_(['settings', 'languages', 'addLanguage']); 751 self.verifyHistory_(['', 'languages', 'addLanguage'], function() { 752 // Close the top overlay, then go back to it. 753 PageManager.closeOverlay(); 754 self.verifyOpenPages_(['settings', 'languages']); 755 self.verifyHistory_( 756 ['', 'languages', 'addLanguage', 'languages'], 757 function() { 758 // Going back to the 'addLanguage' page should not close 'languages'. 759 self.prohibitChangesToOverlay_(options.LanguageOptions.getInstance()); 760 window.history.back(); 761 waitForPopstate(function() { 762 self.verifyOpenPages_(['settings', 'languages', 'addLanguage']); 763 self.verifyHistory_(['', 'languages', 'addLanguage'], 764 testDone); 765 }); 766 }); 767 }); 768}); 769 770// Going back to an unrelated overlay should close the overlay and its parent. 771TEST_F('OptionsWebUIExtendedTest', 'OverlayBackToUnrelated', function() { 772 // Open a layer-1 overlay, then an unrelated layer-2 overlay. 773 PageManager.showPageByName('languages', true); 774 PageManager.showPageByName('cookies', true); 775 var self = this; 776 self.verifyOpenPages_(['settings', 'content', 'cookies']); 777 self.verifyHistory_(['', 'languages', 'cookies'], function() { 778 window.history.back(); 779 waitForPopstate(function() { 780 self.verifyOpenPages_(['settings', 'languages']); 781 testDone(); 782 }); 783 }); 784}); 785 786// Verify history changes properly while the page is loading. 787TEST_F('OptionsWebUIExtendedTest', 'HistoryUpdatedAfterLoading', function() { 788 var loc = location.href; 789 790 document.documentElement.classList.add('loading'); 791 assertTrue(PageManager.isLoading()); 792 PageManager.showPageByName('searchEngines'); 793 expectNotEquals(loc, location.href); 794 795 document.documentElement.classList.remove('loading'); 796 assertFalse(PageManager.isLoading()); 797 PageManager.showDefaultPage(); 798 expectEquals(loc, location.href); 799 800 testDone(); 801}); 802 803// A tip should be shown or hidden depending on whether this profile manages any 804// supervised users. 805TEST_F('OptionsWebUIExtendedTest', 'SupervisingUsers', function() { 806 // We start managing no supervised users. 807 assertTrue($('profiles-supervised-dashboard-tip').hidden); 808 809 // Remove all supervised users, then add some, watching for the pref change 810 // notifications and UI updates in each case. Any non-empty pref dictionary 811 // is interpreted as having supervised users. 812 chrome.send('optionsTestSetPref', [SUPERVISED_USERS_PREF, {key: 'value'}]); 813 waitForResponse(BrowserOptions, 'updateManagesSupervisedUsers', function() { 814 assertFalse($('profiles-supervised-dashboard-tip').hidden); 815 chrome.send('optionsTestSetPref', [SUPERVISED_USERS_PREF, {}]); 816 waitForResponse(BrowserOptions, 'updateManagesSupervisedUsers', function() { 817 assertTrue($('profiles-supervised-dashboard-tip').hidden); 818 testDone(); 819 }); 820 }); 821}); 822 823/** 824 * TestFixture that loads the options page at a bogus URL. 825 * @extends {OptionsWebUIExtendedTest} 826 * @constructor 827 */ 828function OptionsWebUIRedirectTest() { 829 OptionsWebUIExtendedTest.call(this); 830} 831 832OptionsWebUIRedirectTest.prototype = { 833 __proto__: OptionsWebUIExtendedTest.prototype, 834 835 /** @override */ 836 browsePreload: 'chrome://settings-frame/nonexistantPage', 837}; 838 839TEST_F('OptionsWebUIRedirectTest', 'TestURL', function() { 840 assertEquals('chrome://settings-frame/', document.location.href); 841 this.verifyHistory_([''], testDone); 842}); 843