// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

GEN('#include "chrome/browser/ui/webui/options/options_browsertest.h"');

/** @const */ var MANAGED_USERS_PREF = 'profile.managed_users';

/**
 * Wait for the method specified by |methodName|, on the |object| object, to be
 * called, then execute |afterFunction|.
 * @param {*} object Object with callable property named |methodName|.
 * @param {string} methodName The name of the property on |object| to use as a
 *     callback.
 * @param {!Function} afterFunction A function to call after object.methodName()
 *     is called.
 */
function waitForResponse(object, methodName, afterFunction) {
  var originalCallback = object[methodName];

  // Install a wrapper that temporarily replaces the original function.
  object[methodName] = function() {
    object[methodName] = originalCallback;
    originalCallback.apply(this, arguments);
    afterFunction();
  };
}

/**
  * Wait for the global window.onpopstate callback to be called (after a tab
  * history navigation), then execute |afterFunction|.
  * @param {!Function} afterFunction A function to call after pop state events.
  */
function waitForPopstate(afterFunction) {
  waitForResponse(window, 'onpopstate', afterFunction);
}

/**
 * TestFixture for OptionsPage WebUI testing.
 * @extends {testing.Test}
 * @constructor
 */
function OptionsWebUITest() {}

OptionsWebUITest.prototype = {
  __proto__: testing.Test.prototype,

  /** @override */
  accessibilityIssuesAreErrors: true,

  /** @override */
  setUp: function() {
    // user-image-stream is a streaming video element used for capturing a
    // user image during OOBE.
    this.accessibilityAuditConfig.ignoreSelectors('videoWithoutCaptions',
                                                  '.user-image-stream');
  },

  /**
   * Browse to the options page & call our preLoad().
   */
  browsePreload: 'chrome://settings-frame',

  isAsync: true,

  /**
   * Register a mock handler to ensure expectations are met and options pages
   * behave correctly.
   */
  preLoad: function() {
    this.makeAndRegisterMockHandler(
        ['defaultZoomFactorAction',
         'fetchPrefs',
         'observePrefs',
         'setBooleanPref',
         'setIntegerPref',
         'setDoublePref',
         'setStringPref',
         'setObjectPref',
         'clearPref',
         'coreOptionsUserMetricsAction',
        ]);

    // Register stubs for methods expected to be called before/during tests.
    // Specific expectations can be made in the tests themselves.
    this.mockHandler.stubs().fetchPrefs(ANYTHING);
    this.mockHandler.stubs().observePrefs(ANYTHING);
    this.mockHandler.stubs().coreOptionsUserMetricsAction(ANYTHING);
  },
};

// Crashes on Mac only. See http://crbug.com/79181
GEN('#if defined(OS_MACOSX)');
GEN('#define MAYBE_testSetBooleanPrefTriggers ' +
    'DISABLED_testSetBooleanPrefTriggers');
GEN('#else');
GEN('#define MAYBE_testSetBooleanPrefTriggers testSetBooleanPrefTriggers');
GEN('#endif  // defined(OS_MACOSX)');

TEST_F('OptionsWebUITest', 'MAYBE_testSetBooleanPrefTriggers', function() {
  // TODO(dtseng): make generic to click all buttons.
  var showHomeButton = $('show-home-button');
  var trueListValue = [
    'browser.show_home_button',
    true,
    'Options_Homepage_HomeButton',
  ];
  // Note: this expectation is checked in testing::Test::tearDown.
  this.mockHandler.expects(once()).setBooleanPref(trueListValue);

  // Cause the handler to be called.
  showHomeButton.click();
  showHomeButton.blur();
  testDone();
});

// Not meant to run on ChromeOS at this time.
// Not finishing in windows. http://crbug.com/81723
TEST_F('OptionsWebUITest', 'DISABLED_testRefreshStaysOnCurrentPage',
    function() {
  assertTrue($('search-engine-manager-page').hidden);
  var item = $('manage-default-search-engines');
  item.click();

  assertFalse($('search-engine-manager-page').hidden);

  window.location.reload();

  assertEquals('chrome://settings-frame/searchEngines', document.location.href);
  assertFalse($('search-engine-manager-page').hidden);
  testDone();
});

