options_browsertest.js revision f2477e01787aa58f445919b809d89e252beef54f
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 MANAGED_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 */
13function waitForResponse(object, methodName, afterFunction) {
14  var originalCallback = object[methodName];
15
16  // Install a wrapper that temporarily replaces the original function.
17  object[methodName] = function() {
18    object[methodName] = originalCallback;
19    originalCallback.apply(this, arguments);
20    afterFunction();
21  };
22}
23
24/**
25  * Wait for the global window.onpopstate callback to be called (after a tab
26  * history navigation), then execute |afterFunction|.
27  */
28function waitForPopstate(afterFunction) {
29  waitForResponse(window, 'onpopstate', afterFunction);
30}
31
32/**
33 * TestFixture for OptionsPage WebUI testing.
34 * @extends {testing.Test}
35 * @constructor
36 */
37function OptionsWebUITest() {}
38
39OptionsWebUITest.prototype = {
40  __proto__: testing.Test.prototype,
41
42  /** @override */
43  accessibilityIssuesAreErrors: true,
44
45  /** @override */
46  setUp: function() {
47    // user-image-stream is a streaming video element used for capturing a
48    // user image during OOBE.
49    this.accessibilityAuditConfig.ignoreSelectors('videoWithoutCaptions',
50                                                  '.user-image-stream');
51  },
52
53  /**
54   * Browse to the options page & call our preLoad().
55   */
56  browsePreload: 'chrome://settings-frame',
57
58  isAsync: true,
59
60  /**
61   * Register a mock handler to ensure expectations are met and options pages
62   * behave correctly.
63   */
64  preLoad: function() {
65    this.makeAndRegisterMockHandler(
66        ['defaultZoomFactorAction',
67         'fetchPrefs',
68         'observePrefs',
69         'setBooleanPref',
70         'setIntegerPref',
71         'setDoublePref',
72         'setStringPref',
73         'setObjectPref',
74         'clearPref',
75         'coreOptionsUserMetricsAction',
76        ]);
77
78    // Register stubs for methods expected to be called before/during tests.
79    // Specific expectations can be made in the tests themselves.
80    this.mockHandler.stubs().fetchPrefs(ANYTHING);
81    this.mockHandler.stubs().observePrefs(ANYTHING);
82    this.mockHandler.stubs().coreOptionsUserMetricsAction(ANYTHING);
83  },
84};
85
86// Crashes on Mac only. See http://crbug.com/79181
87GEN('#if defined(OS_MACOSX)');
88GEN('#define MAYBE_testSetBooleanPrefTriggers ' +
89    'DISABLED_testSetBooleanPrefTriggers');
90GEN('#else');
91GEN('#define MAYBE_testSetBooleanPrefTriggers testSetBooleanPrefTriggers');
92GEN('#endif  // defined(OS_MACOSX)');
93
94TEST_F('OptionsWebUITest', 'MAYBE_testSetBooleanPrefTriggers', function() {
95  // TODO(dtseng): make generic to click all buttons.
96  var showHomeButton = $('show-home-button');
97  var trueListValue = [
98    'browser.show_home_button',
99    true,
100    'Options_Homepage_HomeButton',
101  ];
102  // Note: this expectation is checked in testing::Test::tearDown.
103  this.mockHandler.expects(once()).setBooleanPref(trueListValue);
104
105  // Cause the handler to be called.
106  showHomeButton.click();
107  showHomeButton.blur();
108  testDone();
109});
110
111// Not meant to run on ChromeOS at this time.
112// Not finishing in windows. http://crbug.com/81723
113TEST_F('OptionsWebUITest', 'DISABLED_testRefreshStaysOnCurrentPage',
114    function() {
115  assertTrue($('search-engine-manager-page').hidden);
116  var item = $('manage-default-search-engines');
117  item.click();
118
119  assertFalse($('search-engine-manager-page').hidden);
120
121  window.location.reload();
122
123  assertEquals('chrome://settings-frame/searchEngines', document.location.href);
124  assertFalse($('search-engine-manager-page').hidden);
125  testDone();
126});
127
128/**
129 * Test the default zoom factor select element.
130 */
131TEST_F('OptionsWebUITest', 'testDefaultZoomFactor', function() {
132  // The expected minimum length of the |defaultZoomFactor| element.
133  var defaultZoomFactorMinimumLength = 10;
134  // Verify that the zoom factor element exists.
135  var defaultZoomFactor = $('defaultZoomFactor');
136  assertNotEquals(defaultZoomFactor, null);
137
138  // Verify that the zoom factor element has a reasonable number of choices.
139  expectGE(defaultZoomFactor.options.length, defaultZoomFactorMinimumLength);
140
141  // Simulate a change event, selecting the highest zoom value.  Verify that
142  // the javascript handler was invoked once.
143  this.mockHandler.expects(once()).defaultZoomFactorAction(NOT_NULL).
144      will(callFunction(function() { }));
145  defaultZoomFactor.selectedIndex = defaultZoomFactor.options.length - 1;
146  var event = { target: defaultZoomFactor };
147  if (defaultZoomFactor.onchange) defaultZoomFactor.onchange(event);
148  testDone();
149});
150
151// If |confirmInterstitial| is true, the OK button of the Do Not Track
152// interstitial is pressed, otherwise the abort button is pressed.
153OptionsWebUITest.prototype.testDoNotTrackInterstitial =
154    function(confirmInterstitial) {
155  Preferences.prefsFetchedCallback({'enable_do_not_track': {'value': false } });
156  var buttonToClick = confirmInterstitial ? $('do-not-track-confirm-ok')
157                                          : $('do-not-track-confirm-cancel');
158  var dntCheckbox = $('do-not-track-enabled');
159  var dntOverlay = OptionsPage.registeredOverlayPages['donottrackconfirm'];
160  assertFalse(dntCheckbox.checked);
161
162  var visibleChangeCounter = 0;
163  var visibleChangeHandler = function() {
164    ++visibleChangeCounter;
165    switch (visibleChangeCounter) {
166      case 1:
167        window.setTimeout(function() {
168          assertTrue(dntOverlay.visible);
169          buttonToClick.click();
170        }, 0);
171        break;
172      case 2:
173        window.setTimeout(function() {
174          assertFalse(dntOverlay.visible);
175          assertEquals(confirmInterstitial, dntCheckbox.checked);
176          dntOverlay.removeEventListener(visibleChangeHandler);
177          testDone();
178        }, 0);
179        break;
180      default:
181        assertTrue(false);
182    }
183  }
184  dntOverlay.addEventListener('visibleChange', visibleChangeHandler);
185
186  if (confirmInterstitial) {
187    this.mockHandler.expects(once()).setBooleanPref(
188        ['enable_do_not_track', true, 'Options_DoNotTrackCheckbox']);
189  } else {
190    // The mock handler complains if setBooleanPref is called even though
191    // it should not be.
192  }
193
194  dntCheckbox.click();
195}
196
197TEST_F('OptionsWebUITest', 'EnableDoNotTrackAndConfirmInterstitial',
198       function() {
199  this.testDoNotTrackInterstitial(true);
200});
201
202TEST_F('OptionsWebUITest', 'EnableDoNotTrackAndCancelInterstitial',
203       function() {
204  this.testDoNotTrackInterstitial(false);
205});
206
207// Check that the "Do not Track" preference can be correctly disabled.
208// In order to do that, we need to enable it first.
209TEST_F('OptionsWebUITest', 'EnableAndDisableDoNotTrack', function() {
210  Preferences.prefsFetchedCallback({'enable_do_not_track': {'value': false } });
211  var dntCheckbox = $('do-not-track-enabled');
212  var dntOverlay = OptionsPage.registeredOverlayPages['donottrackconfirm'];
213  assertFalse(dntCheckbox.checked);
214
215  var visibleChangeCounter = 0;
216  var visibleChangeHandler = function() {
217    ++visibleChangeCounter;
218    switch (visibleChangeCounter) {
219      case 1:
220        window.setTimeout(function() {
221          assertTrue(dntOverlay.visible);
222          $('do-not-track-confirm-ok').click();
223        }, 0);
224        break;
225      case 2:
226        window.setTimeout(function() {
227          assertFalse(dntOverlay.visible);
228          assertTrue(dntCheckbox.checked);
229          dntOverlay.removeEventListener(visibleChangeHandler);
230          dntCheckbox.click();
231        }, 0);
232        break;
233      default:
234        assertNotReached();
235    }
236  }
237  dntOverlay.addEventListener('visibleChange', visibleChangeHandler);
238
239  this.mockHandler.expects(once()).setBooleanPref(
240      eq(["enable_do_not_track", true, 'Options_DoNotTrackCheckbox']));
241
242  var verifyCorrectEndState = function() {
243    window.setTimeout(function() {
244      assertFalse(dntOverlay.visible);
245      assertFalse(dntCheckbox.checked);
246      testDone();
247    }, 0)
248  }
249  this.mockHandler.expects(once()).setBooleanPref(
250      eq(["enable_do_not_track", false, 'Options_DoNotTrackCheckbox'])).will(
251          callFunction(verifyCorrectEndState));
252
253  dntCheckbox.click();
254});
255
256// Verify that preventDefault() is called on 'Enter' keydown events that trigger
257// the default button. If this doesn't happen, other elements that may get
258// focus (by the overlay closing for instance), will execute in addition to the
259// default button. See crbug.com/268336.
260TEST_F('OptionsWebUITest', 'EnterPreventsDefault', function() {
261  var page = HomePageOverlay.getInstance();
262  OptionsPage.showPageByName(page.name);
263  var event = new KeyboardEvent('keydown', {
264    'bubbles': true,
265    'cancelable': true,
266    'keyIdentifier': 'Enter'
267  });
268  assertFalse(event.defaultPrevented);
269  page.pageDiv.dispatchEvent(event);
270  assertTrue(event.defaultPrevented);
271  testDone();
272});
273
274// Verifies that sending an empty list of indexes to move doesn't crash chrome.
275TEST_F('OptionsWebUITest', 'emptySelectedIndexesDoesntCrash', function() {
276  chrome.send('dragDropStartupPage', [0, []]);
277  setTimeout(testDone);
278});
279
280// Flaky on win. See http://crbug.com/315250
281GEN('#if defined(OS_WIN)');
282GEN('#define MAYBE_OverlayShowDoesntShift DISABLED_OverlayShowDoesntShift');
283GEN('#else');
284GEN('#define MAYBE_OverlayShowDoesntShift OverlayShowDoesntShift');
285GEN('#endif  // defined(OS_WIN)');
286
287// An overlay's position should remain the same as it shows.
288TEST_F('OptionsWebUITest', 'MAYBE_OverlayShowDoesntShift', function() {
289  var overlayName = 'startup';
290  var overlay = $('startup-overlay');
291  var frozenPages = document.getElementsByClassName('frozen');  // Gets updated.
292  expectEquals(0, frozenPages.length);
293
294  document.addEventListener('webkitTransitionEnd', function(e) {
295    if (e.target != overlay)
296      return;
297
298    assertFalse(overlay.classList.contains('transparent'));
299    expectEquals(numFrozenPages, frozenPages.length);
300    testDone();
301  });
302
303  OptionsPage.navigateToPage(overlayName);
304  var numFrozenPages = frozenPages.length;
305  expectGT(numFrozenPages, 0);
306});
307
308/**
309 * TestFixture for OptionsPage WebUI testing including tab history and support
310 * for preference manipulation. If you don't need the features in the C++
311 * fixture, use the simpler OptionsWebUITest (above) instead.
312 * @extends {testing.Test}
313 * @constructor
314 */
315function OptionsWebUIExtendedTest() {}
316
317OptionsWebUIExtendedTest.prototype = {
318  __proto__: testing.Test.prototype,
319
320  /** @override */
321  browsePreload: 'chrome://settings-frame',
322
323  /** @override */
324  typedefCppFixture: 'OptionsBrowserTest',
325
326  testGenPreamble: function() {
327    // Start with no supervised users managed by this profile.
328    GEN('  ClearPref("' + MANAGED_USERS_PREF + '");');
329  },
330
331  /** @override */
332  isAsync: true,
333
334  /** @override */
335  setUp: function () {
336      // user-image-stream is a streaming video element used for capturing a
337      // user image during OOBE.
338      this.accessibilityAuditConfig.ignoreSelectors('videoWithoutCaptions',
339                                                    '.user-image-stream');
340  },
341
342  /**
343   * Asserts that two non-nested arrays are equal. The arrays must contain only
344   * plain data types, no nested arrays or other objects.
345   * @param {Array} expected An array of expected values.
346   * @param {Array} result An array of actual values.
347   * @param {boolean} doSort If true, the arrays will be sorted before being
348   *     compared.
349   * @param {string} description A brief description for the array of actual
350   *     values, to use in an error message if the arrays differ.
351   * @private
352   */
353  compareArrays_: function(expected, result, doSort, description) {
354    var errorMessage = '\n' + description + ': ' + result +
355                       '\nExpected: ' + expected;
356    assertEquals(expected.length, result.length, errorMessage);
357
358    var expectedSorted = expected.slice();
359    var resultSorted = result.slice();
360    if (doSort) {
361      expectedSorted.sort();
362      resultSorted.sort();
363    }
364
365    for (var i = 0; i < expectedSorted.length; ++i) {
366      assertEquals(expectedSorted[i], resultSorted[i], errorMessage);
367    }
368  },
369
370  /**
371   * Verifies that the correct pages are currently open/visible.
372   * @param {!Array.<string>} expectedPages An array of page names expected to
373   *     be open, with the topmost listed last.
374   * @param {string=} expectedUrl The URL path, including hash, expected to be
375   *     open. If undefined, the topmost (last) page name in |expectedPages|
376   *     will be used. In either case, 'chrome://settings-frame/' will be
377   *     prepended.
378   * @private
379   */
380  verifyOpenPages_: function(expectedPages, expectedUrl) {
381    // Check the topmost page.
382    assertEquals(null, OptionsPage.getVisibleBubble());
383    var currentPage = OptionsPage.getTopmostVisiblePage();
384
385    var lastExpected = expectedPages[expectedPages.length - 1];
386    assertEquals(lastExpected, currentPage.name);
387    // We'd like to check the title too, but we have to load the settings-frame
388    // instead of the outer settings page in order to have access to
389    // OptionsPage, and setting the title from within the settings-frame fails
390    // because of cross-origin access restrictions.
391    // TODO(pamg): Add a test fixture that loads chrome://settings and uses
392    // UI elements to access sub-pages, so we can test the titles and
393    // search-page URLs.
394    var fullExpectedUrl = 'chrome://settings-frame/' +
395                          (expectedUrl ? expectedUrl : lastExpected);
396    assertEquals(fullExpectedUrl, window.location.href);
397
398    // Collect open pages.
399    var allPageNames = Object.keys(OptionsPage.registeredPages).concat(
400                       Object.keys(OptionsPage.registeredOverlayPages));
401    var openPages = [];
402    for (var i = 0; i < allPageNames.length; ++i) {
403      var name = allPageNames[i];
404      var page = OptionsPage.registeredPages[name] ||
405                 OptionsPage.registeredOverlayPages[name];
406      if (page.visible)
407        openPages.push(page.name);
408    }
409
410    this.compareArrays_(expectedPages, openPages, true, 'Open pages');
411  },
412
413  /*
414   * Verifies that the correct URLs are listed in the history. Asynchronous.
415   * @param {!Array.<string>} expectedHistory An array of URL paths expected to
416   *     be in the tab navigation history, sorted by visit time, including the
417   *     current page as the last entry. The base URL (chrome://settings-frame/)
418   *     will be prepended to each. An initial 'about:blank' history entry is
419   *     assumed and should not be included in this list.
420   * @param {Function=} callback A function to be called after the history has
421   *     been verified successfully. May be undefined.
422   * @private
423   */
424  verifyHistory_: function(expectedHistory, callback) {
425    var self = this;
426    OptionsWebUIExtendedTest.verifyHistoryCallback = function(results) {
427      // The history always starts with a blank page.
428      assertEquals('about:blank', results.shift());
429      var fullExpectedHistory = [];
430      for (var i = 0; i < expectedHistory.length; ++i) {
431        fullExpectedHistory.push(
432            'chrome://settings-frame/' + expectedHistory[i]);
433      }
434      self.compareArrays_(fullExpectedHistory, results, false, 'History');
435      callback();
436    };
437
438    // The C++ fixture will call verifyHistoryCallback with the results.
439    chrome.send('optionsTestReportHistory');
440  },
441
442  /**
443   * Overrides the page callbacks for the given OptionsPage overlay to verify
444   * that they are not called.
445   * @param {Object} overlay The singleton instance of the overlay.
446   * @private
447   */
448  prohibitChangesToOverlay_: function(overlay) {
449    overlay.initializePage =
450        overlay.didShowPage =
451        overlay.didClosePage = function() {
452          assertTrue(false,
453                     'Overlay was affected when changes were prohibited.');
454        };
455  },
456};
457
458/*
459 * Set by verifyHistory_ to incorporate a followup callback, then called by the
460 * C++ fixture with the navigation history to be verified.
461 * @type {Function}
462 */
463OptionsWebUIExtendedTest.verifyHistoryCallback = null;
464
465// Show the search page with no query string, to fall back to the settings page.
466// Test disabled because it's flaky. crbug.com/303841
467TEST_F('OptionsWebUIExtendedTest', 'DISABLED_ShowSearchPageNoQuery',
468       function() {
469  OptionsPage.showPageByName('search');
470  this.verifyOpenPages_(['settings']);
471  this.verifyHistory_(['settings'], testDone);
472});
473
474// Show a page without updating history.
475TEST_F('OptionsWebUIExtendedTest', 'ShowPageNoHistory', function() {
476  this.verifyOpenPages_(['settings']);
477  // There are only two main pages, 'settings' and 'search'. It's not possible
478  // to show the search page using OptionsPage.showPageByName, because it
479  // reverts to the settings page if it has no search text set. So we show the
480  // search page by performing a search, then test showPageByName.
481  $('search-field').onsearch({currentTarget: {value: 'query'}});
482
483  // The settings page is also still "open" (i.e., visible), in order to show
484  // the search results. Furthermore, the URL hasn't been updated in the parent
485  // page, because we've loaded the chrome-settings frame instead of the whole
486  // settings page, so the cross-origin call to set the URL fails.
487  this.verifyOpenPages_(['settings', 'search'], 'settings#query');
488  var self = this;
489  this.verifyHistory_(['settings', 'settings#query'], function() {
490    OptionsPage.showPageByName('settings', false);
491    self.verifyOpenPages_(['settings'], 'settings#query');
492    self.verifyHistory_(['settings', 'settings#query'], testDone);
493  });
494});
495
496TEST_F('OptionsWebUIExtendedTest', 'ShowPageWithHistory', function() {
497  // See comments for ShowPageNoHistory.
498  $('search-field').onsearch({currentTarget: {value: 'query'}});
499  var self = this;
500  this.verifyHistory_(['settings', 'settings#query'], function() {
501    OptionsPage.showPageByName('settings', true);
502    self.verifyOpenPages_(['settings'], 'settings#query');
503    self.verifyHistory_(['settings', 'settings#query', 'settings#query'],
504                        testDone);
505  });
506});
507
508TEST_F('OptionsWebUIExtendedTest', 'ShowPageReplaceHistory', function() {
509  // See comments for ShowPageNoHistory.
510  $('search-field').onsearch({currentTarget: {value: 'query'}});
511  var self = this;
512  this.verifyHistory_(['settings', 'settings#query'], function() {
513    OptionsPage.showPageByName('settings', true, {'replaceState': true});
514    self.verifyOpenPages_(['settings'], 'settings#query');
515    self.verifyHistory_(['settings', 'settings#query'], testDone);
516  });
517});
518
519// This should be identical to ShowPageWithHisory.
520TEST_F('OptionsWebUIExtendedTest', 'NavigateToPage', function() {
521  // See comments for ShowPageNoHistory.
522  $('search-field').onsearch({currentTarget: {value: 'query'}});
523  var self = this;
524  this.verifyHistory_(['settings', 'settings#query'], function() {
525    OptionsPage.navigateToPage('settings');
526    self.verifyOpenPages_(['settings'], 'settings#query');
527    self.verifyHistory_(['settings', 'settings#query', 'settings#query'],
528                        testDone);
529  });
530});
531
532// Settings overlays are much more straightforward than settings pages, opening
533// normally with none of the latter's quirks in the expected history or URL.
534TEST_F('OptionsWebUIExtendedTest', 'ShowOverlayNoHistory', function() {
535  // Open a layer-1 overlay, not updating history.
536  OptionsPage.showPageByName('languages', false);
537  this.verifyOpenPages_(['settings', 'languages'], 'settings');
538
539  var self = this;
540  this.verifyHistory_(['settings'], function() {
541    // Open a layer-2 overlay for which the layer-1 is a parent, not updating
542    // history.
543    OptionsPage.showPageByName('addLanguage', false);
544    self.verifyOpenPages_(['settings', 'languages', 'addLanguage'],
545                          'settings');
546    self.verifyHistory_(['settings'], testDone);
547  });
548});
549
550TEST_F('OptionsWebUIExtendedTest', 'ShowOverlayWithHistory', function() {
551  // Open a layer-1 overlay, updating history.
552  OptionsPage.showPageByName('languages', true);
553  this.verifyOpenPages_(['settings', 'languages']);
554
555  var self = this;
556  this.verifyHistory_(['settings', 'languages'], function() {
557    // Open a layer-2 overlay, updating history.
558    OptionsPage.showPageByName('addLanguage', true);
559    self.verifyOpenPages_(['settings', 'languages', 'addLanguage']);
560    self.verifyHistory_(['settings', 'languages', 'addLanguage'], testDone);
561  });
562});
563
564TEST_F('OptionsWebUIExtendedTest', 'ShowOverlayReplaceHistory', function() {
565  // Open a layer-1 overlay, updating history.
566  OptionsPage.showPageByName('languages', true);
567  var self = this;
568  this.verifyHistory_(['settings', 'languages'], function() {
569    // Open a layer-2 overlay, replacing history.
570    OptionsPage.showPageByName('addLanguage', true, {'replaceState': true});
571    self.verifyOpenPages_(['settings', 'languages', 'addLanguage']);
572    self.verifyHistory_(['settings', 'addLanguage'], testDone);
573  });
574});
575
576// Directly show an overlay further above this page, i.e. one for which the
577// current page is an ancestor but not a parent.
578TEST_F('OptionsWebUIExtendedTest', 'ShowOverlayFurtherAbove', function() {
579  // Open a layer-2 overlay directly.
580  OptionsPage.showPageByName('addLanguage', true);
581  this.verifyOpenPages_(['settings', 'languages', 'addLanguage']);
582  var self = this;
583  this.verifyHistory_(['settings', 'addLanguage'], testDone);
584});
585
586// Directly show a layer-2 overlay for which the layer-1 overlay is not a
587// parent.
588TEST_F('OptionsWebUIExtendedTest', 'ShowUnrelatedOverlay', function() {
589  // Open a layer-1 overlay.
590  OptionsPage.showPageByName('languages', true);
591  this.verifyOpenPages_(['settings', 'languages']);
592
593  var self = this;
594  this.verifyHistory_(['settings', 'languages'], function() {
595    // Open an unrelated layer-2 overlay.
596    OptionsPage.showPageByName('cookies', true);
597    self.verifyOpenPages_(['settings', 'content', 'cookies']);
598    self.verifyHistory_(['settings', 'languages', 'cookies'], testDone);
599  });
600});
601
602// Close an overlay.
603TEST_F('OptionsWebUIExtendedTest', 'CloseOverlay', function() {
604  // Open a layer-1 overlay, then a layer-2 overlay on top of it.
605  OptionsPage.showPageByName('languages', true);
606  this.verifyOpenPages_(['settings', 'languages']);
607  OptionsPage.showPageByName('addLanguage', true);
608  this.verifyOpenPages_(['settings', 'languages', 'addLanguage']);
609
610  var self = this;
611  this.verifyHistory_(['settings', 'languages', 'addLanguage'], function() {
612    // Close the layer-2 overlay.
613    OptionsPage.closeOverlay();
614    self.verifyOpenPages_(['settings', 'languages']);
615    self.verifyHistory_(
616        ['settings', 'languages', 'addLanguage', 'languages'],
617        function() {
618      // Close the layer-1 overlay.
619      OptionsPage.closeOverlay();
620      self.verifyOpenPages_(['settings']);
621      self.verifyHistory_(
622          ['settings', 'languages', 'addLanguage', 'languages', 'settings'],
623          testDone);
624    });
625  });
626});
627
628// Make sure an overlay isn't closed (even temporarily) when another overlay is
629// opened on top.
630TEST_F('OptionsWebUIExtendedTest', 'OverlayAboveNoReset', function() {
631  // Open a layer-1 overlay.
632  OptionsPage.showPageByName('languages', true);
633  this.verifyOpenPages_(['settings', 'languages']);
634
635  // Open a layer-2 overlay on top. This should not close 'languages'.
636  this.prohibitChangesToOverlay_(options.LanguageOptions.getInstance());
637  OptionsPage.showPageByName('addLanguage', true);
638  this.verifyOpenPages_(['settings', 'languages', 'addLanguage']);
639  testDone();
640});
641
642TEST_F('OptionsWebUIExtendedTest', 'OverlayTabNavigation', function() {
643  // Open a layer-1 overlay, then a layer-2 overlay on top of it.
644  OptionsPage.showPageByName('languages', true);
645  OptionsPage.showPageByName('addLanguage', true);
646  var self = this;
647
648  // Go back twice, then forward twice.
649  self.verifyOpenPages_(['settings', 'languages', 'addLanguage']);
650  self.verifyHistory_(['settings', 'languages', 'addLanguage'], function() {
651    window.history.back();
652    waitForPopstate(function() {
653      self.verifyOpenPages_(['settings', 'languages']);
654      self.verifyHistory_(['settings', 'languages'], function() {
655        window.history.back();
656        waitForPopstate(function() {
657          self.verifyOpenPages_(['settings']);
658          self.verifyHistory_(['settings'], function() {
659            window.history.forward();
660            waitForPopstate(function() {
661              self.verifyOpenPages_(['settings', 'languages']);
662              self.verifyHistory_(['settings', 'languages'], function() {
663                window.history.forward();
664                waitForPopstate(function() {
665                  self.verifyOpenPages_(
666                      ['settings', 'languages', 'addLanguage']);
667                  self.verifyHistory_(
668                      ['settings', 'languages', 'addLanguage'], testDone);
669                });
670              });
671            });
672          });
673        });
674      });
675    });
676  });
677});
678
679// Going "back" to an overlay that's a child of the current overlay shouldn't
680// close the current one.
681TEST_F('OptionsWebUIExtendedTest', 'OverlayBackToChild', function() {
682  // Open a layer-1 overlay, then a layer-2 overlay on top of it.
683  OptionsPage.showPageByName('languages', true);
684  OptionsPage.showPageByName('addLanguage', true);
685  var self = this;
686
687  self.verifyOpenPages_(['settings', 'languages', 'addLanguage']);
688  self.verifyHistory_(['settings', 'languages', 'addLanguage'], function() {
689    // Close the top overlay, then go back to it.
690    OptionsPage.closeOverlay();
691    self.verifyOpenPages_(['settings', 'languages']);
692    self.verifyHistory_(
693        ['settings', 'languages', 'addLanguage', 'languages'],
694        function() {
695      // Going back to the 'addLanguage' page should not close 'languages'.
696      self.prohibitChangesToOverlay_(options.LanguageOptions.getInstance());
697      window.history.back();
698      waitForPopstate(function() {
699        self.verifyOpenPages_(['settings', 'languages', 'addLanguage']);
700        self.verifyHistory_(['settings', 'languages', 'addLanguage'],
701                            testDone);
702      });
703    });
704  });
705});
706
707// Going back to an unrelated overlay should close the overlay and its parent.
708TEST_F('OptionsWebUIExtendedTest', 'OverlayBackToUnrelated', function() {
709  // Open a layer-1 overlay, then an unrelated layer-2 overlay.
710  OptionsPage.showPageByName('languages', true);
711  OptionsPage.showPageByName('cookies', true);
712  var self = this;
713  self.verifyOpenPages_(['settings', 'content', 'cookies']);
714  self.verifyHistory_(['settings', 'languages', 'cookies'], function() {
715    window.history.back();
716    waitForPopstate(function() {
717      self.verifyOpenPages_(['settings', 'languages']);
718      testDone();
719    });
720  });
721});
722
723// Verify history changes properly while the page is loading.
724TEST_F('OptionsWebUIExtendedTest', 'HistoryUpdatedAfterLoading', function() {
725  var loc = location.href;
726
727  document.documentElement.classList.add('loading');
728  assertTrue(OptionsPage.isLoading());
729  OptionsPage.navigateToPage('searchEngines');
730  expectNotEquals(loc, location.href);
731
732  document.documentElement.classList.remove('loading');
733  assertFalse(OptionsPage.isLoading());
734  OptionsPage.showDefaultPage();
735  expectEquals(loc, location.href);
736
737  testDone();
738});
739
740// A tip should be shown or hidden depending on whether this profile manages any
741// supervised users.
742TEST_F('OptionsWebUIExtendedTest', 'SupervisingUsers', function() {
743  // We start managing no supervised users.
744  assertTrue($('profiles-supervised-dashboard-tip').hidden);
745
746  // Remove all supervised users, then add some, watching for the pref change
747  // notifications and UI updates in each case. Any non-empty pref dictionary
748  // is interpreted as having supervised users.
749  chrome.send('optionsTestSetPref', [MANAGED_USERS_PREF, {key: 'value'}]);
750  waitForResponse(BrowserOptions, 'updateManagesSupervisedUsers', function() {
751    assertFalse($('profiles-supervised-dashboard-tip').hidden);
752    chrome.send('optionsTestSetPref', [MANAGED_USERS_PREF, {}]);
753    waitForResponse(BrowserOptions, 'updateManagesSupervisedUsers', function() {
754      assertTrue($('profiles-supervised-dashboard-tip').hidden);
755      testDone();
756    });
757  });
758});
759