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