/**
 * Test the default zoom factor select element.
 */
TEST_F('OptionsWebUITest', 'testDefaultZoomFactor', function() {
  // The expected minimum length of the |defaultZoomFactor| element.
  var defaultZoomFactorMinimumLength = 10;
  // Verify that the zoom factor element exists.
  var defaultZoomFactor = $('defaultZoomFactor');
  assertNotEquals(defaultZoomFactor, null);

  // Verify that the zoom factor element has a reasonable number of choices.
  expectGE(defaultZoomFactor.options.length, defaultZoomFactorMinimumLength);

  // Simulate a change event, selecting the highest zoom value.  Verify that
  // the javascript handler was invoked once.
  this.mockHandler.expects(once()).defaultZoomFactorAction(NOT_NULL).
      will(callFunction(function() { }));
  defaultZoomFactor.selectedIndex = defaultZoomFactor.options.length - 1;
  var event = {target: defaultZoomFactor};
  if (defaultZoomFactor.onchange) defaultZoomFactor.onchange(event);
  testDone();
});

/**
 * If |confirmInterstitial| is true, the OK button of the Do Not Track
 * interstitial is pressed, otherwise the abort button is pressed.
 * @param {boolean} confirmInterstitial Whether to confirm the Do Not Track
 *     interstitial.
 */
OptionsWebUITest.prototype.testDoNotTrackInterstitial =
    function(confirmInterstitial) {
  Preferences.prefsFetchedCallback({'enable_do_not_track': {'value': false}});
  var buttonToClick = confirmInterstitial ? $('do-not-track-confirm-ok') :
                                            $('do-not-track-confirm-cancel');
  var dntCheckbox = $('do-not-track-enabled');
  var dntOverlay = OptionsPage.registeredOverlayPages['donottrackconfirm'];
  assertFalse(dntCheckbox.checked);

  var visibleChangeCounter = 0;
  var visibleChangeHandler = function() {
    ++visibleChangeCounter;
    switch (visibleChangeCounter) {
      case 1:
        window.setTimeout(function() {
          assertTrue(dntOverlay.visible);
          buttonToClick.click();
        }, 0);
        break;
      case 2:
        window.setTimeout(function() {
          assertFalse(dntOverlay.visible);
          assertEquals(confirmInterstitial, dntCheckbox.checked);
          dntOverlay.removeEventListener(visibleChangeHandler);
          testDone();
        }, 0);
        break;
      default:
        assertTrue(false);
    }
  };
  dntOverlay.addEventListener('visibleChange', visibleChangeHandler);

  if (confirmInterstitial) {
    this.mockHandler.expects(once()).setBooleanPref(
        ['enable_do_not_track', true, 'Options_DoNotTrackCheckbox']);
  } else {
    // The mock handler complains if setBooleanPref is called even though
    // it should not be.
  }

  dntCheckbox.click();
};

TEST_F('OptionsWebUITest', 'EnableDoNotTrackAndConfirmInterstitial',
       function() {
  this.testDoNotTrackInterstitial(true);
});

TEST_F('OptionsWebUITest', 'EnableDoNotTrackAndCancelInterstitial',
       function() {
  this.testDoNotTrackInterstitial(false);
});

