home *** CD-ROM | disk | FTP | other *** search
/ Freelog 115 / FreelogNo115-MaiJuin2013.iso / Internet / AvantBrowser / asetup.exe / _data / webkit / chrome.dll / 0 / BINDATA / 625 < prev    next >
Encoding:
Text File  |  2013-04-03  |  78.8 KB  |  2,475 lines

  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. // TODO(sail): Refactor options_page and remove this include.
  6. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  7. // Use of this source code is governed by a BSD-style license that can be
  8. // found in the LICENSE file.
  9.  
  10. cr.define('options', function() {
  11.   /////////////////////////////////////////////////////////////////////////////
  12.   // OptionsPage class:
  13.  
  14.   /**
  15.    * Base class for options page.
  16.    * @constructor
  17.    * @param {string} name Options page name.
  18.    * @param {string} title Options page title, used for history.
  19.    * @extends {EventTarget}
  20.    */
  21.   function OptionsPage(name, title, pageDivName) {
  22.     this.name = name;
  23.     this.title = title;
  24.     this.pageDivName = pageDivName;
  25.     this.pageDiv = $(this.pageDivName);
  26.     this.tab = null;
  27.     this.lastFocusedElement = null;
  28.   }
  29.  
  30.   /**
  31.    * This is the absolute difference maintained between standard and
  32.    * fixed-width font sizes. Refer http://crbug.com/91922.
  33.    * @const
  34.    */
  35.   OptionsPage.SIZE_DIFFERENCE_FIXED_STANDARD = 3;
  36.  
  37.   /**
  38.    * Offset of page container in pixels, to allow room for side menu.
  39.    * Simplified settings pages can override this if they don't use the menu.
  40.    * The default (155) comes from -webkit-margin-start in uber_shared.css
  41.    * @private
  42.    */
  43.   OptionsPage.horizontalOffset = 155;
  44.  
  45.   /**
  46.    * Main level option pages. Maps lower-case page names to the respective page
  47.    * object.
  48.    * @protected
  49.    */
  50.   OptionsPage.registeredPages = {};
  51.  
  52.   /**
  53.    * Pages which are meant to behave like modal dialogs. Maps lower-case overlay
  54.    * names to the respective overlay object.
  55.    * @protected
  56.    */
  57.   OptionsPage.registeredOverlayPages = {};
  58.  
  59.   /**
  60.    * Gets the default page (to be shown on initial load).
  61.    */
  62.   OptionsPage.getDefaultPage = function() {
  63.     return BrowserOptions.getInstance();
  64.   };
  65.  
  66.   /**
  67.    * Shows the default page.
  68.    */
  69.   OptionsPage.showDefaultPage = function() {
  70.     this.navigateToPage(this.getDefaultPage().name);
  71.   };
  72.  
  73.   /**
  74.    * "Navigates" to a page, meaning that the page will be shown and the
  75.    * appropriate entry is placed in the history.
  76.    * @param {string} pageName Page name.
  77.    */
  78.   OptionsPage.navigateToPage = function(pageName) {
  79.     this.showPageByName(pageName, true);
  80.   };
  81.  
  82.   /**
  83.    * Shows a registered page. This handles both top-level and overlay pages.
  84.    * @param {string} pageName Page name.
  85.    * @param {boolean} updateHistory True if we should update the history after
  86.    *     showing the page.
  87.    * @param {Object=} opt_propertyBag An optional bag of properties including
  88.    *     replaceState (if history state should be replaced instead of pushed).
  89.    * @private
  90.    */
  91.   OptionsPage.showPageByName = function(pageName,
  92.                                         updateHistory,
  93.                                         opt_propertyBag) {
  94.     // If |opt_propertyBag| is non-truthy, homogenize to object.
  95.     opt_propertyBag = opt_propertyBag || {};
  96.  
  97.     // If a bubble is currently being shown, hide it.
  98.     this.hideBubble();
  99.  
  100.     // Find the currently visible root-level page.
  101.     var rootPage = null;
  102.     for (var name in this.registeredPages) {
  103.       var page = this.registeredPages[name];
  104.       if (page.visible && !page.parentPage) {
  105.         rootPage = page;
  106.         break;
  107.       }
  108.     }
  109.  
  110.     // Find the target page.
  111.     var targetPage = this.registeredPages[pageName.toLowerCase()];
  112.     if (!targetPage || !targetPage.canShowPage()) {
  113.       // If it's not a page, try it as an overlay.
  114.       if (!targetPage && this.showOverlay_(pageName, rootPage)) {
  115.         if (updateHistory)
  116.           this.updateHistoryState_(!!opt_propertyBag.replaceState);
  117.         return;
  118.       } else {
  119.         targetPage = this.getDefaultPage();
  120.       }
  121.     }
  122.  
  123.     pageName = targetPage.name.toLowerCase();
  124.     var targetPageWasVisible = targetPage.visible;
  125.  
  126.     // Determine if the root page is 'sticky', meaning that it
  127.     // shouldn't change when showing an overlay. This can happen for special
  128.     // pages like Search.
  129.     var isRootPageLocked =
  130.         rootPage && rootPage.sticky && targetPage.parentPage;
  131.  
  132.     var allPageNames = Array.prototype.concat.call(
  133.         Object.keys(this.registeredPages),
  134.         Object.keys(this.registeredOverlayPages));
  135.  
  136.     // Notify pages if they will be hidden.
  137.     for (var i = 0; i < allPageNames.length; ++i) {
  138.       var name = allPageNames[i];
  139.       var page = this.registeredPages[name] ||
  140.                  this.registeredOverlayPages[name];
  141.       if (!page.parentPage && isRootPageLocked)
  142.         continue;
  143.       if (page.willHidePage && name != pageName &&
  144.           !page.isAncestorOfPage(targetPage)) {
  145.         page.willHidePage();
  146.       }
  147.     }
  148.  
  149.     // Update visibilities to show only the hierarchy of the target page.
  150.     for (var i = 0; i < allPageNames.length; ++i) {
  151.       var name = allPageNames[i];
  152.       var page = this.registeredPages[name] ||
  153.                  this.registeredOverlayPages[name];
  154.       if (!page.parentPage && isRootPageLocked)
  155.         continue;
  156.       page.visible = name == pageName || page.isAncestorOfPage(targetPage);
  157.     }
  158.  
  159.     // Update the history and current location.
  160.     if (updateHistory)
  161.       this.updateHistoryState_(!!opt_propertyBag.replaceState);
  162.  
  163.     // Update tab title.
  164.     this.setTitle_(targetPage.title);
  165.  
  166.     // Update focus if any other control was focused before.
  167.     if (document.activeElement != document.body)
  168.       targetPage.focus();
  169.  
  170.     // Notify pages if they were shown.
  171.     for (var i = 0; i < allPageNames.length; ++i) {
  172.       var name = allPageNames[i];
  173.       var page = this.registeredPages[name] ||
  174.                  this.registeredOverlayPages[name];
  175.       if (!page.parentPage && isRootPageLocked)
  176.         continue;
  177.       if (!targetPageWasVisible && page.didShowPage &&
  178.           (name == pageName || page.isAncestorOfPage(targetPage))) {
  179.         page.didShowPage();
  180.       }
  181.     }
  182.   };
  183.  
  184.   /**
  185.    * Sets the title of the page. This is accomplished by calling into the
  186.    * parent page API.
  187.    * @param {String} title The title string.
  188.    * @private
  189.    */
  190.   OptionsPage.setTitle_ = function(title) {
  191.     uber.invokeMethodOnParent('setTitle', {title: title});
  192.   };
  193.  
  194.   /**
  195.    * Scrolls the page to the correct position (the top when opening an overlay,
  196.    * or the old scroll position a previously hidden overlay becomes visible).
  197.    * @private
  198.    */
  199.   OptionsPage.updateScrollPosition_ = function() {
  200.     var container = $('page-container');
  201.     var scrollTop = container.oldScrollTop || 0;
  202.     container.oldScrollTop = undefined;
  203.     window.scroll(document.body.scrollLeft, scrollTop);
  204.   };
  205.  
  206.   /**
  207.    * Pushes the current page onto the history stack, overriding the last page
  208.    * if it is the generic chrome://settings/.
  209.    * @param {boolean} replace If true, allow no history events to be created.
  210.    * @param {object=} opt_params A bag of optional params, including:
  211.    *     {boolean} ignoreHash Whether to include the hash or not.
  212.    * @private
  213.    */
  214.   OptionsPage.updateHistoryState_ = function(replace, opt_params) {
  215.     var page = this.getTopmostVisiblePage();
  216.     var path = window.location.pathname + window.location.hash;
  217.     if (path)
  218.       path = path.slice(1).replace(/\/(?:#|$)/, '');  // Remove trailing slash.
  219.  
  220.     // Update tab title.
  221.     this.setTitle_(page.title);
  222.  
  223.     // The page is already in history (the user may have clicked the same link
  224.     // twice). Do nothing.
  225.     if (path == page.name &&
  226.         !document.documentElement.classList.contains('loading')) {
  227.       return;
  228.     }
  229.  
  230.     var hash = opt_params && opt_params.ignoreHash ? '' : window.location.hash;
  231.  
  232.     // If settings are embedded, tell the outer page to set its "path" to the
  233.     // inner frame's path.
  234.     var outerPath = (page == this.getDefaultPage() ? '' : page.name) + hash;
  235.     uber.invokeMethodOnParent('setPath', {path: outerPath});
  236.  
  237.     // If there is no path, the current location is chrome://settings/.
  238.     // Override this with the new page.
  239.     var historyFunction = path && !replace ? window.history.pushState :
  240.                                              window.history.replaceState;
  241.     historyFunction.call(window.history,
  242.                          {pageName: page.name},
  243.                          page.title,
  244.                          '/' + page.name + hash);
  245.   };
  246.  
  247.   /**
  248.    * Shows a registered Overlay page. Does not update history.
  249.    * @param {string} overlayName Page name.
  250.    * @param {OptionPage} rootPage The currently visible root-level page.
  251.    * @return {boolean} whether we showed an overlay.
  252.    */
  253.   OptionsPage.showOverlay_ = function(overlayName, rootPage) {
  254.     var overlay = this.registeredOverlayPages[overlayName.toLowerCase()];
  255.     if (!overlay || !overlay.canShowPage())
  256.       return false;
  257.  
  258.     // Save the currently focused element in the page for restoration later.
  259.     var currentPage = this.getTopmostVisiblePage();
  260.     if (currentPage)
  261.       currentPage.lastFocusedElement = document.activeElement;
  262.  
  263.     if ((!rootPage || !rootPage.sticky) && overlay.parentPage)
  264.       this.showPageByName(overlay.parentPage.name, false);
  265.  
  266.     if (!overlay.visible) {
  267.       overlay.visible = true;
  268.       if (overlay.didShowPage) overlay.didShowPage();
  269.     }
  270.  
  271.     // Update tab title.
  272.     this.setTitle_(overlay.title);
  273.  
  274.     // Change focus to the overlay if any other control was focused before.
  275.     if (document.activeElement != document.body)
  276.       overlay.focus();
  277.  
  278.     $('searchBox').setAttribute('aria-hidden', true);
  279.  
  280.     if ($('search-field').value == '') {
  281.       var section = overlay.associatedSection;
  282.       if (section)
  283.         options.BrowserOptions.scrollToSection(section);
  284.     }
  285.  
  286.     return true;
  287.   };
  288.  
  289.   /**
  290.    * Returns whether or not an overlay is visible.
  291.    * @return {boolean} True if an overlay is visible.
  292.    * @private
  293.    */
  294.   OptionsPage.isOverlayVisible_ = function() {
  295.     return this.getVisibleOverlay_() != null;
  296.   };
  297.  
  298.   /**
  299.    * Returns the currently visible overlay, or null if no page is visible.
  300.    * @return {OptionPage} The visible overlay.
  301.    */
  302.   OptionsPage.getVisibleOverlay_ = function() {
  303.     var topmostPage = null;
  304.     for (var name in this.registeredOverlayPages) {
  305.       var page = this.registeredOverlayPages[name];
  306.       if (page.visible &&
  307.           (!topmostPage || page.nestingLevel > topmostPage.nestingLevel)) {
  308.         topmostPage = page;
  309.       }
  310.     }
  311.     return topmostPage;
  312.   };
  313.  
  314.   /**
  315.    * Restores the last focused element on a given page.
  316.    */
  317.   OptionsPage.restoreLastFocusedElement_ = function() {
  318.     var currentPage = this.getTopmostVisiblePage();
  319.     if (currentPage.lastFocusedElement)
  320.       currentPage.lastFocusedElement.focus();
  321.   };
  322.  
  323.   /**
  324.    * Closes the visible overlay. Updates the history state after closing the
  325.    * overlay.
  326.    */
  327.   OptionsPage.closeOverlay = function() {
  328.     var overlay = this.getVisibleOverlay_();
  329.     if (!overlay)
  330.       return;
  331.  
  332.     overlay.visible = false;
  333.  
  334.     if (overlay.didClosePage) overlay.didClosePage();
  335.     this.updateHistoryState_(false, {ignoreHash: true});
  336.  
  337.     this.restoreLastFocusedElement_();
  338.     if (!this.isOverlayVisible_())
  339.       $('searchBox').removeAttribute('aria-hidden');
  340.   };
  341.  
  342.   /**
  343.    * Cancels (closes) the overlay, due to the user pressing <Esc>.
  344.    */
  345.   OptionsPage.cancelOverlay = function() {
  346.     // Blur the active element to ensure any changed pref value is saved.
  347.     document.activeElement.blur();
  348.     var overlay = this.getVisibleOverlay_();
  349.     // Let the overlay handle the <Esc> if it wants to.
  350.     if (overlay.handleCancel) {
  351.       overlay.handleCancel();
  352.       this.restoreLastFocusedElement_();
  353.     } else {
  354.       this.closeOverlay();
  355.     }
  356.   };
  357.  
  358.   /**
  359.    * Hides the visible overlay. Does not affect the history state.
  360.    * @private
  361.    */
  362.   OptionsPage.hideOverlay_ = function() {
  363.     var overlay = this.getVisibleOverlay_();
  364.     if (overlay)
  365.       overlay.visible = false;
  366.   };
  367.  
  368.   /**
  369.    * Returns the pages which are currently visible, ordered by nesting level
  370.    * (ascending).
  371.    * @return {Array.OptionPage} The pages which are currently visible, ordered
  372.    * by nesting level (ascending).
  373.    */
  374.   OptionsPage.getVisiblePages_ = function() {
  375.     var visiblePages = [];
  376.     for (var name in this.registeredPages) {
  377.       var page = this.registeredPages[name];
  378.       if (page.visible)
  379.         visiblePages[page.nestingLevel] = page;
  380.     }
  381.     return visiblePages;
  382.   };
  383.  
  384.   /**
  385.    * Returns the topmost visible page (overlays excluded).
  386.    * @return {OptionPage} The topmost visible page aside any overlay.
  387.    * @private
  388.    */
  389.   OptionsPage.getTopmostVisibleNonOverlayPage_ = function() {
  390.     var topPage = null;
  391.     for (var name in this.registeredPages) {
  392.       var page = this.registeredPages[name];
  393.       if (page.visible &&
  394.           (!topPage || page.nestingLevel > topPage.nestingLevel))
  395.         topPage = page;
  396.     }
  397.  
  398.     return topPage;
  399.   };
  400.  
  401.   /**
  402.    * Returns the topmost visible page, or null if no page is visible.
  403.    * @return {OptionPage} The topmost visible page.
  404.    */
  405.   OptionsPage.getTopmostVisiblePage = function() {
  406.     // Check overlays first since they're top-most if visible.
  407.     return this.getVisibleOverlay_() || this.getTopmostVisibleNonOverlayPage_();
  408.   };
  409.  
  410.   /**
  411.    * Returns the currently visible bubble, or null if no bubble is visible.
  412.    * @return {OptionsBubble} The bubble currently being shown.
  413.    */
  414.   OptionsPage.getVisibleBubble = function() {
  415.     var bubble = OptionsPage.bubble_;
  416.     return bubble && !bubble.hidden ? bubble : null;
  417.   };
  418.  
  419.   /**
  420.    * Shows an informational bubble displaying |content| and pointing at the
  421.    * |target| element. If |content| has focusable elements, they join the
  422.    * current page's tab order as siblings of |domSibling|.
  423.    * @param {HTMLDivElement} content The content of the bubble.
  424.    * @param {HTMLElement} target The element at which the bubble points.
  425.    * @param {HTMLElement} domSibling The element after which the bubble is added
  426.    *                      to the DOM.
  427.    * @param {cr.ui.ArrowLocation} location The arrow location.
  428.    */
  429.   OptionsPage.showBubble = function(content, target, domSibling, location) {
  430.     OptionsPage.hideBubble();
  431.  
  432.     var bubble = new options.OptionsBubble;
  433.     bubble.anchorNode = target;
  434.     bubble.domSibling = domSibling;
  435.     bubble.arrowLocation = location;
  436.     bubble.content = content;
  437.     bubble.show();
  438.     OptionsPage.bubble_ = bubble;
  439.   };
  440.  
  441.   /**
  442.    * Hides the currently visible bubble, if any.
  443.    */
  444.   OptionsPage.hideBubble = function() {
  445.     if (OptionsPage.bubble_)
  446.       OptionsPage.bubble_.hide();
  447.   };
  448.  
  449.   /**
  450.    * Shows the tab contents for the given navigation tab.
  451.    * @param {!Element} tab The tab that the user clicked.
  452.    */
  453.   OptionsPage.showTab = function(tab) {
  454.     // Search parents until we find a tab, or the nav bar itself. This allows
  455.     // tabs to have child nodes, e.g. labels in separately-styled spans.
  456.     while (tab && !tab.classList.contains('subpages-nav-tabs') &&
  457.            !tab.classList.contains('tab')) {
  458.       tab = tab.parentNode;
  459.     }
  460.     if (!tab || !tab.classList.contains('tab'))
  461.       return;
  462.  
  463.     // Find tab bar of the tab.
  464.     var tabBar = tab;
  465.     while (tabBar && !tabBar.classList.contains('subpages-nav-tabs')) {
  466.       tabBar = tabBar.parentNode;
  467.     }
  468.     if (!tabBar)
  469.       return;
  470.  
  471.     if (tabBar.activeNavTab != null) {
  472.       tabBar.activeNavTab.classList.remove('active-tab');
  473.       $(tabBar.activeNavTab.getAttribute('tab-contents')).classList.
  474.           remove('active-tab-contents');
  475.     }
  476.  
  477.     tab.classList.add('active-tab');
  478.     $(tab.getAttribute('tab-contents')).classList.add('active-tab-contents');
  479.     tabBar.activeNavTab = tab;
  480.   };
  481.  
  482.   /**
  483.    * Registers new options page.
  484.    * @param {OptionsPage} page Page to register.
  485.    */
  486.   OptionsPage.register = function(page) {
  487.     this.registeredPages[page.name.toLowerCase()] = page;
  488.     page.initializePage();
  489.   };
  490.  
  491.   /**
  492.    * Find an enclosing section for an element if it exists.
  493.    * @param {Element} element Element to search.
  494.    * @return {OptionPage} The section element, or null.
  495.    * @private
  496.    */
  497.   OptionsPage.findSectionForNode_ = function(node) {
  498.     while (node = node.parentNode) {
  499.       if (node.nodeName == 'SECTION')
  500.         return node;
  501.     }
  502.     return null;
  503.   };
  504.  
  505.   /**
  506.    * Registers a new Overlay page.
  507.    * @param {OptionsPage} overlay Overlay to register.
  508.    * @param {OptionsPage} parentPage Associated parent page for this overlay.
  509.    * @param {Array} associatedControls Array of control elements associated with
  510.    *   this page.
  511.    */
  512.   OptionsPage.registerOverlay = function(overlay,
  513.                                          parentPage,
  514.                                          associatedControls) {
  515.     this.registeredOverlayPages[overlay.name.toLowerCase()] = overlay;
  516.     overlay.parentPage = parentPage;
  517.     if (associatedControls) {
  518.       overlay.associatedControls = associatedControls;
  519.       if (associatedControls.length) {
  520.         overlay.associatedSection =
  521.             this.findSectionForNode_(associatedControls[0]);
  522.       }
  523.  
  524.       // Sanity check.
  525.       for (var i = 0; i < associatedControls.length; ++i) {
  526.         assert(associatedControls[i], 'Invalid element passed.');
  527.       }
  528.     }
  529.  
  530.     // Reverse the button strip for views. See the documentation of
  531.     // reverseButtonStrip_() for an explanation of why this is necessary.
  532.     if (cr.isViews)
  533.       this.reverseButtonStrip_(overlay);
  534.  
  535.     overlay.tab = undefined;
  536.     overlay.isOverlay = true;
  537.     overlay.initializePage();
  538.   };
  539.  
  540.   /**
  541.    * Reverses the child elements of a button strip. This is necessary because
  542.    * WebKit does not alter the tab order for elements that are visually reversed
  543.    * using -webkit-box-direction: reverse, and the button order is reversed for
  544.    * views.  See https://bugs.webkit.org/show_bug.cgi?id=62664 for more
  545.    * information.
  546.    * @param {Object} overlay The overlay containing the button strip to reverse.
  547.    * @private
  548.    */
  549.   OptionsPage.reverseButtonStrip_ = function(overlay) {
  550.     var buttonStrips = overlay.pageDiv.querySelectorAll('.button-strip');
  551.  
  552.     // Reverse all button-strips in the overlay.
  553.     for (var j = 0; j < buttonStrips.length; j++) {
  554.       var buttonStrip = buttonStrips[j];
  555.  
  556.       var childNodes = buttonStrip.childNodes;
  557.       for (var i = childNodes.length - 1; i >= 0; i--)
  558.         buttonStrip.appendChild(childNodes[i]);
  559.     }
  560.   };
  561.  
  562.   /**
  563.    * Callback for window.onpopstate.
  564.    * @param {Object} data State data pushed into history.
  565.    */
  566.   OptionsPage.setState = function(data) {
  567.     if (data && data.pageName) {
  568.       this.willClose();
  569.       this.showPageByName(data.pageName, false);
  570.     }
  571.   };
  572.  
  573.   /**
  574.    * Callback for window.onbeforeunload. Used to notify overlays that they will
  575.    * be closed.
  576.    */
  577.   OptionsPage.willClose = function() {
  578.     var overlay = this.getVisibleOverlay_();
  579.     if (overlay && overlay.didClosePage)
  580.       overlay.didClosePage();
  581.   };
  582.  
  583.   /**
  584.    * Freezes/unfreezes the scroll position of the root page container.
  585.    * @param {boolean} freeze Whether the page should be frozen.
  586.    * @private
  587.    */
  588.   OptionsPage.setRootPageFrozen_ = function(freeze) {
  589.     var container = $('page-container');
  590.     if (container.classList.contains('frozen') == freeze)
  591.       return;
  592.  
  593.     if (freeze) {
  594.       // Lock the width, since auto width computation may change.
  595.       container.style.width = window.getComputedStyle(container).width;
  596.       container.oldScrollTop = document.body.scrollTop;
  597.       container.classList.add('frozen');
  598.       var verticalPosition =
  599.           container.getBoundingClientRect().top - container.oldScrollTop;
  600.       container.style.top = verticalPosition + 'px';
  601.       this.updateFrozenElementHorizontalPosition_(container);
  602.     } else {
  603.       container.classList.remove('frozen');
  604.       container.style.top = '';
  605.       container.style.left = '';
  606.       container.style.right = '';
  607.       container.style.width = '';
  608.     }
  609.   };
  610.  
  611.   /**
  612.    * Freezes/unfreezes the scroll position of the root page based on the current
  613.    * page stack.
  614.    */
  615.   OptionsPage.updateRootPageFreezeState = function() {
  616.     var topPage = OptionsPage.getTopmostVisiblePage();
  617.     if (topPage)
  618.       this.setRootPageFrozen_(topPage.isOverlay);
  619.   };
  620.  
  621.   /**
  622.    * Initializes the complete options page.  This will cause all C++ handlers to
  623.    * be invoked to do final setup.
  624.    */
  625.   OptionsPage.initialize = function() {
  626.     chrome.send('coreOptionsInitialize');
  627.     uber.onContentFrameLoaded();
  628.  
  629.     document.addEventListener('scroll', this.handleScroll_.bind(this));
  630.  
  631.     // Trigger the scroll handler manually to set the initial state.
  632.     this.handleScroll_();
  633.  
  634.     // Shake the dialog if the user clicks outside the dialog bounds.
  635.     var containers = [$('overlay-container-1'), $('overlay-container-2')];
  636.     for (var i = 0; i < containers.length; i++) {
  637.       var overlay = containers[i];
  638.       cr.ui.overlay.setupOverlay(overlay);
  639.       overlay.addEventListener('cancelOverlay',
  640.                                OptionsPage.cancelOverlay.bind(OptionsPage));
  641.     }
  642.   };
  643.  
  644.   /**
  645.    * Does a bounds check for the element on the given x, y client coordinates.
  646.    * @param {Element} e The DOM element.
  647.    * @param {number} x The client X to check.
  648.    * @param {number} y The client Y to check.
  649.    * @return {boolean} True if the point falls within the element's bounds.
  650.    * @private
  651.    */
  652.   OptionsPage.elementContainsPoint_ = function(e, x, y) {
  653.     var clientRect = e.getBoundingClientRect();
  654.     return x >= clientRect.left && x <= clientRect.right &&
  655.         y >= clientRect.top && y <= clientRect.bottom;
  656.   };
  657.  
  658.   /**
  659.    * Called when the page is scrolled; moves elements that are position:fixed
  660.    * but should only behave as if they are fixed for vertical scrolling.
  661.    * @private
  662.    */
  663.   OptionsPage.handleScroll_ = function() {
  664.     this.updateAllFrozenElementPositions_();
  665.   };
  666.  
  667.   /**
  668.    * Updates all frozen pages to match the horizontal scroll position.
  669.    * @private
  670.    */
  671.   OptionsPage.updateAllFrozenElementPositions_ = function() {
  672.     var frozenElements = document.querySelectorAll('.frozen');
  673.     for (var i = 0; i < frozenElements.length; i++)
  674.       this.updateFrozenElementHorizontalPosition_(frozenElements[i]);
  675.   };
  676.  
  677.   /**
  678.    * Updates the given frozen element to match the horizontal scroll position.
  679.    * @param {HTMLElement} e The frozen element to update.
  680.    * @private
  681.    */
  682.   OptionsPage.updateFrozenElementHorizontalPosition_ = function(e) {
  683.     if (isRTL()) {
  684.       e.style.right = OptionsPage.horizontalOffset + 'px';
  685.     } else {
  686.       e.style.left = OptionsPage.horizontalOffset -
  687.           document.body.scrollLeft + 'px';
  688.     }
  689.   };
  690.  
  691.   /**
  692.    * Change the horizontal offset used to reposition elements while showing an
  693.    * overlay from the default.
  694.    */
  695.   OptionsPage.setHorizontalOffset = function(value) {
  696.     OptionsPage.horizontalOffset = value;
  697.   };
  698.  
  699.   OptionsPage.setClearPluginLSODataEnabled = function(enabled) {
  700.     if (enabled) {
  701.       document.documentElement.setAttribute(
  702.           'flashPluginSupportsClearSiteData', '');
  703.     } else {
  704.       document.documentElement.removeAttribute(
  705.           'flashPluginSupportsClearSiteData');
  706.     }
  707.   };
  708.  
  709.   OptionsPage.setPepperFlashSettingsEnabled = function(enabled) {
  710.     if (enabled) {
  711.       document.documentElement.setAttribute(
  712.           'enablePepperFlashSettings', '');
  713.     } else {
  714.       document.documentElement.removeAttribute(
  715.           'enablePepperFlashSettings');
  716.     }
  717.   };
  718.  
  719.   OptionsPage.prototype = {
  720.     __proto__: cr.EventTarget.prototype,
  721.  
  722.     /**
  723.      * The parent page of this option page, or null for top-level pages.
  724.      * @type {OptionsPage}
  725.      */
  726.     parentPage: null,
  727.  
  728.     /**
  729.      * The section on the parent page that is associated with this page.
  730.      * Can be null.
  731.      * @type {Element}
  732.      */
  733.     associatedSection: null,
  734.  
  735.     /**
  736.      * An array of controls that are associated with this page.  The first
  737.      * control should be located on a top-level page.
  738.      * @type {OptionsPage}
  739.      */
  740.     associatedControls: null,
  741.  
  742.     /**
  743.      * Initializes page content.
  744.      */
  745.     initializePage: function() {},
  746.  
  747.     /**
  748.      * Sets focus on the first focusable element. Override for a custom focus
  749.      * strategy.
  750.      */
  751.     focus: function() {
  752.       var elements = this.pageDiv.querySelectorAll(
  753.           'input, list, select, textarea, button');
  754.       for (var i = 0; i < elements.length; i++) {
  755.         var element = elements[i];
  756.         // Try to focus. If fails, then continue.
  757.         element.focus();
  758.         if (document.activeElement == element)
  759.           return;
  760.       }
  761.     },
  762.  
  763.     /**
  764.      * Gets the container div for this page if it is an overlay.
  765.      * @type {HTMLElement}
  766.      */
  767.     get container() {
  768.       assert(this.isOverlay);
  769.       return this.pageDiv.parentNode;
  770.     },
  771.  
  772.     /**
  773.      * Gets page visibility state.
  774.      * @type {boolean}
  775.      */
  776.     get visible() {
  777.       // If this is an overlay dialog it is no longer considered visible while
  778.       // the overlay is fading out. See http://crbug.com/118629.
  779.       if (this.isOverlay &&
  780.           this.container.classList.contains('transparent')) {
  781.         return false;
  782.       }
  783.       return !this.pageDiv.hidden;
  784.     },
  785.  
  786.     /**
  787.      * Sets page visibility.
  788.      * @type {boolean}
  789.      */
  790.     set visible(visible) {
  791.       if ((this.visible && visible) || (!this.visible && !visible))
  792.         return;
  793.  
  794.       // If using an overlay, the visibility of the dialog is toggled at the
  795.       // same time as the overlay to show the dialog's out transition. This
  796.       // is handled in setOverlayVisible.
  797.       if (this.isOverlay) {
  798.         this.setOverlayVisible_(visible);
  799.       } else {
  800.         this.pageDiv.hidden = !visible;
  801.         this.onVisibilityChanged_();
  802.       }
  803.  
  804.       cr.dispatchPropertyChange(this, 'visible', visible, !visible);
  805.     },
  806.  
  807.     /**
  808.      * Shows or hides an overlay (including any visible dialog).
  809.      * @param {boolean} visible Whether the overlay should be visible or not.
  810.      * @private
  811.      */
  812.     setOverlayVisible_: function(visible) {
  813.       assert(this.isOverlay);
  814.       var pageDiv = this.pageDiv;
  815.       var container = this.container;
  816.  
  817.       if (visible) {
  818.         uber.invokeMethodOnParent('beginInterceptingEvents');
  819.         this.pageDiv.removeAttribute('aria-hidden');
  820.         if (this.parentPage)
  821.           this.parentPage.pageDiv.setAttribute('aria-hidden', true);
  822.       } else {
  823.         if (this.parentPage)
  824.           this.parentPage.pageDiv.removeAttribute('aria-hidden');
  825.       }
  826.  
  827.       if (container.hidden != visible) {
  828.         if (visible) {
  829.           // If the container is set hidden and then immediately set visible
  830.           // again, the fadeCompleted_ callback would cause it to be erroneously
  831.           // hidden again. Removing the transparent tag avoids that.
  832.           container.classList.remove('transparent');
  833.  
  834.           // Hide all dialogs in this container since a different one may have
  835.           // been previously visible before fading out.
  836.           var pages = container.querySelectorAll('.page');
  837.           for (var i = 0; i < pages.length; i++)
  838.             pages[i].hidden = true;
  839.           // Show the new dialog.
  840.           pageDiv.hidden = false;
  841.         }
  842.         return;
  843.       }
  844.  
  845.       if (visible) {
  846.         container.hidden = false;
  847.         pageDiv.hidden = false;
  848.         // NOTE: This is a hacky way to force the container to layout which
  849.         // will allow us to trigger the webkit transition.
  850.         container.scrollTop;
  851.         container.classList.remove('transparent');
  852.         this.onVisibilityChanged_();
  853.       } else {
  854.         var self = this;
  855.         // TODO: Use an event delegate to avoid having to subscribe and
  856.         // unsubscribe for webkitTransitionEnd events.
  857.         container.addEventListener('webkitTransitionEnd', function f(e) {
  858.           if (e.target != e.currentTarget || e.propertyName != 'opacity')
  859.             return;
  860.           container.removeEventListener('webkitTransitionEnd', f);
  861.           self.fadeCompleted_();
  862.         });
  863.         container.classList.add('transparent');
  864.       }
  865.     },
  866.  
  867.     /**
  868.      * Called when a container opacity transition finishes.
  869.      * @private
  870.      */
  871.     fadeCompleted_: function() {
  872.       if (this.container.classList.contains('transparent')) {
  873.         this.pageDiv.hidden = true;
  874.         this.container.hidden = true;
  875.         this.onVisibilityChanged_();
  876.         if (this.nestingLevel == 1)
  877.           uber.invokeMethodOnParent('stopInterceptingEvents');
  878.       }
  879.     },
  880.  
  881.     /**
  882.      * Called when a page is shown or hidden to update the root options page
  883.      * based on this page's visibility.
  884.      * @private
  885.      */
  886.     onVisibilityChanged_: function() {
  887.       OptionsPage.updateRootPageFreezeState();
  888.  
  889.       if (this.isOverlay && !this.visible)
  890.         OptionsPage.updateScrollPosition_();
  891.     },
  892.  
  893.     /**
  894.      * The nesting level of this page.
  895.      * @type {number} The nesting level of this page (0 for top-level page)
  896.      */
  897.     get nestingLevel() {
  898.       var level = 0;
  899.       var parent = this.parentPage;
  900.       while (parent) {
  901.         level++;
  902.         parent = parent.parentPage;
  903.       }
  904.       return level;
  905.     },
  906.  
  907.     /**
  908.      * Whether the page is considered 'sticky', such that it will
  909.      * remain a top-level page even if sub-pages change.
  910.      * @type {boolean} True if this page is sticky.
  911.      */
  912.     get sticky() {
  913.       return false;
  914.     },
  915.  
  916.     /**
  917.      * Checks whether this page is an ancestor of the given page in terms of
  918.      * subpage nesting.
  919.      * @param {OptionsPage} page The potential descendent of this page.
  920.      * @return {boolean} True if |page| is nested under this page.
  921.      */
  922.     isAncestorOfPage: function(page) {
  923.       var parent = page.parentPage;
  924.       while (parent) {
  925.         if (parent == this)
  926.           return true;
  927.         parent = parent.parentPage;
  928.       }
  929.       return false;
  930.     },
  931.  
  932.     /**
  933.      * Whether it should be possible to show the page.
  934.      * @return {boolean} True if the page should be shown.
  935.      */
  936.     canShowPage: function() {
  937.       return true;
  938.     },
  939.   };
  940.  
  941.   // Export
  942.   return {
  943.     OptionsPage: OptionsPage
  944.   };
  945. });
  946.  
  947. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  948. // Use of this source code is governed by a BSD-style license that can be
  949. // found in the LICENSE file.
  950.  
  951. /**
  952.  * The global object.
  953.  * @type {!Object}
  954.  * @const
  955.  */
  956. var global = this;
  957.  
  958. /**
  959.  * Alias for document.getElementById.
  960.  * @param {string} id The ID of the element to find.
  961.  * @return {HTMLElement} The found element or null if not found.
  962.  */
  963. function $(id) {
  964.   return document.getElementById(id);
  965. }
  966.  
  967. /**
  968.  * Calls chrome.send with a callback and restores the original afterwards.
  969.  * @param {string} name The name of the message to send.
  970.  * @param {!Array} params The parameters to send.
  971.  * @param {string} callbackName The name of the function that the backend calls.
  972.  * @param {!Function} callback The function to call.
  973.  */
  974. function chromeSend(name, params, callbackName, callback) {
  975.   var old = global[callbackName];
  976.   global[callbackName] = function() {
  977.     // restore
  978.     global[callbackName] = old;
  979.  
  980.     var args = Array.prototype.slice.call(arguments);
  981.     return callback.apply(global, args);
  982.   };
  983.   chrome.send(name, params);
  984. }
  985.  
  986. /**
  987.  * Generates a CSS url string.
  988.  * @param {string} s The URL to generate the CSS url for.
  989.  * @return {string} The CSS url string.
  990.  */
  991. function url(s) {
  992.   // http://www.w3.org/TR/css3-values/#uris
  993.   // Parentheses, commas, whitespace characters, single quotes (') and double
  994.   // quotes (") appearing in a URI must be escaped with a backslash
  995.   var s2 = s.replace(/(\(|\)|\,|\s|\'|\"|\\)/g, '\\$1');
  996.   // WebKit has a bug when it comes to URLs that end with \
  997.   // https://bugs.webkit.org/show_bug.cgi?id=28885
  998.   if (/\\\\$/.test(s2)) {
  999.     // Add a space to work around the WebKit bug.
  1000.     s2 += ' ';
  1001.   }
  1002.   return 'url("' + s2 + '")';
  1003. }
  1004.  
  1005. /**
  1006.  * Parses query parameters from Location.
  1007.  * @param {string} location The URL to generate the CSS url for.
  1008.  * @return {object} Dictionary containing name value pairs for URL
  1009.  */
  1010. function parseQueryParams(location) {
  1011.   var params = {};
  1012.   var query = unescape(location.search.substring(1));
  1013.   var vars = query.split('&');
  1014.   for (var i = 0; i < vars.length; i++) {
  1015.     var pair = vars[i].split('=');
  1016.     params[pair[0]] = pair[1];
  1017.   }
  1018.   return params;
  1019. }
  1020.  
  1021. function findAncestorByClass(el, className) {
  1022.   return findAncestor(el, function(el) {
  1023.     if (el.classList)
  1024.       return el.classList.contains(className);
  1025.     return null;
  1026.   });
  1027. }
  1028.  
  1029. /**
  1030.  * Return the first ancestor for which the {@code predicate} returns true.
  1031.  * @param {Node} node The node to check.
  1032.  * @param {function(Node) : boolean} predicate The function that tests the
  1033.  *     nodes.
  1034.  * @return {Node} The found ancestor or null if not found.
  1035.  */
  1036. function findAncestor(node, predicate) {
  1037.   var last = false;
  1038.   while (node != null && !(last = predicate(node))) {
  1039.     node = node.parentNode;
  1040.   }
  1041.   return last ? node : null;
  1042. }
  1043.  
  1044. function swapDomNodes(a, b) {
  1045.   var afterA = a.nextSibling;
  1046.   if (afterA == b) {
  1047.     swapDomNodes(b, a);
  1048.     return;
  1049.   }
  1050.   var aParent = a.parentNode;
  1051.   b.parentNode.replaceChild(a, b);
  1052.   aParent.insertBefore(b, afterA);
  1053. }
  1054.  
  1055. /**
  1056.  * Disables text selection and dragging, with optional whitelist callbacks.
  1057.  * @param {function(Event):boolean=} opt_allowSelectStart Unless this function
  1058.  *    is defined and returns true, the onselectionstart event will be
  1059.  *    surpressed.
  1060.  * @param {function(Event):boolean=} opt_allowDragStart Unless this function
  1061.  *    is defined and returns true, the ondragstart event will be surpressed.
  1062.  */
  1063. function disableTextSelectAndDrag(opt_allowSelectStart, opt_allowDragStart) {
  1064.   // Disable text selection.
  1065.   document.onselectstart = function(e) {
  1066.     if (!(opt_allowSelectStart && opt_allowSelectStart.call(this, e)))
  1067.       e.preventDefault();
  1068.   };
  1069.  
  1070.   // Disable dragging.
  1071.   document.ondragstart = function(e) {
  1072.     if (!(opt_allowDragStart && opt_allowDragStart.call(this, e)))
  1073.       e.preventDefault();
  1074.   };
  1075. }
  1076.  
  1077. /**
  1078.  * Call this to stop clicks on <a href="#"> links from scrolling to the top of
  1079.  * the page (and possibly showing a # in the link).
  1080.  */
  1081. function preventDefaultOnPoundLinkClicks() {
  1082.   document.addEventListener('click', function(e) {
  1083.     var anchor = findAncestor(e.target, function(el) {
  1084.       return el.tagName == 'A';
  1085.     });
  1086.     // Use getAttribute() to prevent URL normalization.
  1087.     if (anchor && anchor.getAttribute('href') == '#')
  1088.       e.preventDefault();
  1089.   });
  1090. }
  1091.  
  1092. /**
  1093.  * Check the directionality of the page.
  1094.  * @return {boolean} True if Chrome is running an RTL UI.
  1095.  */
  1096. function isRTL() {
  1097.   return document.documentElement.dir == 'rtl';
  1098. }
  1099.  
  1100. /**
  1101.  * Simple common assertion API
  1102.  * @param {*} condition The condition to test.  Note that this may be used to
  1103.  *     test whether a value is defined or not, and we don't want to force a
  1104.  *     cast to Boolean.
  1105.  * @param {string=} opt_message A message to use in any error.
  1106.  */
  1107. function assert(condition, opt_message) {
  1108.   'use strict';
  1109.   if (!condition) {
  1110.     var msg = 'Assertion failed';
  1111.     if (opt_message)
  1112.       msg = msg + ': ' + opt_message;
  1113.     throw new Error(msg);
  1114.   }
  1115. }
  1116.  
  1117. /**
  1118.  * Get an element that's known to exist by its ID. We use this instead of just
  1119.  * calling getElementById and not checking the result because this lets us
  1120.  * satisfy the JSCompiler type system.
  1121.  * @param {string} id The identifier name.
  1122.  * @return {!Element} the Element.
  1123.  */
  1124. function getRequiredElement(id) {
  1125.   var element = $(id);
  1126.   assert(element, 'Missing required element: ' + id);
  1127.   return element;
  1128. }
  1129.  
  1130. // Handle click on a link. If the link points to a chrome: or file: url, then
  1131. // call into the browser to do the navigation.
  1132. document.addEventListener('click', function(e) {
  1133.   // Allow preventDefault to work.
  1134.   if (!e.returnValue)
  1135.     return;
  1136.  
  1137.   var el = e.target;
  1138.   if (el.nodeType == Node.ELEMENT_NODE &&
  1139.       el.webkitMatchesSelector('A, A *')) {
  1140.     while (el.tagName != 'A') {
  1141.       el = el.parentElement;
  1142.     }
  1143.  
  1144.     if ((el.protocol == 'file:' || el.protocol == 'about:') &&
  1145.         (e.button == 0 || e.button == 1)) {
  1146.       chrome.send('navigateToUrl', [
  1147.         el.href,
  1148.         el.target,
  1149.         e.button,
  1150.         e.altKey,
  1151.         e.ctrlKey,
  1152.         e.metaKey,
  1153.         e.shiftKey
  1154.       ]);
  1155.       e.preventDefault();
  1156.     }
  1157.   }
  1158. });
  1159.  
  1160. /**
  1161.  * Creates a new URL which is the old URL with a GET param of key=value.
  1162.  * @param {string} url The base URL. There is not sanity checking on the URL so
  1163.  *     it must be passed in a proper format.
  1164.  * @param {string} key The key of the param.
  1165.  * @param {string} value The value of the param.
  1166.  * @return {string} The new URL.
  1167.  */
  1168. function appendParam(url, key, value) {
  1169.   var param = encodeURIComponent(key) + '=' + encodeURIComponent(value);
  1170.  
  1171.   if (url.indexOf('?') == -1)
  1172.     return url + '?' + param;
  1173.   return url + '&' + param;
  1174. }
  1175.  
  1176. /**
  1177.  * Creates a new URL for a favicon request.
  1178.  * @param {string} url The url for the favicon.
  1179.  * @param {number=} opt_size Optional preferred size of the favicon.
  1180.  * @return {string} Updated URL for the favicon.
  1181.  */
  1182. function getFaviconURL(url, opt_size) {
  1183.   var size = opt_size || 16;
  1184.   return 'chrome://favicon/size/' + size + '@' +
  1185.       window.devicePixelRatio + 'x/' + url;
  1186. }
  1187.  
  1188. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  1189. // Use of this source code is governed by a BSD-style license that can be
  1190. // found in the LICENSE file.
  1191.  
  1192. cr.define('options', function() {
  1193.   /** @const */ var OptionsPage = options.OptionsPage;
  1194.  
  1195.   // Variable to track if a captcha challenge was issued. If this gets set to
  1196.   // true, it stays that way until we are told about successful login from
  1197.   // the browser.  This means subsequent errors (like invalid password) are
  1198.   // rendered in the captcha state, which is basically identical except we
  1199.   // don't show the top error blurb 'Error Signing in' or the 'Create
  1200.   // account' link.
  1201.   var captchaChallengeActive_ = false;
  1202.  
  1203.   // When true, the password value may be empty when submitting auth info.
  1204.   // This is true when requesting an access code or when requesting an OTP or
  1205.   // captcha with the oauth sign in flow.
  1206.   var allowEmptyPassword_ = false;
  1207.  
  1208.   // True if the synced account uses a custom passphrase.
  1209.   var usePassphrase_ = false;
  1210.  
  1211.   // True if the synced account uses 'encrypt everything'.
  1212.   var useEncryptEverything_ = false;
  1213.  
  1214.   // True if the support for keystore encryption is enabled. Controls whether
  1215.   // the new unified encryption UI is displayed instead of the old encryption
  1216.   // ui (where passphrase and encrypted types could be set independently of
  1217.   // each other).
  1218.   var keystoreEncryptionEnabled_ = false;
  1219.  
  1220.   // The last email address that this profile was connected to.  If the profile
  1221.   // was never connected this is an empty string.  Otherwise it is a normalized
  1222.   // email address.
  1223.   var lastEmailAddress_ = '';
  1224.  
  1225.   /**
  1226.    * SyncSetupOverlay class
  1227.    * Encapsulated handling of the 'Sync Setup' overlay page.
  1228.    * @class
  1229.    */
  1230.   function SyncSetupOverlay() {
  1231.     OptionsPage.call(this, 'syncSetup',
  1232.                      loadTimeData.getString('syncSetupOverlayTabTitle'),
  1233.                      'sync-setup-overlay');
  1234.   }
  1235.  
  1236.   cr.addSingletonGetter(SyncSetupOverlay);
  1237.  
  1238.   SyncSetupOverlay.prototype = {
  1239.     __proto__: OptionsPage.prototype,
  1240.  
  1241.     /**
  1242.      * Initializes the page.
  1243.      */
  1244.     initializePage: function() {
  1245.       OptionsPage.prototype.initializePage.call(this);
  1246.  
  1247.       var self = this;
  1248.       $('gaia-login-form').onsubmit = function() {
  1249.         self.sendCredentialsAndClose_();
  1250.         return false;
  1251.       };
  1252.       $('google-option').onchange = $('explicit-option').onchange = function() {
  1253.         self.onPassphraseRadioChanged_();
  1254.       };
  1255.       $('basic-encryption-option').onchange =
  1256.           $('full-encryption-option').onchange = function() {
  1257.         self.onEncryptionRadioChanged_();
  1258.       }
  1259.       $('choose-datatypes-cancel').onclick =
  1260.           $('sync-setup-cancel').onclick =
  1261.           $('confirm-everything-cancel').onclick =
  1262.           $('stop-syncing-cancel').onclick =
  1263.           $('sync-spinner-cancel').onclick = function() {
  1264.         self.closeOverlay_();
  1265.       };
  1266.       $('confirm-everything-ok').onclick = function() {
  1267.         self.sendConfiguration_();
  1268.       };
  1269.       $('timeout-ok').onclick = function() {
  1270.         chrome.send('CloseTimeout');
  1271.         self.closeOverlay_();
  1272.       };
  1273.       $('stop-syncing-ok').onclick = function() {
  1274.         chrome.send('SyncSetupStopSyncing');
  1275.         self.closeOverlay_();
  1276.       };
  1277.       $('different-email').innerHTML = loadTimeData.getString('differentEmail');
  1278.     },
  1279.  
  1280.     showOverlay_: function() {
  1281.       OptionsPage.navigateToPage('syncSetup');
  1282.     },
  1283.  
  1284.     closeOverlay_: function() {
  1285.       OptionsPage.closeOverlay();
  1286.     },
  1287.  
  1288.     /** @override */
  1289.     didShowPage: function() {
  1290.       var forceLogin = document.location.hash == '#forceLogin';
  1291.       var result = JSON.stringify({'forceLogin': forceLogin});
  1292.       chrome.send('SyncSetupAttachHandler', [result]);
  1293.     },
  1294.  
  1295.     /** @override */
  1296.     didClosePage: function() {
  1297.       chrome.send('SyncSetupDidClosePage');
  1298.     },
  1299.  
  1300.     getEncryptionRadioCheckedValue_: function() {
  1301.       var f = $('choose-data-types-form');
  1302.       for (var i = 0; i < f.encrypt.length; ++i) {
  1303.         if (f.encrypt[i].checked)
  1304.           return f.encrypt[i].value;
  1305.       }
  1306.  
  1307.       return undefined;
  1308.     },
  1309.  
  1310.     getPassphraseRadioCheckedValue_: function() {
  1311.       var f = $('choose-data-types-form');
  1312.       for (var i = 0; i < f.option.length; ++i) {
  1313.         if (f.option[i].checked) {
  1314.           return f.option[i].value;
  1315.         }
  1316.       }
  1317.  
  1318.       return undefined;
  1319.     },
  1320.  
  1321.     disableEncryptionRadioGroup_: function() {
  1322.       var f = $('choose-data-types-form');
  1323.       for (var i = 0; i < f.encrypt.length; ++i)
  1324.         f.encrypt[i].disabled = true;
  1325.     },
  1326.  
  1327.     onPassphraseRadioChanged_: function() {
  1328.       var visible = this.getPassphraseRadioCheckedValue_() == 'explicit';
  1329.       $('sync-custom-passphrase').hidden = !visible;
  1330.     },
  1331.  
  1332.     onEncryptionRadioChanged_: function() {
  1333.       var visible = $('full-encryption-option').checked;
  1334.       $('sync-custom-passphrase').hidden = !visible;
  1335.     },
  1336.  
  1337.     checkAllDataTypeCheckboxes_: function() {
  1338.       // Only check the visible ones (since there's no way to uncheck
  1339.       // the invisible ones).
  1340.       var checkboxes = $('choose-data-types-body').querySelectorAll(
  1341.           '.sync-type-checkbox:not([hidden]) input');
  1342.       for (var i = 0; i < checkboxes.length; i++) {
  1343.         checkboxes[i].checked = true;
  1344.       }
  1345.     },
  1346.  
  1347.     setDataTypeCheckboxesEnabled_: function(enabled) {
  1348.       var checkboxes = $('choose-data-types-body').querySelectorAll('input');
  1349.       for (var i = 0; i < checkboxes.length; i++) {
  1350.         checkboxes[i].disabled = !enabled;
  1351.       }
  1352.     },
  1353.  
  1354.     setCheckboxesToKeepEverythingSynced_: function(value) {
  1355.       this.setDataTypeCheckboxesEnabled_(!value);
  1356.       if (value)
  1357.         this.checkAllDataTypeCheckboxes_();
  1358.     },
  1359.  
  1360.     // Returns true if none of the visible checkboxes are checked.
  1361.     noDataTypesChecked_: function() {
  1362.       var query = '.sync-type-checkbox:not([hidden]) input:checked';
  1363.       var checkboxes = $('choose-data-types-body').querySelectorAll(query);
  1364.       return checkboxes.length == 0;
  1365.     },
  1366.  
  1367.     checkPassphraseMatch_: function() {
  1368.       var emptyError = $('empty-error');
  1369.       var mismatchError = $('mismatch-error');
  1370.       emptyError.hidden = true;
  1371.       mismatchError.hidden = true;
  1372.  
  1373.       var f = $('choose-data-types-form');
  1374.       if ((this.getPassphraseRadioCheckedValue_() != 'explicit' ||
  1375.            $('google-option').disabled) &&
  1376.           (!$('full-encryption-option').checked ||
  1377.            $('basic-encryption-option').disabled)) {
  1378.         return true;
  1379.       }
  1380.  
  1381.       var customPassphrase = $('custom-passphrase');
  1382.       if (customPassphrase.value.length == 0) {
  1383.         emptyError.hidden = false;
  1384.         return false;
  1385.       }
  1386.  
  1387.       var confirmPassphrase = $('confirm-passphrase');
  1388.       if (confirmPassphrase.value != customPassphrase.value) {
  1389.         mismatchError.hidden = false;
  1390.         return false;
  1391.       }
  1392.  
  1393.       return true;
  1394.     },
  1395.  
  1396.     sendConfiguration_: function() {
  1397.       // Trying to submit, so hide previous errors.
  1398.       $('error-text').hidden = true;
  1399.  
  1400.       var syncAll = $('sync-select-datatypes').selectedIndex == 0;
  1401.       if (!syncAll && this.noDataTypesChecked_()) {
  1402.         $('error-text').hidden = false;
  1403.         return;
  1404.       }
  1405.  
  1406.       var encryptAllData = this.getEncryptionRadioCheckedValue_() == 'all';
  1407.       if (!encryptAllData &&
  1408.           $('full-encryption-option').checked &&
  1409.           this.keystoreEncryptionEnabled_) {
  1410.         encryptAllData = true;
  1411.       }
  1412.  
  1413.       var usePassphrase;
  1414.       var customPassphrase;
  1415.       var googlePassphrase = false;
  1416.       if (!$('sync-existing-passphrase-container').hidden) {
  1417.         // If we were prompted for an existing passphrase, use it.
  1418.         customPassphrase = $('choose-data-types-form').passphrase.value;
  1419.         usePassphrase = true;
  1420.         // If we were displaying the 'enter your old google password' prompt,
  1421.         // then that means this is the user's google password.
  1422.         googlePassphrase = !$('google-passphrase-needed-body').hidden;
  1423.         // We allow an empty passphrase, in case the user has disabled
  1424.         // all their encrypted datatypes. In that case, the PSS will accept
  1425.         // the passphrase and finish configuration. If the user has enabled
  1426.         // encrypted datatypes, the PSS will prompt again specifying that the
  1427.         // passphrase failed.
  1428.       } else if ((!$('google-option').disabled &&
  1429.                   this.getPassphraseRadioCheckedValue_() == 'explicit') ||
  1430.                  (!$('basic-encryption-option').disabled &&
  1431.                   $('full-encryption-option').checked)) {
  1432.         // The user is setting a custom passphrase for the first time.
  1433.         if (!this.checkPassphraseMatch_())
  1434.           return;
  1435.         customPassphrase = $('custom-passphrase').value;
  1436.         usePassphrase = true;
  1437.       } else {
  1438.         // The user is not setting a custom passphrase.
  1439.         usePassphrase = false;
  1440.       }
  1441.  
  1442.       // Don't allow the user to tweak the settings once we send the
  1443.       // configuration to the backend.
  1444.       this.setInputElementsDisabledState_(true);
  1445.       this.animateDisableLink_($('use-default-link'), true, null);
  1446.  
  1447.       // These values need to be kept in sync with where they are read in
  1448.       // SyncSetupFlow::GetDataTypeChoiceData().
  1449.       var result = JSON.stringify({
  1450.         'syncAllDataTypes': syncAll,
  1451.         'bookmarksSynced': syncAll || $('bookmarks-checkbox').checked,
  1452.         'preferencesSynced': syncAll || $('preferences-checkbox').checked,
  1453.         'themesSynced': syncAll || $('themes-checkbox').checked,
  1454.         'passwordsSynced': syncAll || $('passwords-checkbox').checked,
  1455.         'autofillSynced': syncAll || $('autofill-checkbox').checked,
  1456.         'extensionsSynced': syncAll || $('extensions-checkbox').checked,
  1457.         'typedUrlsSynced': syncAll || $('typed-urls-checkbox').checked,
  1458.         'appsSynced': syncAll || $('apps-checkbox').checked,
  1459.         'sessionsSynced': syncAll || $('sessions-checkbox').checked,
  1460.         'encryptAllData': encryptAllData,
  1461.         'usePassphrase': usePassphrase,
  1462.         'isGooglePassphrase': googlePassphrase,
  1463.         'passphrase': customPassphrase
  1464.       });
  1465.       chrome.send('SyncSetupConfigure', [result]);
  1466.     },
  1467.  
  1468.     /**
  1469.      * Sets the disabled property of all input elements within the 'Customize
  1470.      * Sync Preferences' screen. This is used to prohibit the user from changing
  1471.      * the inputs after confirming the customized sync preferences, or resetting
  1472.      * the state when re-showing the dialog.
  1473.      * @param {boolean} disabled True if controls should be set to disabled.
  1474.      * @private
  1475.      */
  1476.     setInputElementsDisabledState_: function(disabled) {
  1477.       var configureElements =
  1478.           $('customize-sync-preferences').querySelectorAll('input');
  1479.       for (var i = 0; i < configureElements.length; i++)
  1480.         configureElements[i].disabled = disabled;
  1481.       $('sync-select-datatypes').disabled = disabled;
  1482.  
  1483.       var self = this;
  1484.       this.animateDisableLink_($('customize-link'), disabled, function() {
  1485.         self.showCustomizePage_(null, true);
  1486.       });
  1487.     },
  1488.  
  1489.     /**
  1490.      * Animate a link being enabled/disabled. The link is hidden by animating
  1491.      * its opacity, but to ensure the user doesn't click it during that time,
  1492.      * its onclick handler is changed to null as well.
  1493.      * @param {HTMLElement} elt The anchor element to enable/disable.
  1494.      * @param {boolean} disabled True if the link should be disabled.
  1495.      * @param {function} enabledFunction The onclick handler when the link is
  1496.      *     enabled.
  1497.      * @private
  1498.      */
  1499.     animateDisableLink_: function(elt, disabled, enabledFunction) {
  1500.       if (disabled) {
  1501.         elt.classList.add('transparent');
  1502.         elt.onclick = null;
  1503.         elt.addEventListener('webkitTransitionEnd', function f(e) {
  1504.           if (e.propertyName != 'opacity')
  1505.             return;
  1506.           elt.removeEventListener('webkitTransitionEnd', f);
  1507.           elt.classList.remove('transparent');
  1508.           elt.hidden = true;
  1509.         });
  1510.       } else {
  1511.         elt.hidden = false;
  1512.         elt.onclick = enabledFunction;
  1513.       }
  1514.     },
  1515.  
  1516.     /**
  1517.      * Shows or hides the Sync data type checkboxes in the advanced
  1518.      * configuration screen.
  1519.      * @param {Object} args The configuration data used to show/hide UI.
  1520.      * @private
  1521.      */
  1522.     setChooseDataTypesCheckboxes_: function(args) {
  1523.       var datatypeSelect = $('sync-select-datatypes');
  1524.       datatypeSelect.selectedIndex = args.syncAllDataTypes ? 0 : 1;
  1525.  
  1526.       $('bookmarks-checkbox').checked = args.bookmarksSynced;
  1527.       $('preferences-checkbox').checked = args.preferencesSynced;
  1528.       $('themes-checkbox').checked = args.themesSynced;
  1529.  
  1530.       if (args.passwordsRegistered) {
  1531.         $('passwords-checkbox').checked = args.passwordsSynced;
  1532.         $('passwords-item').hidden = false;
  1533.       } else {
  1534.         $('passwords-item').hidden = true;
  1535.       }
  1536.       if (args.autofillRegistered) {
  1537.         $('autofill-checkbox').checked = args.autofillSynced;
  1538.         $('autofill-item').hidden = false;
  1539.       } else {
  1540.         $('autofill-item').hidden = true;
  1541.       }
  1542.       if (args.extensionsRegistered) {
  1543.         $('extensions-checkbox').checked = args.extensionsSynced;
  1544.         $('extensions-item').hidden = false;
  1545.       } else {
  1546.         $('extensions-item').hidden = true;
  1547.       }
  1548.       if (args.typedUrlsRegistered) {
  1549.         $('typed-urls-checkbox').checked = args.typedUrlsSynced;
  1550.         $('omnibox-item').hidden = false;
  1551.       } else {
  1552.         $('omnibox-item').hidden = true;
  1553.       }
  1554.       if (args.appsRegistered) {
  1555.         $('apps-checkbox').checked = args.appsSynced;
  1556.         $('apps-item').hidden = false;
  1557.       } else {
  1558.         $('apps-item').hidden = true;
  1559.       }
  1560.       if (args.sessionsRegistered) {
  1561.         $('sessions-checkbox').checked = args.sessionsSynced;
  1562.         $('sessions-item').hidden = false;
  1563.       } else {
  1564.         $('sessions-item').hidden = true;
  1565.       }
  1566.  
  1567.       this.setCheckboxesToKeepEverythingSynced_(args.syncAllDataTypes);
  1568.     },
  1569.  
  1570.     setEncryptionRadios_: function(args) {
  1571.       if (args.encryptAllData) {
  1572.         $('encrypt-all-option').checked = true;
  1573.         this.disableEncryptionRadioGroup_();
  1574.       } else {
  1575.         $('encrypt-sensitive-option').checked = true;
  1576.       }
  1577.  
  1578.       if (!args.encryptAllData && !args.usePassphrase) {
  1579.         $('basic-encryption-option').checked = true;
  1580.       } else {
  1581.         $('full-encryption-option').checked = true;
  1582.         $('full-encryption-option').disabled = true;
  1583.         $('basic-encryption-option').disabled = true;
  1584.       }
  1585.     },
  1586.  
  1587.     setPassphraseRadios_: function(args) {
  1588.       if (args.usePassphrase) {
  1589.         $('explicit-option').checked = true;
  1590.  
  1591.         // The passphrase, once set, cannot be unset, but we show a reset link.
  1592.         $('explicit-option').disabled = true;
  1593.         $('google-option').disabled = true;
  1594.         $('sync-custom-passphrase').hidden = true;
  1595.       } else {
  1596.         $('google-option').checked = true;
  1597.       }
  1598.     },
  1599.  
  1600.     setCheckboxesAndErrors_: function(args) {
  1601.       this.setChooseDataTypesCheckboxes_(args);
  1602.       this.setEncryptionRadios_(args);
  1603.       this.setPassphraseRadios_(args);
  1604.     },
  1605.  
  1606.     showConfigure_: function(args) {
  1607.       var datatypeSelect = $('sync-select-datatypes');
  1608.       var self = this;
  1609.       datatypeSelect.onchange = function() {
  1610.         var syncAll = this.selectedIndex == 0;
  1611.         self.setCheckboxesToKeepEverythingSynced_(syncAll);
  1612.       };
  1613.  
  1614.       this.resetPage_('sync-setup-configure');
  1615.       $('sync-setup-configure').hidden = false;
  1616.  
  1617.       // onsubmit is changed when submitting a passphrase. Reset it to its
  1618.       // default.
  1619.       $('choose-data-types-form').onsubmit = function() {
  1620.         self.sendConfiguration_();
  1621.         return false;
  1622.       };
  1623.  
  1624.       if (args) {
  1625.         this.setCheckboxesAndErrors_(args);
  1626.  
  1627.         this.useEncryptEverything_ = args.encryptAllData;
  1628.  
  1629.         // Whether to display the 'Sync everything' confirmation page or the
  1630.         // customize data types page.
  1631.         var syncAllDataTypes = args.syncAllDataTypes;
  1632.         this.usePassphrase_ = args.usePassphrase;
  1633.         this.keystoreEncryptionEnabled_ = args.keystoreEncryptionEnabled;
  1634.         if (args.showSyncEverythingPage == false || this.usePassphrase_ ||
  1635.             syncAllDataTypes == false || args.showPassphrase) {
  1636.           this.showCustomizePage_(args, syncAllDataTypes);
  1637.         } else {
  1638.           this.showSyncEverythingPage_();
  1639.         }
  1640.       }
  1641.     },
  1642.  
  1643.     showSpinner_: function() {
  1644.       this.resetPage_('sync-setup-spinner');
  1645.       $('sync-setup-spinner').hidden = false;
  1646.     },
  1647.  
  1648.     showTimeoutPage_: function() {
  1649.       this.resetPage_('sync-setup-timeout');
  1650.       $('sync-setup-timeout').hidden = false;
  1651.     },
  1652.  
  1653.     showSyncEverythingPage_: function() {
  1654.       $('confirm-sync-preferences').hidden = false;
  1655.       $('customize-sync-preferences').hidden = true;
  1656.  
  1657.       // Reset the selection to 'Sync everything'.
  1658.       $('sync-select-datatypes').selectedIndex = 0;
  1659.  
  1660.       // The default state is to sync everything.
  1661.       this.setCheckboxesToKeepEverythingSynced_(true);
  1662.  
  1663.       // Encrypt passwords is the default, but don't set it if the previously
  1664.       // synced account is already set to encrypt everything.
  1665.       if (!this.useEncryptEverything_)
  1666.         $('encrypt-sensitive-option').checked = true;
  1667.  
  1668.       // If the account is not synced with a custom passphrase, reset the
  1669.       // passphrase radio when switching to the 'Sync everything' page.
  1670.       if (!this.usePassphrase_) {
  1671.         $('google-option').checked = true;
  1672.         $('sync-custom-passphrase').hidden = true;
  1673.       }
  1674.  
  1675.       if (!this.useEncryptEverything_ && !this.usePassphrase_)
  1676.         $('basic-encryption-option').checked = true;
  1677.  
  1678.       $('confirm-everything-ok').focus();
  1679.     },
  1680.  
  1681.     /**
  1682.      * Reveals the UI for entering a custom passphrase during initial setup.
  1683.      * This happens if the user has previously enabled a custom passphrase on a
  1684.      * different machine.
  1685.      * @param {Array} args The args that contain the passphrase UI
  1686.      *     configuration.
  1687.      * @private
  1688.      */
  1689.     showPassphraseContainer_: function(args) {
  1690.       // Once we require a passphrase, we prevent the user from returning to
  1691.       // the Sync Everything pane.
  1692.       $('use-default-link').hidden = true;
  1693.       $('sync-custom-passphrase-container').hidden = true;
  1694.       $('sync-existing-passphrase-container').hidden = false;
  1695.  
  1696.       // Hide the selection options within the new encryption section when
  1697.       // prompting for a passphrase.
  1698.       $('sync-new-encryption-section-container').hidden = true;
  1699.  
  1700.       $('normal-body').hidden = true;
  1701.       $('google-passphrase-needed-body').hidden = true;
  1702.       // Display the correct prompt to the user depending on what type of
  1703.       // passphrase is needed.
  1704.       if (args.usePassphrase)
  1705.         $('normal-body').hidden = false;
  1706.       else
  1707.         $('google-passphrase-needed-body').hidden = false;
  1708.  
  1709.       $('passphrase-learn-more').hidden = false;
  1710.       // Warn the user about their incorrect passphrase if we need a passphrase
  1711.       // and the passphrase field is non-empty (meaning they tried to set it
  1712.       // previously but failed).
  1713.       $('incorrect-passphrase').hidden =
  1714.           !(args.usePassphrase && args.passphraseFailed);
  1715.  
  1716.       $('sync-passphrase-warning').hidden = false;
  1717.       $('passphrase').focus();
  1718.     },
  1719.  
  1720.     /** @private */
  1721.     showCustomizePage_: function(args, syncEverything) {
  1722.       $('confirm-sync-preferences').hidden = true;
  1723.       $('customize-sync-preferences').hidden = false;
  1724.  
  1725.       $('sync-custom-passphrase-container').hidden = false;
  1726.  
  1727.       if (this.keystoreEncryptionEnabled_) {
  1728.         $('customize-sync-encryption').hidden = true;
  1729.         $('sync-custom-passphrase-options').hidden = true;
  1730.         $('sync-new-encryption-section-container').hidden = false;
  1731.         $('customize-sync-encryption-new').hidden = false;
  1732.       } else {
  1733.         $('customize-sync-encryption').hidden = false;
  1734.         $('sync-custom-passphrase-options').hidden = false;
  1735.         $('customize-sync-encryption-new').hidden = true;
  1736.       }
  1737.  
  1738.       $('sync-existing-passphrase-container').hidden = true;
  1739.  
  1740.       // If the user has selected the 'Customize' page on initial set up, it's
  1741.       // likely he intends to change the data types. Select the
  1742.       // 'Choose data types' option in this case.
  1743.       var index = syncEverything ? 0 : 1;
  1744.       $('sync-select-datatypes').selectedIndex = index;
  1745.       this.setDataTypeCheckboxesEnabled_(!syncEverything);
  1746.  
  1747.       // The passphrase input may need to take over focus from the OK button, so
  1748.       // set focus before that logic.
  1749.       $('choose-datatypes-ok').focus();
  1750.  
  1751.       if (args && args.showPassphrase) {
  1752.         this.showPassphraseContainer_(args);
  1753.       } else {
  1754.         // We only show the 'Use Default' link if we're not prompting for an
  1755.         // existing passphrase.
  1756.         var self = this;
  1757.         this.animateDisableLink_($('use-default-link'), false, function() {
  1758.           self.showSyncEverythingPage_();
  1759.         });
  1760.       }
  1761.     },
  1762.  
  1763.     /**
  1764.      * Shows the appropriate sync setup page.
  1765.      * @param {string} page A page of the sync setup to show.
  1766.      * @param {object} args Data from the C++ to forward on to the right
  1767.      *     section.
  1768.      */
  1769.     showSyncSetupPage_: function(page, args) {
  1770.       this.setThrobbersVisible_(false);
  1771.  
  1772.       // Hide an existing visible overlay (ensuring the close button is not
  1773.       // hidden).
  1774.       var children = document.querySelectorAll(
  1775.           '#sync-setup-overlay > *:not(.close-button)');
  1776.       for (var i = 0; i < children.length; i++)
  1777.         children[i].hidden = true;
  1778.  
  1779.       this.setInputElementsDisabledState_(false);
  1780.  
  1781.       // If new passphrase bodies are present, overwrite the existing ones.
  1782.       if (args && args.enterPassphraseBody != undefined)
  1783.         $('normal-body').innerHTML = args.enterPassphraseBody;
  1784.       if (args && args.enterGooglePassphraseBody != undefined) {
  1785.         $('google-passphrase-needed-body').innerHTML =
  1786.             args.enterGooglePassphraseBody;
  1787.       }
  1788.       if (args && args.fullEncryptionBody != undefined)
  1789.         $('full-encryption-body').innerHTML = args.fullEncryptionBody;
  1790.  
  1791.       // NOTE: Because both showGaiaLogin_() and showConfigure_() change the
  1792.       // focus, we need to ensure that the overlay container and dialog aren't
  1793.       // [hidden] (as trying to focus() nodes inside of a [hidden] DOM section
  1794.       // doesn't work).
  1795.       if (page == 'done')
  1796.         this.closeOverlay_();
  1797.       else
  1798.         this.showOverlay_();
  1799.  
  1800.       if (page == 'login')
  1801.         this.showGaiaLogin_(args);
  1802.       else if (page == 'configure' || page == 'passphrase')
  1803.         this.showConfigure_(args);
  1804.       else if (page == 'spinner')
  1805.         this.showSpinner_();
  1806.       else if (page == 'timeout')
  1807.         this.showTimeoutPage_();
  1808.     },
  1809.  
  1810.     /**
  1811.      * Changes the visibility of throbbers on this page.
  1812.      * @param {boolean} visible Whether or not to set all throbber nodes
  1813.      *     visible.
  1814.      */
  1815.     setThrobbersVisible_: function(visible) {
  1816.       var throbbers = document.getElementsByClassName('throbber');
  1817.       for (var i = 0; i < throbbers.length; i++)
  1818.         throbbers[i].style.visibility = visible ? 'visible' : 'hidden';
  1819.     },
  1820.  
  1821.     /**
  1822.      * Set the appropriate focus on the GAIA login section of the overlay.
  1823.      * @private
  1824.      */
  1825.     loginSetFocus_: function() {
  1826.       var email = this.getLoginEmail_();
  1827.       if (email && !email.value) {
  1828.         email.focus();
  1829.         return;
  1830.       }
  1831.  
  1832.       var passwd = this.getLoginPasswd_();
  1833.       if (passwd)
  1834.         passwd.focus();
  1835.     },
  1836.  
  1837.     /**
  1838.      * Get the login email text input DOM element.
  1839.      * @return {DOMElement} The login email text input.
  1840.      * @private
  1841.      */
  1842.     getLoginEmail_: function() {
  1843.       return $('gaia-email');
  1844.     },
  1845.  
  1846.     /**
  1847.      * Get the login password text input DOM element.
  1848.      * @return {DOMElement} The login password text input.
  1849.      * @private
  1850.      */
  1851.     getLoginPasswd_: function() {
  1852.       return $('gaia-passwd');
  1853.     },
  1854.  
  1855.     /**
  1856.      * Get the sign in button DOM element.
  1857.      * @return {DOMElement} The sign in button.
  1858.      * @private
  1859.      */
  1860.     getSignInButton_: function() {
  1861.       return $('sign-in');
  1862.     },
  1863.  
  1864.     showAccessCodeRequired_: function() {
  1865.       this.allowEmptyPassword_ = true;
  1866.  
  1867.       $('password-row').hidden = true;
  1868.       $('email-row').hidden = true;
  1869.       $('otp-input-row').hidden = true;
  1870.  
  1871.       $('access-code-input-row').hidden = false;
  1872.       $('access-code').disabled = false;
  1873.       $('access-code').focus();
  1874.     },
  1875.  
  1876.     showOtpRequired_: function() {
  1877.       this.allowEmptyPassword_ = true;
  1878.  
  1879.       $('password-row').hidden = true;
  1880.       $('email-row').hidden = true;
  1881.       $('access-code-input-row').hidden = true;
  1882.  
  1883.       $('otp-input-row').hidden = false;
  1884.       $('otp').disabled = false;
  1885.       $('otp').focus();
  1886.     },
  1887.  
  1888.     showCaptcha_: function(args) {
  1889.       this.allowEmptyPassword_ = args.hideEmailAndPassword;
  1890.       this.captchaChallengeActive_ = true;
  1891.  
  1892.       if (args.hideEmailAndPassword) {
  1893.         $('password-row').hidden = true;
  1894.         $('email-row').hidden = true;
  1895.         $('create-account-div').hidden = true;
  1896.       } else {
  1897.         // The captcha takes up lots of space, so make room.
  1898.         $('top-blurb-error').hidden = true;
  1899.         $('create-account-div').hidden = true;
  1900.         $('gaia-email').disabled = true;
  1901.         $('gaia-passwd').disabled = false;
  1902.       }
  1903.  
  1904.       // It's showtime for the captcha now.
  1905.       $('captcha-div').hidden = false;
  1906.       $('captcha-value').disabled = false;
  1907.       $('captcha-wrapper').style.backgroundImage = url(args.captchaUrl);
  1908.     },
  1909.  
  1910.     /**
  1911.      * Reset the state of all descendant elements of a root element to their
  1912.      * initial state.
  1913.      * The initial state is specified by adding a class to the descendant
  1914.      * element in sync_setup_overlay.html.
  1915.      * @param {HTMLElement} pageElementId The root page element id.
  1916.      * @private
  1917.      */
  1918.     resetPage_: function(pageElementId) {
  1919.       var page = $(pageElementId);
  1920.       var forEach = function(arr, fn) {
  1921.         var length = arr.length;
  1922.         for (var i = 0; i < length; i++) {
  1923.           fn(arr[i]);
  1924.         }
  1925.       };
  1926.  
  1927.       forEach(page.getElementsByClassName('reset-hidden'),
  1928.           function(elt) { elt.hidden = true; });
  1929.       forEach(page.getElementsByClassName('reset-shown'),
  1930.           function(elt) { elt.hidden = false; });
  1931.       forEach(page.getElementsByClassName('reset-disabled'),
  1932.           function(elt) { elt.disabled = true; });
  1933.       forEach(page.getElementsByClassName('reset-enabled'),
  1934.           function(elt) { elt.disabled = false; });
  1935.       forEach(page.getElementsByClassName('reset-value'),
  1936.           function(elt) { elt.value = ''; });
  1937.       forEach(page.getElementsByClassName('reset-opaque'),
  1938.           function(elt) { elt.classList.remove('transparent'); });
  1939.     },
  1940.  
  1941.     showGaiaLogin_: function(args) {
  1942.       var oldAccessCodeValue = $('access-code').value;
  1943.       this.resetPage_('sync-setup-login');
  1944.       $('sync-setup-login').hidden = false;
  1945.       this.allowEmptyPassword_ = false;
  1946.       this.captchaChallengeActive_ = false;
  1947.       this.lastEmailAddress_ = args.lastEmailAddress;
  1948.  
  1949.       var f = $('gaia-login-form');
  1950.       var email = $('gaia-email');
  1951.       var passwd = $('gaia-passwd');
  1952.       if (f) {
  1953.         if (args.user != undefined) {
  1954.           if (email.value != args.user)
  1955.             passwd.value = ''; // Reset the password field
  1956.           email.value = args.user;
  1957.         }
  1958.  
  1959.         if (!args.editableUser) {
  1960.           $('email-row').hidden = true;
  1961.           var span = $('email-readonly');
  1962.           span.textContent = email.value;
  1963.           $('email-readonly-row').hidden = false;
  1964.           $('create-account-div').hidden = true;
  1965.         }
  1966.  
  1967.         f.accessCode.disabled = true;
  1968.         f.otp.disabled = true;
  1969.       }
  1970.  
  1971.       if (1 == args.error) {
  1972.         if (oldAccessCodeValue) {
  1973.           $('errormsg-0-access-code').hidden = false;
  1974.           this.showAccessCodeRequired_();
  1975.         } else {
  1976.           $('errormsg-1-password').hidden = (args.errorMessage != undefined);
  1977.         }
  1978.         this.setBlurbError_(args.errorMessage);
  1979.       } else if (3 == args.error) {
  1980.         $('errormsg-0-connection').hidden = false;
  1981.         this.setBlurbError_(args.errorMessage);
  1982.       } else if (4 == args.error) {
  1983.         this.showCaptcha_(args);
  1984.       } else if (7 == args.error) {
  1985.         this.setBlurbError_(loadTimeData.getString('serviceUnavailableError'));
  1986.       } else if (8 == args.error) {
  1987.         if (args.askForOtp) {
  1988.           this.showOtpRequired_();
  1989.         } else {
  1990.           if (oldAccessCodeValue)
  1991.             $('errormsg-0-access-code').hidden = false;
  1992.           this.showAccessCodeRequired_();
  1993.         }
  1994.       } else if (args.errorMessage) {
  1995.         this.setBlurbError_(args.errorMessage);
  1996.       }
  1997.  
  1998.       if (args.fatalError) {
  1999.         $('errormsg-fatal').hidden = false;
  2000.         $('sign-in').disabled = true;
  2001.         return;
  2002.       }
  2003.  
  2004.       $('sign-in').disabled = false;
  2005.       $('sign-in').value = loadTimeData.getString('signin');
  2006.       this.loginSetFocus_();
  2007.     },
  2008.  
  2009.     resetErrorVisibility_: function() {
  2010.       $('errormsg-0-email').hidden = true;
  2011.       $('errormsg-0-password').hidden = true;
  2012.       $('errormsg-1-password').hidden = true;
  2013.       $('errormsg-different-email').hidden = true;
  2014.       $('errormsg-0-connection').hidden = true;
  2015.       $('errormsg-0-access-code').hidden = true;
  2016.       $('errormsg-0-otp').hidden = true;
  2017.     },
  2018.  
  2019.     setBlurbError_: function(errorMessage) {
  2020.       if (this.captchaChallengeActive_)
  2021.         return;  // No blurb in captcha challenge mode.
  2022.  
  2023.       if (errorMessage) {
  2024.         $('error-signing-in').hidden = true;
  2025.         $('error-custom').hidden = false;
  2026.         $('error-custom').textContent = errorMessage;
  2027.       } else {
  2028.         $('error-signing-in').hidden = false;
  2029.         $('error-custom').hidden = true;
  2030.       }
  2031.  
  2032.       $('top-blurb-error').hidden = false;
  2033.       $('gaia-email').disabled = false;
  2034.       $('gaia-passwd').disabled = false;
  2035.     },
  2036.  
  2037.     matchesASPRegex_: function(toMatch) {
  2038.       var noSpaces = /[a-z]{16}/;
  2039.       var withSpaces = /([a-z]{4}\s){3}[a-z]{4}/;
  2040.       if (toMatch.match(noSpaces) || toMatch.match(withSpaces))
  2041.         return true;
  2042.       return false;
  2043.     },
  2044.  
  2045.     setErrorVisibility_: function() {
  2046.       var errormsgDifferentEmail = $('errormsg-different-email');
  2047.       var isErrormsgDifferentEmailHidden = errormsgDifferentEmail.hidden;
  2048.       this.resetErrorVisibility_();
  2049.       var f = $('gaia-login-form');
  2050.       var email = $('gaia-email');
  2051.       var passwd = $('gaia-passwd');
  2052.       if (!email.value) {
  2053.         $('errormsg-0-email').hidden = false;
  2054.         this.setBlurbError_();
  2055.         return false;
  2056.       }
  2057.       // If email is different from last email, and we have not already warned
  2058.       // the user, tell them now.  Otherwise proceed as usual. When comparing
  2059.       // email ids, use @gmail.com as the domain if not provided.
  2060.       function normalized_email(id) {
  2061.         return ((id.indexOf('@') != -1) ? id : id + '@gmail.com');
  2062.       }
  2063.       if (this.lastEmailAddress_.length > 0 &&
  2064.           normalized_email(email.value) !=
  2065.               normalized_email(this.lastEmailAddress_) &&
  2066.           isErrormsgDifferentEmailHidden) {
  2067.         errormsgDifferentEmail.hidden = false;
  2068.         return false;
  2069.       }
  2070.       // Don't enforce password being non-blank when checking access code (it
  2071.       // will have been cleared when the page was displayed).
  2072.       if (!this.allowEmptyPassword_ && !passwd.value) {
  2073.         $('errormsg-0-password').hidden = false;
  2074.         this.setBlurbError_();
  2075.         return false;
  2076.       }
  2077.  
  2078.       if (!f.accessCode.disabled && !f.accessCode.value) {
  2079.         $('errormsg-0-access-code').hidden = false;
  2080.         return false;
  2081.       }
  2082.  
  2083.       if (f.accessCode.disabled && this.matchesASPRegex_(passwd.value) &&
  2084.           $('asp-warning-div').hidden) {
  2085.         $('asp-warning-div').hidden = false;
  2086.         $('gaia-passwd').value = '';
  2087.         return false;
  2088.       }
  2089.  
  2090.       if (!f.otp.disabled && !f.otp.value) {
  2091.         $('errormsg-0-otp').hidden = false;
  2092.         return false;
  2093.       }
  2094.  
  2095.       return true;
  2096.     },
  2097.  
  2098.     sendCredentialsAndClose_: function() {
  2099.       if (!this.setErrorVisibility_()) {
  2100.         return false;
  2101.       }
  2102.  
  2103.       $('gaia-email').disabled = true;
  2104.       $('gaia-passwd').disabled = true;
  2105.       $('captcha-value').disabled = true;
  2106.       $('access-code').disabled = true;
  2107.       $('otp').disabled = true;
  2108.  
  2109.       this.setThrobbersVisible_(true);
  2110.  
  2111.       var f = $('gaia-login-form');
  2112.       var email = $('gaia-email');
  2113.       var passwd = $('gaia-passwd');
  2114.       var result = JSON.stringify({'user': email.value,
  2115.         'pass': passwd.value,
  2116.         'captcha': f.captchaValue.value,
  2117.         'otp': f.otp.value,
  2118.         'accessCode': f.accessCode.value
  2119.       });
  2120.       $('sign-in').disabled = true;
  2121.       chrome.send('SyncSetupSubmitAuth', [result]);
  2122.     },
  2123.  
  2124.     showSuccessAndClose_: function() {
  2125.       $('sign-in').value = loadTimeData.getString('loginSuccess');
  2126.       setTimeout(this.closeOverlay_, 1600);
  2127.     },
  2128.  
  2129.     showSuccessAndSettingUp_: function() {
  2130.       $('sign-in').value = loadTimeData.getString('settingUp');
  2131.       this.setThrobbersVisible_(true);
  2132.       $('top-blurb-error').hidden = true;
  2133.     },
  2134.  
  2135.     /**
  2136.      * Displays the stop syncing dialog.
  2137.      * @private
  2138.      */
  2139.     showStopSyncingUI_: function() {
  2140.       // Hide any visible children of the overlay.
  2141.       var overlay = $('sync-setup-overlay');
  2142.       for (var i = 0; i < overlay.children.length; i++)
  2143.         overlay.children[i].hidden = true;
  2144.  
  2145.       // Bypass OptionsPage.navigateToPage because it will call didShowPage
  2146.       // which will set its own visible page, based on the flow state.
  2147.       this.visible = true;
  2148.  
  2149.       $('sync-setup-stop-syncing').hidden = false;
  2150.       $('stop-syncing-cancel').focus();
  2151.     },
  2152.  
  2153.     /**
  2154.      * Steps into the appropriate Sync Setup error UI.
  2155.      * @private
  2156.      */
  2157.     showErrorUI_: function() {
  2158.       chrome.send('SyncSetupShowErrorUI');
  2159.     },
  2160.  
  2161.     /**
  2162.      * Determines the appropriate page to show in the Sync Setup UI based on
  2163.      * the state of the Sync backend.
  2164.      * @private
  2165.      */
  2166.     showSetupUI_: function() {
  2167.       chrome.send('SyncSetupShowSetupUI');
  2168.     },
  2169.  
  2170.     /**
  2171.      * Shows advanced configuration UI, skipping the login dialog.
  2172.      * @private
  2173.      */
  2174.     showSetupUIWithoutLogin_: function() {
  2175.       chrome.send('SyncSetupShowSetupUIWithoutLogin');
  2176.     },
  2177.  
  2178.     /**
  2179.      * Forces user to sign out of Chrome for Chrome OS.
  2180.      * @private
  2181.      */
  2182.     doSignOutOnAuthError_: function() {
  2183.       chrome.send('SyncSetupDoSignOutOnAuthError');
  2184.     },
  2185.  
  2186.     /**
  2187.      * Hides the outer elements of the login UI. This is used by the sync promo
  2188.      * to customize the look of the login box.
  2189.      */
  2190.     hideOuterLoginUI_: function() {
  2191.       $('sync-setup-overlay-title').hidden = true;
  2192.       $('sync-setup-cancel').hidden = true;
  2193.     }
  2194.   };
  2195.  
  2196.   // These get methods should only be called by the WebUI tests.
  2197.   SyncSetupOverlay.getLoginEmail = function() {
  2198.     return SyncSetupOverlay.getInstance().getLoginEmail_();
  2199.   };
  2200.  
  2201.   SyncSetupOverlay.getLoginPasswd = function() {
  2202.     return SyncSetupOverlay.getInstance().getLoginPasswd_();
  2203.   };
  2204.  
  2205.   SyncSetupOverlay.getSignInButton = function() {
  2206.     return SyncSetupOverlay.getInstance().getSignInButton_();
  2207.   };
  2208.  
  2209.   // These methods are for general consumption.
  2210.   SyncSetupOverlay.showErrorUI = function() {
  2211.     SyncSetupOverlay.getInstance().showErrorUI_();
  2212.   };
  2213.  
  2214.   SyncSetupOverlay.showSetupUI = function() {
  2215.     SyncSetupOverlay.getInstance().showSetupUI_();
  2216.   };
  2217.  
  2218.   SyncSetupOverlay.showSetupUIWithoutLogin = function() {
  2219.     SyncSetupOverlay.getInstance().showSetupUIWithoutLogin_();
  2220.   };
  2221.  
  2222.   SyncSetupOverlay.doSignOutOnAuthError = function() {
  2223.     SyncSetupOverlay.getInstance().doSignOutOnAuthError_();
  2224.   };
  2225.  
  2226.   SyncSetupOverlay.showSyncSetupPage = function(page, args) {
  2227.     SyncSetupOverlay.getInstance().showSyncSetupPage_(page, args);
  2228.   };
  2229.  
  2230.   SyncSetupOverlay.showSuccessAndClose = function() {
  2231.     SyncSetupOverlay.getInstance().showSuccessAndClose_();
  2232.   };
  2233.  
  2234.   SyncSetupOverlay.showSuccessAndSettingUp = function() {
  2235.     SyncSetupOverlay.getInstance().showSuccessAndSettingUp_();
  2236.   };
  2237.  
  2238.   SyncSetupOverlay.showStopSyncingUI = function() {
  2239.     SyncSetupOverlay.getInstance().showStopSyncingUI_();
  2240.   };
  2241.  
  2242.   // Export
  2243.   return {
  2244.     SyncSetupOverlay: SyncSetupOverlay
  2245.   };
  2246. });
  2247.  
  2248.  
  2249. cr.define('sync_promo', function() {
  2250.   /**
  2251.    * SyncPromo class
  2252.    * Subclass of options.SyncSetupOverlay that customizes the sync setup
  2253.    * overlay for use in the new tab page.
  2254.    * @class
  2255.    */
  2256.   function SyncPromo() {
  2257.     options.SyncSetupOverlay.call(this, 'syncSetup',
  2258.         loadTimeData.getString('syncSetupOverlayTabTitle'),
  2259.         'sync-setup-overlay');
  2260.   }
  2261.  
  2262.   // Replicating enum from chrome/common/extensions/extension_constants.h.
  2263.   /** @const */ var actions = (function() {
  2264.     var i = 0;
  2265.     return {
  2266.       VIEWED: i++,
  2267.       LEARN_MORE_CLICKED: i++,
  2268.       ACCOUNT_HELP_CLICKED: i++,
  2269.       CREATE_ACCOUNT_CLICKED: i++,
  2270.       SKIP_CLICKED: i++,
  2271.       SIGN_IN_ATTEMPTED: i++,
  2272.       SIGNED_IN_SUCCESSFULLY: i++,
  2273.       ADVANCED_CLICKED: i++,
  2274.       ENCRYPTION_HELP_CLICKED: i++,
  2275.       CANCELLED_AFTER_SIGN_IN: i++,
  2276.       CONFIRMED_AFTER_SIGN_IN: i++,
  2277.       CLOSED_TAB: i++,
  2278.       CLOSED_WINDOW: i++,
  2279.       LEFT_DURING_THROBBER: i++,
  2280.     };
  2281.   }());
  2282.  
  2283.   cr.addSingletonGetter(SyncPromo);
  2284.  
  2285.   SyncPromo.prototype = {
  2286.     __proto__: options.SyncSetupOverlay.prototype,
  2287.  
  2288.     showOverlay_: function() {
  2289.       $('sync-setup-overlay').hidden = false;
  2290.     },
  2291.  
  2292.     closeOverlay_: function() {
  2293.       chrome.send('SyncPromo:Close');
  2294.     },
  2295.  
  2296.     // Initializes the page.
  2297.     initializePage: function() {
  2298.       options.SyncSetupOverlay.prototype.initializePage.call(this);
  2299.  
  2300.       // Hide parts of the login UI and show the promo UI.
  2301.       this.hideOuterLoginUI_();
  2302.       $('promo-skip').hidden = false;
  2303.  
  2304.       chrome.send('SyncPromo:Initialize');
  2305.  
  2306.       var self = this;
  2307.  
  2308.       $('promo-skip-button').addEventListener('click', function() {
  2309.         chrome.send('SyncPromo:UserSkipped');
  2310.         self.closeOverlay_();
  2311.       });
  2312.  
  2313.       var learnMoreClickedAlready = false;
  2314.       $('promo-learn-more').addEventListener('click', function() {
  2315.         if (!learnMoreClickedAlready)
  2316.           chrome.send('SyncPromo:UserFlowAction', [actions.LEARN_MORE_CLICKED]);
  2317.         learnMoreClickedAlready = true;
  2318.       });
  2319.  
  2320.       $('promo-advanced').addEventListener('click', function() {
  2321.         chrome.send('SyncPromo:ShowAdvancedSettings');
  2322.       });
  2323.  
  2324.       var accountHelpClickedAlready = false;
  2325.       $('cannot-access-account-link').addEventListener('click', function() {
  2326.         if (!accountHelpClickedAlready)
  2327.           chrome.send('SyncPromo:UserFlowAction',
  2328.                       [actions.ACCOUNT_HELP_CLICKED]);
  2329.         accountHelpClickedAlready = true;
  2330.       });
  2331.  
  2332.       var createAccountClickedAlready = false;
  2333.       $('create-account-link').addEventListener('click', function() {
  2334.         if (!createAccountClickedAlready)
  2335.           chrome.send('SyncPromo:UserFlowAction',
  2336.                       [actions.CREATE_ACCOUNT_CLICKED]);
  2337.         createAccountClickedAlready = true;
  2338.       });
  2339.  
  2340.       // We listen to the <form>'s submit vs. the <input type="submit"> click so
  2341.       // we also track users that use the keyboard and press enter.
  2342.       var signInAttemptedAlready = false;
  2343.       $('gaia-login-form').addEventListener('submit', function() {
  2344.         ++self.signInAttempts_;
  2345.         if (!signInAttemptedAlready)
  2346.           chrome.send('SyncPromo:UserFlowAction', [actions.SIGN_IN_ATTEMPTED]);
  2347.         signInAttemptedAlready = true;
  2348.       });
  2349.  
  2350.       var encryptionHelpClickedAlready = false;
  2351.       $('encryption-help-link').addEventListener('click', function() {
  2352.         if (!encryptionHelpClickedAlready)
  2353.           chrome.send('SyncPromo:UserFlowAction',
  2354.                       [actions.ENCRYPTION_HELP_CLICKED]);
  2355.         encryptionHelpClickedAlready = true;
  2356.       });
  2357.  
  2358.       var advancedOptionsClickedAlready = false;
  2359.       $('customize-link').addEventListener('click', function() {
  2360.         if (!advancedOptionsClickedAlready)
  2361.           chrome.send('SyncPromo:UserFlowAction', [actions.ADVANCED_CLICKED]);
  2362.         advancedOptionsClickedAlready = true;
  2363.       });
  2364.  
  2365.       // Re-used across both cancel buttons after a successful sign in.
  2366.       var cancelFunc = function() {
  2367.         chrome.send('SyncPromo:UserFlowAction',
  2368.                     [actions.CANCELLED_AFTER_SIGN_IN]);
  2369.       };
  2370.       $('confirm-everything-cancel').addEventListener('click', cancelFunc);
  2371.       $('choose-datatypes-cancel').addEventListener('click', cancelFunc);
  2372.  
  2373.       // Add the source parameter to the document so that it can be used to
  2374.       // selectively show and hide elements using CSS.
  2375.       var params = parseQueryParams(document.location);
  2376.       if (params.source == '0')
  2377.         document.documentElement.setAttribute('isstartup', '');
  2378.     },
  2379.  
  2380.     /**
  2381.      * Called when the page is unloading.
  2382.      * @private
  2383.      */
  2384.     onUnload: function() {
  2385.       // Record number of times a user tried to sign in and if they left
  2386.       // while a throbber was running.
  2387.       chrome.send('SyncPromo:RecordSignInAttempts', [this.signInAttempts_]);
  2388.       if (this.throbberStart_)
  2389.         chrome.send('SyncPromo:UserFlowAction', [actions.LEFT_DURING_THROBBER]);
  2390.       chrome.send('SyncSetupDidClosePage');
  2391.     },
  2392.  
  2393.     /** @override */
  2394.     sendConfiguration_: function() {
  2395.       chrome.send('SyncPromo:UserFlowAction',
  2396.                   [actions.CONFIRMED_AFTER_SIGN_IN]);
  2397.       options.SyncSetupOverlay.prototype.sendConfiguration_.apply(this,
  2398.           arguments);
  2399.     },
  2400.  
  2401.     /** @override */
  2402.     setThrobbersVisible_: function(visible) {
  2403.       if (visible) {
  2404.         this.throbberStart_ = Date.now();
  2405.       } else {
  2406.         if (this.throbberStart_) {
  2407.           chrome.send('SyncPromo:RecordThrobberTime',
  2408.                       [Date.now() - this.throbberStart_]);
  2409.         }
  2410.         this.throbberStart_ = 0;
  2411.       }
  2412.       // Pass through to SyncSetupOverlay to handle display logic.
  2413.       options.SyncSetupOverlay.prototype.setThrobbersVisible_.apply(
  2414.           this, arguments);
  2415.     },
  2416.  
  2417.     /**
  2418.      * Number of times a user attempted to sign in to GAIA during this page
  2419.      * view.
  2420.      * @private
  2421.      */
  2422.     signInAttempts_: 0,
  2423.  
  2424.     /**
  2425.      * The start time of a throbber on the page.
  2426.      * @private
  2427.      */
  2428.     throbberStart_: 0,
  2429.   };
  2430.  
  2431.   SyncPromo.showErrorUI = function() {
  2432.     SyncPromo.getInstance().showErrorUI_();
  2433.   };
  2434.  
  2435.   SyncPromo.showSyncSetupPage = function(page, args) {
  2436.     SyncPromo.getInstance().showSyncSetupPage_(page, args);
  2437.   };
  2438.  
  2439.   SyncPromo.showSuccessAndClose = function() {
  2440.     SyncPromo.getInstance().showSuccessAndClose_();
  2441.   };
  2442.  
  2443.   SyncPromo.showSuccessAndSettingUp = function() {
  2444.     chrome.send('SyncPromo:UserFlowAction', [actions.SIGNED_IN_SUCCESSFULLY]);
  2445.     SyncPromo.getInstance().showSuccessAndSettingUp_();
  2446.   };
  2447.  
  2448.   SyncPromo.showStopSyncingUI = function() {
  2449.     SyncPromo.getInstance().showStopSyncingUI_();
  2450.   };
  2451.  
  2452.   SyncPromo.initialize = function() {
  2453.     SyncPromo.getInstance().initializePage();
  2454.   };
  2455.  
  2456.   SyncPromo.onUnload = function() {
  2457.     SyncPromo.getInstance().onUnload();
  2458.   };
  2459.  
  2460.   SyncPromo.populatePromoMessage = function(resName) {
  2461.     SyncPromo.getInstance().populatePromoMessage_(resName);
  2462.   };
  2463.  
  2464.   // Export
  2465.   return {
  2466.     SyncPromo: SyncPromo
  2467.   };
  2468. });
  2469.  
  2470. var OptionsPage = options.OptionsPage;
  2471. var SyncSetupOverlay = sync_promo.SyncPromo;
  2472. window.addEventListener('DOMContentLoaded', sync_promo.SyncPromo.initialize);
  2473. window.addEventListener('unload',
  2474.     sync_promo.SyncPromo.onUnload.bind(sync_promo.SyncPromo));
  2475.