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