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