// Check that the "Do not Track" preference can be correctly disabled.
// In order to do that, we need to enable it first.
TEST_F('OptionsWebUITest', 'EnableAndDisableDoNotTrack', function() {
  Preferences.prefsFetchedCallback({'enable_do_not_track': {'value': false}});
  var dntCheckbox = $('do-not-track-enabled');
  var dntOverlay = OptionsPage.registeredOverlayPages.donottrackconfirm;
  assertFalse(dntCheckbox.checked);

  var visibleChangeCounter = 0;
  var visibleChangeHandler = function() {
    ++visibleChangeCounter;
    switch (visibleChangeCounter) {
      case 1:
        window.setTimeout(function() {
          assertTrue(dntOverlay.visible);
          $('do-not-track-confirm-ok').click();
        }, 0);
        break;
      case 2:
        window.setTimeout(function() {
          assertFalse(dntOverlay.visible);
          assertTrue(dntCheckbox.checked);
          dntOverlay.removeEventListener(visibleChangeHandler);
          dntCheckbox.click();
        }, 0);
        break;
      default:
        assertNotReached();
    }
  }
  dntOverlay.addEventListener('visibleChange', visibleChangeHandler);

  this.mockHandler.expects(once()).setBooleanPref(
      eq(['enable_do_not_track', true, 'Options_DoNotTrackCheckbox']));

  var verifyCorrectEndState = function() {
    window.setTimeout(function() {
      assertFalse(dntOverlay.visible);
      assertFalse(dntCheckbox.checked);
      testDone();
    }, 0);
  }
  this.mockHandler.expects(once()).setBooleanPref(
      eq(['enable_do_not_track', false, 'Options_DoNotTrackCheckbox'])).will(
          callFunction(verifyCorrectEndState));

  dntCheckbox.click();
});

// Verify that preventDefault() is called on 'Enter' keydown events that trigger
// the default button. If this doesn't happen, other elements that may get
// focus (by the overlay closing for instance), will execute in addition to the
// default button. See crbug.com/268336.
TEST_F('OptionsWebUITest', 'EnterPreventsDefault', function() {
  var page = HomePageOverlay.getInstance();
  OptionsPage.showPageByName(page.name);
  var event = new KeyboardEvent('keydown', {
    'bubbles': true,
    'cancelable': true,
    'keyIdentifier': 'Enter'
  });
  assertFalse(event.defaultPrevented);
  page.pageDiv.dispatchEvent(event);
  assertTrue(event.defaultPrevented);
  testDone();
});

// Verifies that sending an empty list of indexes to move doesn't crash chrome.
TEST_F('OptionsWebUITest', 'emptySelectedIndexesDoesntCrash', function() {
  chrome.send('dragDropStartupPage', [0, []]);
  setTimeout(testDone);
});

// This test turns out to be flaky on all platforms.
// See http://crbug.com/315250.

// An overlay's position should remain the same as it shows.
TEST_F('OptionsWebUITest', 'DISABLED_OverlayShowDoesntShift', function() {
  var overlayName = 'startup';
  var overlay = $('startup-overlay');
  var frozenPages = document.getElementsByClassName('frozen');  // Gets updated.
  expectEquals(0, frozenPages.length);

  document.addEventListener('webkitTransitionEnd', function(e) {
    if (e.target != overlay)
      return;

    assertFalse(overlay.classList.contains('transparent'));
    expectEquals(numFrozenPages, frozenPages.length);
    testDone();
  });

  OptionsPage.navigateToPage(overlayName);
  var numFrozenPages = frozenPages.length;
  expectGT(numFrozenPages, 0);
});

GEN('#if defined(OS_CHROMEOS)');
// Verify that range inputs respond to touch events. Currently only Chrome OS
// uses slider options.
TEST_F('OptionsWebUITest', 'RangeInputHandlesTouchEvents', function() {
  this.mockHandler.expects(once()).setIntegerPref([
    'settings.touchpad.sensitivity2', 1]);

  var touchpadRange = $('touchpad-sensitivity-range');
  var event = document.createEvent('UIEvent');
  event.initUIEvent('touchstart', true, true, window);
  touchpadRange.dispatchEvent(event);

  event = document.createEvent('UIEvent');
  event.initUIEvent('touchmove', true, true, window);
  touchpadRange.dispatchEvent(event);

  touchpadRange.value = 1;

  event = document.createEvent('UIEvent');
  event.initUIEvent('touchend', true, true, window);
  touchpadRange.dispatchEvent(event);

  // touchcancel should also trigger the handler, since it
  // changes the slider position.
  this.mockHandler.expects(once()).setIntegerPref([
    'settings.touchpad.sensitivity2', 2]);

  event = document.createEvent('UIEvent');
  event.initUIEvent('touchstart', true, true, window);
  touchpadRange.dispatchEvent(event);

  touchpadRange.value = 2;

  event = document.createEvent('UIEvent');
  event.initUIEvent('touchcancel', true, true, window);
  touchpadRange.dispatchEvent(event);

  testDone();
});
GEN('#endif');  // defined(OS_CHROMEOS)

