Home | History | Annotate | Download | only in options
      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