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