/**
 * TestFixture for OptionsPage WebUI testing including tab history and support
 * for preference manipulation. If you don't need the features in the C++
 * fixture, use the simpler OptionsWebUITest (above) instead.
 * @extends {testing.Test}
 * @constructor
 */
function OptionsWebUIExtendedTest() {}

OptionsWebUIExtendedTest.prototype = {
  __proto__: testing.Test.prototype,

  /** @override */
  browsePreload: 'chrome://settings-frame',

  /** @override */
  typedefCppFixture: 'OptionsBrowserTest',

  testGenPreamble: function() {
    // Start with no supervised users managed by this profile.
    GEN('  ClearPref("' + MANAGED_USERS_PREF + '");');
  },

  /** @override */
  isAsync: true,

  /** @override */
  setUp: function() {
      // user-image-stream is a streaming video element used for capturing a
      // user image during OOBE.
      this.accessibilityAuditConfig.ignoreSelectors('videoWithoutCaptions',
                                                    '.user-image-stream');
  },

  /**
   * Asserts that two non-nested arrays are equal. The arrays must contain only
   * plain data types, no nested arrays or other objects.
   * @param {Array} expected An array of expected values.
   * @param {Array} result An array of actual values.
   * @param {boolean} doSort If true, the arrays will be sorted before being
   *     compared.
   * @param {string} description A brief description for the array of actual
   *     values, to use in an error message if the arrays differ.
   * @private
   */
  compareArrays_: function(expected, result, doSort, description) {
    var errorMessage = '\n' + description + ': ' + result +
                       '\nExpected: ' + expected;
    assertEquals(expected.length, result.length, errorMessage);

    var expectedSorted = expected.slice();
    var resultSorted = result.slice();
    if (doSort) {
      expectedSorted.sort();
      resultSorted.sort();
    }

    for (var i = 0; i < expectedSorted.length; ++i) {
      assertEquals(expectedSorted[i], resultSorted[i], errorMessage);
    }
  },

  /**
   * Verifies that the correct pages are currently open/visible.
   * @param {!Array.<string>} expectedPages An array of page names expected to
   *     be open, with the topmost listed last.
   * @param {string=} opt_expectedUrl The URL path, including hash, expected to
   *     be open. If undefined, the topmost (last) page name in |expectedPages|
   *     will be used. In either case, 'chrome://settings-frame/' will be
   *     prepended.
   * @private
   */
  verifyOpenPages_: function(expectedPages, opt_expectedUrl) {
    // Check the topmost page.
    expectEquals(null, OptionsPage.getVisibleBubble());
    var currentPage = OptionsPage.getTopmostVisiblePage();

    var lastExpected = expectedPages[expectedPages.length - 1];
    expectEquals(lastExpected, currentPage.name);
    // We'd like to check the title too, but we have to load the settings-frame
    // instead of the outer settings page in order to have access to
    // OptionsPage, and setting the title from within the settings-frame fails
    // because of cross-origin access restrictions.
    // TODO(pamg): Add a test fixture that loads chrome://settings and uses
    // UI elements to access sub-pages, so we can test the titles and
    // search-page URLs.
    var expectedUrl = (typeof opt_expectedUrl == 'undefined') ?
        lastExpected : opt_expectedUrl;
    var fullExpectedUrl = 'chrome://settings-frame/' + expectedUrl;
    expectEquals(fullExpectedUrl, window.location.href);

    // Collect open pages.
    var allPageNames = Object.keys(OptionsPage.registeredPages).concat(
                       Object.keys(OptionsPage.registeredOverlayPages));
    var openPages = [];
    for (var i = 0; i < allPageNames.length; ++i) {
      var name = allPageNames[i];
      var page = OptionsPage.registeredPages[name] ||
                 OptionsPage.registeredOverlayPages[name];
      if (page.visible)
        openPages.push(page.name);
    }

    this.compareArrays_(expectedPages, openPages, true, 'Open pages');
  },

  /*
   * Verifies that the correct URLs are listed in the history. Asynchronous.
   * @param {!Array.<string>} expectedHistory An array of URL paths expected to
   *     be in the tab navigation history, sorted by visit time, including the
   *     current page as the last entry. The base URL (chrome://settings-frame/)
   *     will be prepended to each. An initial 'about:blank' history entry is
   *     assumed and should not be included in this list.
   * @param {Function=} callback A function to be called after the history has
   *     been verified successfully. May be undefined.
   * @private
   */
  verifyHistory_: function(expectedHistory, callback) {
    var self = this;
    OptionsWebUIExtendedTest.verifyHistoryCallback = function(results) {
      // The history always starts with a blank page.
      assertEquals('about:blank', results.shift());
      var fullExpectedHistory = [];
      for (var i = 0; i < expectedHistory.length; ++i) {
        fullExpectedHistory.push(
            'chrome://settings-frame/' + expectedHistory[i]);
      }
      self.compareArrays_(fullExpectedHistory, results, false, 'History');
      callback();
    };

    // The C++ fixture will call verifyHistoryCallback with the results.
    chrome.send('optionsTestReportHistory');
  },

  /**
   * Overrides the page callbacks for the given OptionsPage overlay to verify
   * that they are not called.
   * @param {Object} overlay The singleton instance of the overlay.
   * @private
   */
  prohibitChangesToOverlay_: function(overlay) {
    overlay.initializePage =
        overlay.didShowPage =
        overlay.didClosePage = function() {
          assertTrue(false,
                     'Overlay was affected when changes were prohibited.');
        };
  },
};

