Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / webui / options / options_browsertest.js
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
5 GEN('#include "chrome/browser/ui/webui/options/options_browsertest.h"');
6
7 /** @const */ var SUPERVISED_USERS_PREF = 'profile.managed_users';
8
9 /**
10  * Wait for the method specified by |methodName|, on the |object| object, to be
11  * called, then execute |afterFunction|.
12  * @param {*} object Object with callable property named |methodName|.
13  * @param {string} methodName The name of the property on |object| to use as a
14  *     callback.
15  * @param {!Function} afterFunction A function to call after object.methodName()
16  *     is called.
17  */
18 function 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   */
34 function waitForPopstate(afterFunction) {
35   waitForResponse(window, 'onpopstate', afterFunction);
36 }
37
38 /**
39  * TestFixture for OptionsPage WebUI testing.
40  * @extends {testing.Test}
41  * @constructor
42  */
43 function OptionsWebUITest() {}
44
45 OptionsWebUITest.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
93 GEN('#if defined(OS_MACOSX)');
94 GEN('#define MAYBE_testSetBooleanPrefTriggers ' +
95     'DISABLED_testSetBooleanPrefTriggers');
96 GEN('#else');
97 GEN('#define MAYBE_testSetBooleanPrefTriggers testSetBooleanPrefTriggers');
98 GEN('#endif  // defined(OS_MACOSX)');
99
100 TEST_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
119 TEST_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  */
137 TEST_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  */
163 OptionsWebUITest.prototype.testDoNotTrackInterstitial =
164     function(confirmInterstitial) {
165   Preferences.prefsFetchedCallback({'enable_do_not_track': {'value': false}});
166   var buttonToClick = confirmInterstitial ? $('do-not-track-confirm-ok') :
167                                             $('do-not-track-confirm-cancel');
168   var dntCheckbox = $('do-not-track-enabled');
169   var dntOverlay = PageManager.registeredOverlayPages['donottrackconfirm'];
170   assertFalse(dntCheckbox.checked);
171
172   var visibleChangeCounter = 0;
173   var visibleChangeHandler = function() {
174     ++visibleChangeCounter;
175     switch (visibleChangeCounter) {
176       case 1:
177         window.setTimeout(function() {
178           assertTrue(dntOverlay.visible);
179           buttonToClick.click();
180         }, 0);
181         break;
182       case 2:
183         window.setTimeout(function() {
184           assertFalse(dntOverlay.visible);
185           assertEquals(confirmInterstitial, dntCheckbox.checked);
186           dntOverlay.removeEventListener(visibleChangeHandler);
187           testDone();
188         }, 0);
189         break;
190       default:
191         assertTrue(false);
192     }
193   };
194   dntOverlay.addEventListener('visibleChange', visibleChangeHandler);
195
196   if (confirmInterstitial) {
197     this.mockHandler.expects(once()).setBooleanPref(
198         ['enable_do_not_track', true, 'Options_DoNotTrackCheckbox']);
199   } else {
200     // The mock handler complains if setBooleanPref is called even though
201     // it should not be.
202   }
203
204   dntCheckbox.click();
205 };
206
207 TEST_F('OptionsWebUITest', 'EnableDoNotTrackAndConfirmInterstitial',
208        function() {
209   this.testDoNotTrackInterstitial(true);
210 });
211
212 TEST_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.
219 TEST_F('OptionsWebUITest', 'EnableAndDisableDoNotTrack', function() {
220   Preferences.prefsFetchedCallback({'enable_do_not_track': {'value': false}});
221   var dntCheckbox = $('do-not-track-enabled');
222   var dntOverlay = PageManager.registeredOverlayPages.donottrackconfirm;
223   assertFalse(dntCheckbox.checked);
224
225   var visibleChangeCounter = 0;
226   var visibleChangeHandler = function() {
227     ++visibleChangeCounter;
228     switch (visibleChangeCounter) {
229       case 1:
230         window.setTimeout(function() {
231           assertTrue(dntOverlay.visible);
232           $('do-not-track-confirm-ok').click();
233         }, 0);
234         break;
235       case 2:
236         window.setTimeout(function() {
237           assertFalse(dntOverlay.visible);
238           assertTrue(dntCheckbox.checked);
239           dntOverlay.removeEventListener(visibleChangeHandler);
240           dntCheckbox.click();
241         }, 0);
242         break;
243       default:
244         assertNotReached();
245     }
246   };
247   dntOverlay.addEventListener('visibleChange', visibleChangeHandler);
248
249   this.mockHandler.expects(once()).setBooleanPref(
250       eq(['enable_do_not_track', true, 'Options_DoNotTrackCheckbox']));
251
252   var verifyCorrectEndState = function() {
253     window.setTimeout(function() {
254       assertFalse(dntOverlay.visible);
255       assertFalse(dntCheckbox.checked);
256       testDone();
257     }, 0);
258   };
259   this.mockHandler.expects(once()).setBooleanPref(
260       eq(['enable_do_not_track', false, 'Options_DoNotTrackCheckbox'])).will(
261           callFunction(verifyCorrectEndState));
262
263   dntCheckbox.click();
264 });
265
266 // Verify that preventDefault() is called on 'Enter' keydown events that trigger
267 // the default button. If this doesn't happen, other elements that may get
268 // focus (by the overlay closing for instance), will execute in addition to the
269 // default button. See crbug.com/268336.
270 TEST_F('OptionsWebUITest', 'EnterPreventsDefault', function() {
271   var page = HomePageOverlay.getInstance();
272   PageManager.showPageByName(page.name);
273   var event = new KeyboardEvent('keydown', {
274     'bubbles': true,
275     'cancelable': true,
276     'keyIdentifier': 'Enter'
277   });
278   assertFalse(event.defaultPrevented);
279   page.pageDiv.dispatchEvent(event);
280   assertTrue(event.defaultPrevented);
281   testDone();
282 });
283
284 // Verifies that sending an empty list of indexes to move doesn't crash chrome.
285 TEST_F('OptionsWebUITest', 'emptySelectedIndexesDoesntCrash', function() {
286   chrome.send('dragDropStartupPage', [0, []]);
287   setTimeout(testDone);
288 });
289
290 // This test turns out to be flaky on all platforms.
291 // See http://crbug.com/315250.
292
293 // An overlay's position should remain the same as it shows.
294 TEST_F('OptionsWebUITest', 'DISABLED_OverlayShowDoesntShift', function() {
295   var overlayName = 'startup';
296   var overlay = $('startup-overlay');
297   var frozenPages = document.getElementsByClassName('frozen');  // Gets updated.
298   expectEquals(0, frozenPages.length);
299
300   document.addEventListener('webkitTransitionEnd', function(e) {
301     if (e.target != overlay)
302       return;
303
304     assertFalse(overlay.classList.contains('transparent'));
305     expectEquals(numFrozenPages, frozenPages.length);
306     testDone();
307   });
308
309   PageManager.showPageByName(overlayName);
310   var numFrozenPages = frozenPages.length;
311   expectGT(numFrozenPages, 0);
312 });
313
314 GEN('#if defined(OS_CHROMEOS)');
315 // Verify that range inputs respond to touch events. Currently only Chrome OS
316 // uses slider options.
317 TEST_F('OptionsWebUITest', 'RangeInputHandlesTouchEvents', function() {
318   this.mockHandler.expects(once()).setIntegerPref([
319     'settings.touchpad.sensitivity2', 1]);
320
321   var touchpadRange = $('touchpad-sensitivity-range');
322   var event = document.createEvent('UIEvent');
323   event.initUIEvent('touchstart', true, true, window);
324   touchpadRange.dispatchEvent(event);
325
326   event = document.createEvent('UIEvent');
327   event.initUIEvent('touchmove', true, true, window);
328   touchpadRange.dispatchEvent(event);
329
330   touchpadRange.value = 1;
331
332   event = document.createEvent('UIEvent');
333   event.initUIEvent('touchend', true, true, window);
334   touchpadRange.dispatchEvent(event);
335
336   // touchcancel should also trigger the handler, since it
337   // changes the slider position.
338   this.mockHandler.expects(once()).setIntegerPref([
339     'settings.touchpad.sensitivity2', 2]);
340
341   event = document.createEvent('UIEvent');
342   event.initUIEvent('touchstart', true, true, window);
343   touchpadRange.dispatchEvent(event);
344
345   touchpadRange.value = 2;
346
347   event = document.createEvent('UIEvent');
348   event.initUIEvent('touchcancel', true, true, window);
349   touchpadRange.dispatchEvent(event);
350
351   testDone();
352 });
353 GEN('#endif');  // defined(OS_CHROMEOS)
354
355 /**
356  * TestFixture for OptionsPage WebUI testing including tab history and support
357  * for preference manipulation. If you don't need the features in the C++
358  * fixture, use the simpler OptionsWebUITest (above) instead.
359  * @extends {testing.Test}
360  * @constructor
361  */
362 function OptionsWebUIExtendedTest() {}
363
364 OptionsWebUIExtendedTest.prototype = {
365   __proto__: testing.Test.prototype,
366
367   /** @override */
368   browsePreload: 'chrome://settings-frame',
369
370   /** @override */
371   typedefCppFixture: 'OptionsBrowserTest',
372
373   testGenPreamble: function() {
374     // Start with no supervised users managed by this profile.
375     GEN('  ClearPref("' + SUPERVISED_USERS_PREF + '");');
376   },
377
378   /** @override */
379   isAsync: true,
380
381   /** @override */
382   setUp: function() {
383       // user-image-stream is a streaming video element used for capturing a
384       // user image during OOBE.
385       this.accessibilityAuditConfig.ignoreSelectors('videoWithoutCaptions',
386                                                     '.user-image-stream');
387   },
388
389   /**
390    * Asserts that two non-nested arrays are equal. The arrays must contain only
391    * plain data types, no nested arrays or other objects.
392    * @param {Array} expected An array of expected values.
393    * @param {Array} result An array of actual values.
394    * @param {boolean} doSort If true, the arrays will be sorted before being
395    *     compared.
396    * @param {string} description A brief description for the array of actual
397    *     values, to use in an error message if the arrays differ.
398    * @private
399    */
400   compareArrays_: function(expected, result, doSort, description) {
401     var errorMessage = '\n' + description + ': ' + result +
402                        '\nExpected: ' + expected;
403     assertEquals(expected.length, result.length, errorMessage);
404
405     var expectedSorted = expected.slice();
406     var resultSorted = result.slice();
407     if (doSort) {
408       expectedSorted.sort();
409       resultSorted.sort();
410     }
411
412     for (var i = 0; i < expectedSorted.length; ++i) {
413       assertEquals(expectedSorted[i], resultSorted[i], errorMessage);
414     }
415   },
416
417   /**
418    * Verifies that the correct pages are currently open/visible.
419    * @param {!Array.<string>} expectedPages An array of page names expected to
420    *     be open, with the topmost listed last.
421    * @param {string=} opt_expectedUrl The URL path, including hash, expected to
422    *     be open. If undefined, the topmost (last) page name in |expectedPages|
423    *     will be used. In either case, 'chrome://settings-frame/' will be
424    *     prepended.
425    * @private
426    */
427   verifyOpenPages_: function(expectedPages, opt_expectedUrl) {
428     // Check the topmost page.
429     expectEquals(null, PageManager.getVisibleBubble());
430     var currentPage = PageManager.getTopmostVisiblePage();
431
432     var lastExpected = expectedPages[expectedPages.length - 1];
433     expectEquals(lastExpected, currentPage.name);
434     // We'd like to check the title too, but we have to load the settings-frame
435     // instead of the outer settings page in order to have access to
436     // OptionsPage, and setting the title from within the settings-frame fails
437     // because of cross-origin access restrictions.
438     // TODO(pamg): Add a test fixture that loads chrome://settings and uses
439     // UI elements to access sub-pages, so we can test the titles and
440     // search-page URLs.
441     var expectedUrl = (typeof opt_expectedUrl == 'undefined') ?
442         lastExpected : opt_expectedUrl;
443     var fullExpectedUrl = 'chrome://settings-frame/' + expectedUrl;
444     expectEquals(fullExpectedUrl, window.location.href);
445
446     // Collect open pages.
447     var allPageNames = Object.keys(PageManager.registeredPages).concat(
448                        Object.keys(PageManager.registeredOverlayPages));
449     var openPages = [];
450     for (var i = 0; i < allPageNames.length; ++i) {
451       var name = allPageNames[i];
452       var page = PageManager.registeredPages[name] ||
453                  PageManager.registeredOverlayPages[name];
454       if (page.visible)
455         openPages.push(page.name);
456     }
457
458     this.compareArrays_(expectedPages, openPages, true, 'Open pages');
459   },
460
461   /*
462    * Verifies that the correct URLs are listed in the history. Asynchronous.
463    * @param {!Array.<string>} expectedHistory An array of URL paths expected to
464    *     be in the tab navigation history, sorted by visit time, including the
465    *     current page as the last entry. The base URL (chrome://settings-frame/)
466    *     will be prepended to each. An initial 'about:blank' history entry is
467    *     assumed and should not be included in this list.
468    * @param {Function=} callback A function to be called after the history has
469    *     been verified successfully. May be undefined.
470    * @private
471    */
472   verifyHistory_: function(expectedHistory, callback) {
473     var self = this;
474     OptionsWebUIExtendedTest.verifyHistoryCallback = function(results) {
475       // The history always starts with a blank page.
476       assertEquals('about:blank', results.shift());
477       var fullExpectedHistory = [];
478       for (var i = 0; i < expectedHistory.length; ++i) {
479         fullExpectedHistory.push(
480             'chrome://settings-frame/' + expectedHistory[i]);
481       }
482       self.compareArrays_(fullExpectedHistory, results, false, 'History');
483       callback();
484     };
485
486     // The C++ fixture will call verifyHistoryCallback with the results.
487     chrome.send('optionsTestReportHistory');
488   },
489
490   /**
491    * Overrides the page callbacks for the given PageManager overlay to verify
492    * that they are not called.
493    * @param {Object} overlay The singleton instance of the overlay.
494    * @private
495    */
496   prohibitChangesToOverlay_: function(overlay) {
497     overlay.initializePage =
498         overlay.didShowPage =
499         overlay.didClosePage = function() {
500           assertTrue(false,
501                      'Overlay was affected when changes were prohibited.');
502         };
503   },
504 };
505
506 /**
507  * Set by verifyHistory_ to incorporate a followup callback, then called by the
508  * C++ fixture with the navigation history to be verified.
509  * @type {Function}
510  */
511 OptionsWebUIExtendedTest.verifyHistoryCallback = null;
512
513 // Show the search page with no query string, to fall back to the settings page.
514 // Test disabled because it's flaky. crbug.com/303841
515 TEST_F('OptionsWebUIExtendedTest', 'DISABLED_ShowSearchPageNoQuery',
516        function() {
517   PageManager.showPageByName('search');
518   this.verifyOpenPages_(['settings']);
519   this.verifyHistory_(['settings'], testDone);
520 });
521
522 // Show a page without updating history.
523 TEST_F('OptionsWebUIExtendedTest', 'ShowPageNoHistory', function() {
524   this.verifyOpenPages_(['settings'], '');
525   // There are only two main pages, 'settings' and 'search'. It's not possible
526   // to show the search page using PageManager.showPageByName, because it
527   // reverts to the settings page if it has no search text set. So we show the
528   // search page by performing a search, then test showPageByName.
529   $('search-field').onsearch({currentTarget: {value: 'query'}});
530
531   // The settings page is also still "open" (i.e., visible), in order to show
532   // the search results. Furthermore, the URL hasn't been updated in the parent
533   // page, because we've loaded the chrome-settings frame instead of the whole
534   // settings page, so the cross-origin call to set the URL fails.
535   this.verifyOpenPages_(['settings', 'search'], 'search#query');
536   var self = this;
537   this.verifyHistory_(['', 'search#query'], function() {
538     PageManager.showPageByName('settings', false);
539     self.verifyOpenPages_(['settings'], 'search#query');
540     self.verifyHistory_(['', 'search#query'], testDone);
541   });
542 });
543
544 TEST_F('OptionsWebUIExtendedTest', 'ShowPageWithHistory', function() {
545   // See comments for ShowPageNoHistory.
546   $('search-field').onsearch({currentTarget: {value: 'query'}});
547   var self = this;
548   this.verifyHistory_(['', 'search#query'], function() {
549     PageManager.showPageByName('settings', true);
550     self.verifyOpenPages_(['settings'], '#query');
551     self.verifyHistory_(['', 'search#query', '#query'],
552                         testDone);
553   });
554 });
555
556 TEST_F('OptionsWebUIExtendedTest', 'ShowPageReplaceHistory', function() {
557   // See comments for ShowPageNoHistory.
558   $('search-field').onsearch({currentTarget: {value: 'query'}});
559   var self = this;
560   this.verifyHistory_(['', 'search#query'], function() {
561     PageManager.showPageByName('settings', true, {'replaceState': true});
562     self.verifyOpenPages_(['settings'], '#query');
563     self.verifyHistory_(['', '#query'], testDone);
564   });
565 });
566
567 // This should be identical to ShowPageWithHisory.
568 TEST_F('OptionsWebUIExtendedTest', 'NavigateToPage', function() {
569   // See comments for ShowPageNoHistory.
570   $('search-field').onsearch({currentTarget: {value: 'query'}});
571   var self = this;
572   this.verifyHistory_(['', 'search#query'], function() {
573     PageManager.showPageByName('settings');
574     self.verifyOpenPages_(['settings'], '#query');
575     self.verifyHistory_(['', 'search#query', '#query'],
576                         testDone);
577   });
578 });
579
580 // Settings overlays are much more straightforward than settings pages, opening
581 // normally with none of the latter's quirks in the expected history or URL.
582 TEST_F('OptionsWebUIExtendedTest', 'ShowOverlayNoHistory', function() {
583   // Open a layer-1 overlay, not updating history.
584   PageManager.showPageByName('languages', false);
585   this.verifyOpenPages_(['settings', 'languages'], '');
586
587   var self = this;
588   this.verifyHistory_([''], function() {
589     // Open a layer-2 overlay for which the layer-1 is a parent, not updating
590     // history.
591     PageManager.showPageByName('addLanguage', false);
592     self.verifyOpenPages_(['settings', 'languages', 'addLanguage'],
593                           '');
594     self.verifyHistory_([''], testDone);
595   });
596 });
597
598 TEST_F('OptionsWebUIExtendedTest', 'ShowOverlayWithHistory', function() {
599   // Open a layer-1 overlay, updating history.
600   PageManager.showPageByName('languages', true);
601   this.verifyOpenPages_(['settings', 'languages']);
602
603   var self = this;
604   this.verifyHistory_(['', 'languages'], function() {
605     // Open a layer-2 overlay, updating history.
606     PageManager.showPageByName('addLanguage', true);
607     self.verifyOpenPages_(['settings', 'languages', 'addLanguage']);
608     self.verifyHistory_(['', 'languages', 'addLanguage'], testDone);
609   });
610 });
611
612 TEST_F('OptionsWebUIExtendedTest', 'ShowOverlayReplaceHistory', function() {
613   // Open a layer-1 overlay, updating history.
614   PageManager.showPageByName('languages', true);
615   var self = this;
616   this.verifyHistory_(['', 'languages'], function() {
617     // Open a layer-2 overlay, replacing history.
618     PageManager.showPageByName('addLanguage', true, {'replaceState': true});
619     self.verifyOpenPages_(['settings', 'languages', 'addLanguage']);
620     self.verifyHistory_(['', 'addLanguage'], testDone);
621   });
622 });
623
624 // Directly show an overlay further above this page, i.e. one for which the
625 // current page is an ancestor but not a parent.
626 TEST_F('OptionsWebUIExtendedTest', 'ShowOverlayFurtherAbove', function() {
627   // Open a layer-2 overlay directly.
628   PageManager.showPageByName('addLanguage', true);
629   this.verifyOpenPages_(['settings', 'languages', 'addLanguage']);
630   var self = this;
631   this.verifyHistory_(['', 'addLanguage'], testDone);
632 });
633
634 // Directly show a layer-2 overlay for which the layer-1 overlay is not a
635 // parent.
636 TEST_F('OptionsWebUIExtendedTest', 'ShowUnrelatedOverlay', function() {
637   // Open a layer-1 overlay.
638   PageManager.showPageByName('languages', true);
639   this.verifyOpenPages_(['settings', 'languages']);
640
641   var self = this;
642   this.verifyHistory_(['', 'languages'], function() {
643     // Open an unrelated layer-2 overlay.
644     PageManager.showPageByName('cookies', true);
645     self.verifyOpenPages_(['settings', 'content', 'cookies']);
646     self.verifyHistory_(['', 'languages', 'cookies'], testDone);
647   });
648 });
649
650 // Close an overlay.
651 TEST_F('OptionsWebUIExtendedTest', 'CloseOverlay', function() {
652   // Open a layer-1 overlay, then a layer-2 overlay on top of it.
653   PageManager.showPageByName('languages', true);
654   this.verifyOpenPages_(['settings', 'languages']);
655   PageManager.showPageByName('addLanguage', true);
656   this.verifyOpenPages_(['settings', 'languages', 'addLanguage']);
657
658   var self = this;
659   this.verifyHistory_(['', 'languages', 'addLanguage'], function() {
660     // Close the layer-2 overlay.
661     PageManager.closeOverlay();
662     self.verifyOpenPages_(['settings', 'languages']);
663     self.verifyHistory_(
664         ['', 'languages', 'addLanguage', 'languages'],
665         function() {
666       // Close the layer-1 overlay.
667       PageManager.closeOverlay();
668       self.verifyOpenPages_(['settings'], '');
669       self.verifyHistory_(
670           ['', 'languages', 'addLanguage', 'languages', ''],
671           testDone);
672     });
673   });
674 });
675
676 // Test that closing an overlay that did not push history when opening does not
677 // again push history.
678 TEST_F('OptionsWebUIExtendedTest', 'CloseOverlayNoHistory', function() {
679   // Open the do not track confirmation prompt.
680   PageManager.showPageByName('doNotTrackConfirm', false);
681
682   // Opening the prompt does not add to the history.
683   this.verifyHistory_([''], function() {
684     // Close the overlay.
685     PageManager.closeOverlay();
686     // Still no history changes.
687     this.verifyHistory_([''], testDone);
688   }.bind(this));
689 });
690
691 // Make sure an overlay isn't closed (even temporarily) when another overlay is
692 // opened on top.
693 TEST_F('OptionsWebUIExtendedTest', 'OverlayAboveNoReset', function() {
694   // Open a layer-1 overlay.
695   PageManager.showPageByName('languages', true);
696   this.verifyOpenPages_(['settings', 'languages']);
697
698   // Open a layer-2 overlay on top. This should not close 'languages'.
699   this.prohibitChangesToOverlay_(options.LanguageOptions.getInstance());
700   PageManager.showPageByName('addLanguage', true);
701   this.verifyOpenPages_(['settings', 'languages', 'addLanguage']);
702   testDone();
703 });
704
705 TEST_F('OptionsWebUIExtendedTest', 'OverlayTabNavigation', function() {
706   // Open a layer-1 overlay, then a layer-2 overlay on top of it.
707   PageManager.showPageByName('languages', true);
708   PageManager.showPageByName('addLanguage', true);
709   var self = this;
710
711   // Go back twice, then forward twice.
712   self.verifyOpenPages_(['settings', 'languages', 'addLanguage']);
713   self.verifyHistory_(['', 'languages', 'addLanguage'], function() {
714     window.history.back();
715     waitForPopstate(function() {
716       self.verifyOpenPages_(['settings', 'languages']);
717       self.verifyHistory_(['', 'languages'], function() {
718         window.history.back();
719         waitForPopstate(function() {
720           self.verifyOpenPages_(['settings'], '');
721           self.verifyHistory_([''], function() {
722             window.history.forward();
723             waitForPopstate(function() {
724               self.verifyOpenPages_(['settings', 'languages']);
725               self.verifyHistory_(['', 'languages'], function() {
726                 window.history.forward();
727                 waitForPopstate(function() {
728                   self.verifyOpenPages_(
729                       ['settings', 'languages', 'addLanguage']);
730                   self.verifyHistory_(
731                       ['', 'languages', 'addLanguage'], testDone);
732                 });
733               });
734             });
735           });
736         });
737       });
738     });
739   });
740 });
741
742 // Going "back" to an overlay that's a child of the current overlay shouldn't
743 // close the current one.
744 TEST_F('OptionsWebUIExtendedTest', 'OverlayBackToChild', function() {
745   // Open a layer-1 overlay, then a layer-2 overlay on top of it.
746   PageManager.showPageByName('languages', true);
747   PageManager.showPageByName('addLanguage', true);
748   var self = this;
749
750   self.verifyOpenPages_(['settings', 'languages', 'addLanguage']);
751   self.verifyHistory_(['', 'languages', 'addLanguage'], function() {
752     // Close the top overlay, then go back to it.
753     PageManager.closeOverlay();
754     self.verifyOpenPages_(['settings', 'languages']);
755     self.verifyHistory_(
756         ['', 'languages', 'addLanguage', 'languages'],
757         function() {
758       // Going back to the 'addLanguage' page should not close 'languages'.
759       self.prohibitChangesToOverlay_(options.LanguageOptions.getInstance());
760       window.history.back();
761       waitForPopstate(function() {
762         self.verifyOpenPages_(['settings', 'languages', 'addLanguage']);
763         self.verifyHistory_(['', 'languages', 'addLanguage'],
764                             testDone);
765       });
766     });
767   });
768 });
769
770 // Going back to an unrelated overlay should close the overlay and its parent.
771 TEST_F('OptionsWebUIExtendedTest', 'OverlayBackToUnrelated', function() {
772   // Open a layer-1 overlay, then an unrelated layer-2 overlay.
773   PageManager.showPageByName('languages', true);
774   PageManager.showPageByName('cookies', true);
775   var self = this;
776   self.verifyOpenPages_(['settings', 'content', 'cookies']);
777   self.verifyHistory_(['', 'languages', 'cookies'], function() {
778     window.history.back();
779     waitForPopstate(function() {
780       self.verifyOpenPages_(['settings', 'languages']);
781       testDone();
782     });
783   });
784 });
785
786 // Verify history changes properly while the page is loading.
787 TEST_F('OptionsWebUIExtendedTest', 'HistoryUpdatedAfterLoading', function() {
788   var loc = location.href;
789
790   document.documentElement.classList.add('loading');
791   assertTrue(PageManager.isLoading());
792   PageManager.showPageByName('searchEngines');
793   expectNotEquals(loc, location.href);
794
795   document.documentElement.classList.remove('loading');
796   assertFalse(PageManager.isLoading());
797   PageManager.showDefaultPage();
798   expectEquals(loc, location.href);
799
800   testDone();
801 });
802
803 // A tip should be shown or hidden depending on whether this profile manages any
804 // supervised users.
805 TEST_F('OptionsWebUIExtendedTest', 'SupervisingUsers', function() {
806   // We start managing no supervised users.
807   assertTrue($('profiles-supervised-dashboard-tip').hidden);
808
809   // Remove all supervised users, then add some, watching for the pref change
810   // notifications and UI updates in each case. Any non-empty pref dictionary
811   // is interpreted as having supervised users.
812   chrome.send('optionsTestSetPref', [SUPERVISED_USERS_PREF, {key: 'value'}]);
813   waitForResponse(BrowserOptions, 'updateManagesSupervisedUsers', function() {
814     assertFalse($('profiles-supervised-dashboard-tip').hidden);
815     chrome.send('optionsTestSetPref', [SUPERVISED_USERS_PREF, {}]);
816     waitForResponse(BrowserOptions, 'updateManagesSupervisedUsers', function() {
817       assertTrue($('profiles-supervised-dashboard-tip').hidden);
818       testDone();
819     });
820   });
821 });
822
823 /**
824  * TestFixture that loads the options page at a bogus URL.
825  * @extends {OptionsWebUIExtendedTest}
826  * @constructor
827  */
828 function OptionsWebUIRedirectTest() {
829   OptionsWebUIExtendedTest.call(this);
830 }
831
832 OptionsWebUIRedirectTest.prototype = {
833   __proto__: OptionsWebUIExtendedTest.prototype,
834
835   /** @override */
836   browsePreload: 'chrome://settings-frame/nonexistantPage',
837 };
838
839 TEST_F('OptionsWebUIRedirectTest', 'TestURL', function() {
840   assertEquals('chrome://settings-frame/', document.location.href);
841   this.verifyHistory_([''], testDone);
842 });