/**
 * Set by verifyHistory_ to incorporate a followup callback, then called by the
 * C++ fixture with the navigation history to be verified.
 * @type {Function}
 */
OptionsWebUIExtendedTest.verifyHistoryCallback = null;

// Show the search page with no query string, to fall back to the settings page.
// Test disabled because it's flaky. crbug.com/303841
TEST_F('OptionsWebUIExtendedTest', 'DISABLED_ShowSearchPageNoQuery',
       function() {
  OptionsPage.showPageByName('search');
  this.verifyOpenPages_(['settings']);
  this.verifyHistory_(['settings'], testDone);
});

// Show a page without updating history.
TEST_F('OptionsWebUIExtendedTest', 'ShowPageNoHistory', function() {
  this.verifyOpenPages_(['settings'], '');
  // There are only two main pages, 'settings' and 'search'. It's not possible
  // to show the search page using OptionsPage.showPageByName, because it
  // reverts to the settings page if it has no search text set. So we show the
  // search page by performing a search, then test showPageByName.
  $('search-field').onsearch({currentTarget: {value: 'query'}});

  // The settings page is also still "open" (i.e., visible), in order to show
  // the search results. Furthermore, the URL hasn't been updated in the parent
  // page, because we've loaded the chrome-settings frame instead of the whole
  // settings page, so the cross-origin call to set the URL fails.
  this.verifyOpenPages_(['settings', 'search'], 'search#query');
  var self = this;
  this.verifyHistory_(['', 'search#query'], function() {
    OptionsPage.showPageByName('settings', false);
    self.verifyOpenPages_(['settings'], 'search#query');
    self.verifyHistory_(['', 'search#query'], testDone);
  });
});

TEST_F('OptionsWebUIExtendedTest', 'ShowPageWithHistory', function() {
  // See comments for ShowPageNoHistory.
  $('search-field').onsearch({currentTarget: {value: 'query'}});
  var self = this;
  this.verifyHistory_(['', 'search#query'], function() {
    OptionsPage.showPageByName('settings', true);
    self.verifyOpenPages_(['settings'], '#query');
    self.verifyHistory_(['', 'search#query', '#query'],
                        testDone);
  });
});

TEST_F('OptionsWebUIExtendedTest', 'ShowPageReplaceHistory', function() {
  // See comments for ShowPageNoHistory.
  $('search-field').onsearch({currentTarget: {value: 'query'}});
  var self = this;
  this.verifyHistory_(['', 'search#query'], function() {
    OptionsPage.showPageByName('settings', true, {'replaceState': true});
    self.verifyOpenPages_(['settings'], '#query');
    self.verifyHistory_(['', '#query'], testDone);
  });
});

// This should be identical to ShowPageWithHisory.
TEST_F('OptionsWebUIExtendedTest', 'NavigateToPage', function() {
  // See comments for ShowPageNoHistory.
  $('search-field').onsearch({currentTarget: {value: 'query'}});
  var self = this;
  this.verifyHistory_(['', 'search#query'], function() {
    OptionsPage.navigateToPage('settings');
    self.verifyOpenPages_(['settings'], '#query');
    self.verifyHistory_(['', 'search#query', '#query'],
                        testDone);
  });
});

// Settings overlays are much more straightforward than settings pages, opening
// normally with none of the latter's quirks in the expected history or URL.
TEST_F('OptionsWebUIExtendedTest', 'ShowOverlayNoHistory', function() {
  // Open a layer-1 overlay, not updating history.
  OptionsPage.showPageByName('languages', false);
  this.verifyOpenPages_(['settings', 'languages'], '');

  var self = this;
  this.verifyHistory_([''], function() {
    // Open a layer-2 overlay for which the layer-1 is a parent, not updating
    // history.
    OptionsPage.showPageByName('addLanguage', false);
    self.verifyOpenPages_(['settings', 'languages', 'addLanguage'],
                          '');
    self.verifyHistory_([''], testDone);
  });
});

TEST_F('OptionsWebUIExtendedTest', 'ShowOverlayWithHistory', function() {
  // Open a layer-1 overlay, updating history.
  OptionsPage.showPageByName('languages', true);
  this.verifyOpenPages_(['settings', 'languages']);

  var self = this;
  this.verifyHistory_(['', 'languages'], function() {
    // Open a layer-2 overlay, updating history.
    OptionsPage.showPageByName('addLanguage', true);
    self.verifyOpenPages_(['settings', 'languages', 'addLanguage']);
    self.verifyHistory_(['', 'languages', 'addLanguage'], testDone);
  });
});

TEST_F('OptionsWebUIExtendedTest', 'ShowOverlayReplaceHistory', function() {
  // Open a layer-1 overlay, updating history.
  OptionsPage.showPageByName('languages', true);
  var self = this;
  this.verifyHistory_(['', 'languages'], function() {
    // Open a layer-2 overlay, replacing history.
    OptionsPage.showPageByName('addLanguage', true, {'replaceState': true});
    self.verifyOpenPages_(['settings', 'languages', 'addLanguage']);
    self.verifyHistory_(['', 'addLanguage'], testDone);
  });
});

// Directly show an overlay further above this page, i.e. one for which the
// current page is an ancestor but not a parent.
TEST_F('OptionsWebUIExtendedTest', 'ShowOverlayFurtherAbove', function() {
  // Open a layer-2 overlay directly.
  OptionsPage.showPageByName('addLanguage', true);
  this.verifyOpenPages_(['settings', 'languages', 'addLanguage']);
  var self = this;
  this.verifyHistory_(['', 'addLanguage'], testDone);
});

// Directly show a layer-2 overlay for which the layer-1 overlay is not a
// parent.
TEST_F('OptionsWebUIExtendedTest', 'ShowUnrelatedOverlay', function() {
  // Open a layer-1 overlay.
  OptionsPage.showPageByName('languages', true);
  this.verifyOpenPages_(['settings', 'languages']);

  var self = this;
  this.verifyHistory_(['', 'languages'], function() {
    // Open an unrelated layer-2 overlay.
    OptionsPage.showPageByName('cookies', true);
    self.verifyOpenPages_(['settings', 'content', 'cookies']);
    self.verifyHistory_(['', 'languages', 'cookies'], testDone);
  });
});

// Close an overlay.
TEST_F('OptionsWebUIExtendedTest', 'CloseOverlay', function() {
  // Open a layer-1 overlay, then a layer-2 overlay on top of it.
  OptionsPage.showPageByName('languages', true);
  this.verifyOpenPages_(['settings', 'languages']);
  OptionsPage.showPageByName('addLanguage', true);
  this.verifyOpenPages_(['settings', 'languages', 'addLanguage']);

  var self = this;
  this.verifyHistory_(['', 'languages', 'addLanguage'], function() {
    // Close the layer-2 overlay.
    OptionsPage.closeOverlay();
    self.verifyOpenPages_(['settings', 'languages']);
    self.verifyHistory_(
        ['', 'languages', 'addLanguage', 'languages'],
        function() {
      // Close the layer-1 overlay.
      OptionsPage.closeOverlay();
      self.verifyOpenPages_(['settings'], '');
      self.verifyHistory_(
          ['', 'languages', 'addLanguage', 'languages', ''],
          testDone);
    });
  });
});

// Test that closing an overlay that did not push history when opening does not
// again push history.
TEST_F('OptionsWebUIExtendedTest', 'CloseOverlayNoHistory', function() {
  // Open the do not track confirmation prompt.
  OptionsPage.showPageByName('doNotTrackConfirm', false);

  // Opening the prompt does not add to the history.
  this.verifyHistory_([''], function() {
    // Close the overlay.
    OptionsPage.closeOverlay();
    // Still no history changes.
    this.verifyHistory_([''], testDone);
  }.bind(this));
});

// Make sure an overlay isn't closed (even temporarily) when another overlay is
// opened on top.
TEST_F('OptionsWebUIExtendedTest', 'OverlayAboveNoReset', function() {
  // Open a layer-1 overlay.
  OptionsPage.showPageByName('languages', true);
  this.verifyOpenPages_(['settings', 'languages']);

  // Open a layer-2 overlay on top. This should not close 'languages'.
  this.prohibitChangesToOverlay_(options.LanguageOptions.getInstance());
  OptionsPage.showPageByName('addLanguage', true);
  this.verifyOpenPages_(['settings', 'languages', 'addLanguage']);
  testDone();
});

TEST_F('OptionsWebUIExtendedTest', 'OverlayTabNavigation', function() {
  // Open a layer-1 overlay, then a layer-2 overlay on top of it.
  OptionsPage.showPageByName('languages', true);
  OptionsPage.showPageByName('addLanguage', true);
  var self = this;

  // Go back twice, then forward twice.
  self.verifyOpenPages_(['settings', 'languages', 'addLanguage']);
  self.verifyHistory_(['', 'languages', 'addLanguage'], function() {
    window.history.back();
    waitForPopstate(function() {
      self.verifyOpenPages_(['settings', 'languages']);
      self.verifyHistory_(['', 'languages'], function() {
        window.history.back();
        waitForPopstate(function() {
          self.verifyOpenPages_(['settings'], '');
          self.verifyHistory_([''], function() {
            window.history.forward();
            waitForPopstate(function() {
              self.verifyOpenPages_(['settings', 'languages']);
              self.verifyHistory_(['', 'languages'], function() {
                window.history.forward();
                waitForPopstate(function() {
                  self.verifyOpenPages_(
                      ['settings', 'languages', 'addLanguage']);
                  self.verifyHistory_(
                      ['', 'languages', 'addLanguage'], testDone);
                });
              });
            });
          });
        });
      });
    });
  });
});

// Going "back" to an overlay that's a child of the current overlay shouldn't
// close the current one.
TEST_F('OptionsWebUIExtendedTest', 'OverlayBackToChild', function() {
  // Open a layer-1 overlay, then a layer-2 overlay on top of it.
  OptionsPage.showPageByName('languages', true);
  OptionsPage.showPageByName('addLanguage', true);
  var self = this;

  self.verifyOpenPages_(['settings', 'languages', 'addLanguage']);
  self.verifyHistory_(['', 'languages', 'addLanguage'], function() {
    // Close the top overlay, then go back to it.
    OptionsPage.closeOverlay();
    self.verifyOpenPages_(['settings', 'languages']);
    self.verifyHistory_(
        ['', 'languages', 'addLanguage', 'languages'],
        function() {
      // Going back to the 'addLanguage' page should not close 'languages'.
      self.prohibitChangesToOverlay_(options.LanguageOptions.getInstance());
      window.history.back();
      waitForPopstate(function() {
        self.verifyOpenPages_(['settings', 'languages', 'addLanguage']);
        self.verifyHistory_(['', 'languages', 'addLanguage'],
                            testDone);
      });
    });
  });
});

// Going back to an unrelated overlay should close the overlay and its parent.
TEST_F('OptionsWebUIExtendedTest', 'OverlayBackToUnrelated', function() {
  // Open a layer-1 overlay, then an unrelated layer-2 overlay.
  OptionsPage.showPageByName('languages', true);
  OptionsPage.showPageByName('cookies', true);
  var self = this;
  self.verifyOpenPages_(['settings', 'content', 'cookies']);
  self.verifyHistory_(['', 'languages', 'cookies'], function() {
    window.history.back();
    waitForPopstate(function() {
      self.verifyOpenPages_(['settings', 'languages']);
      testDone();
    });
  });
});

// Verify history changes properly while the page is loading.
TEST_F('OptionsWebUIExtendedTest', 'HistoryUpdatedAfterLoading', function() {
  var loc = location.href;

  document.documentElement.classList.add('loading');
  assertTrue(OptionsPage.isLoading());
  OptionsPage.navigateToPage('searchEngines');
  expectNotEquals(loc, location.href);

  document.documentElement.classList.remove('loading');
  assertFalse(OptionsPage.isLoading());
  OptionsPage.showDefaultPage();
  expectEquals(loc, location.href);

  testDone();
});

// A tip should be shown or hidden depending on whether this profile manages any
// supervised users.
TEST_F('OptionsWebUIExtendedTest', 'SupervisingUsers', function() {
  // We start managing no supervised users.
  assertTrue($('profiles-supervised-dashboard-tip').hidden);

  // Remove all supervised users, then add some, watching for the pref change
  // notifications and UI updates in each case. Any non-empty pref dictionary
  // is interpreted as having supervised users.
  chrome.send('optionsTestSetPref', [MANAGED_USERS_PREF, {key: 'value'}]);
  waitForResponse(BrowserOptions, 'updateManagesSupervisedUsers', function() {
    assertFalse($('profiles-supervised-dashboard-tip').hidden);
    chrome.send('optionsTestSetPref', [MANAGED_USERS_PREF, {}]);
    waitForResponse(BrowserOptions, 'updateManagesSupervisedUsers', function() {
      assertTrue($('profiles-supervised-dashboard-tip').hidden);
      testDone();
    });
  });
});

/**
 * TestFixture that loads the options page at a bogus URL.
 * @extends {OptionsWebUIExtendedTest}
 * @constructor
 */
function OptionsWebUIRedirectTest() {
  OptionsWebUIExtendedTest.call(this);
}

OptionsWebUIRedirectTest.prototype = {
  __proto__: OptionsWebUIExtendedTest.prototype,

  /** @override */
  browsePreload: 'chrome://settings-frame/nonexistantPage',
};

TEST_F('OptionsWebUIRedirectTest', 'TestURL', function() {
  assertEquals('chrome://settings-frame/', document.location.href);
  this.verifyHistory_([''], testDone);
});