BookmarkManager: Fix 'new folder text field size changes on clicking it' issue.
[chromium-blink-merge.git] / chrome / browser / resources / extensions / extension_options_overlay.js
blob73aac3ed618f819ec22b2a4f844e58e0095cd2e4
1 // Copyright 2014 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.
5 // Returns the width of a scrollbar in logical pixels.
6 function getScrollbarWidth() {
7   // Create nested divs with scrollbars.
8   var outer = document.createElement('div');
9   outer.style.width = '100px';
10   outer.style.overflow = 'scroll';
11   outer.style.visibility = 'hidden';
12   document.body.appendChild(outer);
13   var inner = document.createElement('div');
14   inner.style.width = '101px';
15   outer.appendChild(inner);
17   // The outer div's |clientWidth| and |offsetWidth| differ only by the width of
18   // the vertical scrollbar.
19   var scrollbarWidth = outer.offsetWidth - outer.clientWidth;
20   outer.parentNode.removeChild(outer);
21   return scrollbarWidth;
24 cr.define('extensions', function() {
25   'use strict';
27   /**
28    * The ExtensionOptionsOverlay will show an extension's options page using
29    * an <extensionoptions> element.
30    * @constructor
31    */
32   function ExtensionOptionsOverlay() {}
34   cr.addSingletonGetter(ExtensionOptionsOverlay);
36   ExtensionOptionsOverlay.prototype = {
37     /**
38      * The function that shows the given element in the overlay.
39      * @type {?function(HTMLDivElement)} Function that receives the element to
40      *     show in the overlay.
41      * @private
42      */
43     showOverlay_: null,
45     /**
46      * The id of the extension that this options page display.
47      * @type {string}
48      * @private
49      */
50     extensionId_: '',
52     /**
53      * Initialize the page.
54      * @param {function(HTMLDivElement)} showOverlay The function to show or
55      *     hide the ExtensionOptionsOverlay; this should take a single parameter
56      *     which is either the overlay Div if the overlay should be displayed,
57      *     or null if the overlay should be hidden.
58      */
59     initializePage: function(showOverlay) {
60       var overlay = $('overlay');
62       cr.ui.overlay.setupOverlay(overlay);
63       cr.ui.overlay.globalInitialization();
64       overlay.addEventListener('cancelOverlay', this.handleDismiss_.bind(this));
66       this.showOverlay_ = showOverlay;
67     },
69     setInitialFocus: function() {
70       this.getExtensionOptions_().focus();
71     },
73     /**
74      * @return {?Element}
75      * @private
76      */
77     getExtensionOptions_: function() {
78       return $('extension-options-overlay-guest').querySelector(
79           'extensionoptions');
80     },
82     /**
83      * Handles a click on the close button.
84      * @param {Event} event The click event.
85      * @private
86      */
87     handleDismiss_: function(event) {
88       this.setVisible_(false);
89       var extensionoptions = this.getExtensionOptions_();
90       if (extensionoptions)
91         $('extension-options-overlay-guest').removeChild(extensionoptions);
93       $('extension-options-overlay-icon').removeAttribute('src');
94     },
96     /**
97      * Associate an extension with the overlay and display it.
98      * @param {string} extensionId The id of the extension whose options page
99      *     should be displayed in the overlay.
100      * @param {string} extensionName The name of the extension, which is used
101      *     as the header of the overlay.
102      * @param {string} extensionIcon The URL of the extension's icon.
103      * @param {function():void} shownCallback A function called when
104      *     showing completes.
105      * @suppress {checkTypes}
106      * TODO(vitalyp): remove the suppression after adding
107      * chrome/renderer/resources/extensions/extension_options.js
108      * to dependencies.
109      */
110     setExtensionAndShow: function(extensionId,
111                                   extensionName,
112                                   extensionIcon,
113                                   shownCallback) {
114       var overlay = $('extension-options-overlay');
115       var overlayHeader = $('extension-options-overlay-header');
116       var overlayGuest = $('extension-options-overlay-guest');
117       var overlayStyle = window.getComputedStyle(overlay);
119       $('extension-options-overlay-title').textContent = extensionName;
120       $('extension-options-overlay-icon').src = extensionIcon;
122       this.setVisible_(true);
124       var extensionoptions = new window.ExtensionOptions();
125       extensionoptions.extension = extensionId;
126       this.extensionId_ = extensionId;
128       // The <extensionoptions> content's size needs to be restricted to the
129       // bounds of the overlay window. The overlay gives a minWidth and
130       // maxHeight, but the maxHeight does not include our header height (title
131       // and close button), so we need to subtract that to get the maxHeight
132       // for the extension options.
133       var maxHeight = parseInt(overlayStyle.maxHeight, 10) -
134                       overlayHeader.offsetHeight;
135       var minWidth = parseInt(overlayStyle.minWidth, 10);
137       extensionoptions.onclose = function() {
138         cr.dispatchSimpleEvent($('overlay'), 'cancelOverlay');
139       }.bind(this);
141       // Track the current animation (used to grow/shrink the overlay content),
142       // if any. Preferred size changes can fire more rapidly than the
143       // animation speed, and multiple animations running at the same time has
144       // undesirable effects.
145       var animation = null;
147       /**
148        * Resize the overlay if the <extensionoptions> changes preferred size.
149        * @param {{width: number, height: number}} evt
150        */
151       extensionoptions.onpreferredsizechanged = function(evt) {
152         var oldOverlayWidth = parseInt(overlayStyle.width, 10);
153         var oldOverlayHeight = parseInt(overlayStyle.height, 10);
154         var newOverlayWidth = evt.width;
155         // |evt.height| is just the new overlay guest height, and does not
156         // include the overlay header height, so it needs to be added.
157         var newOverlayHeight = evt.height + overlayHeader.offsetHeight;
159         // Make room for the vertical scrollbar, if there is one.
160         if (newOverlayHeight > maxHeight) {
161           newOverlayWidth += getScrollbarWidth();
162         }
164         // Enforce |minWidth| and |maxHeight|.
165         newOverlayWidth = Math.max(newOverlayWidth, minWidth);
166         newOverlayHeight = Math.min(newOverlayHeight, maxHeight);
168         // animationTime is the amount of time in ms that will be used to resize
169         // the overlay. It is calculated by multiplying the pythagorean distance
170         // between old and the new size (in px) with a constant speed of
171         // 0.25 ms/px.
172         var loading = document.documentElement.classList.contains('loading');
173         var animationTime = loading ? 0 :
174             0.25 * Math.sqrt(Math.pow(newOverlayWidth - oldOverlayWidth, 2) +
175                              Math.pow(newOverlayHeight - oldOverlayHeight, 2));
177         if (animation)
178           animation.cancel();
180         // The header height must be added to the (old and new) preferred
181         // heights to get the full overlay heights.
182         animation = overlay.animate([
183           {width: oldOverlayWidth + 'px', height: oldOverlayHeight + 'px'},
184           {width: newOverlayWidth + 'px', height: newOverlayHeight + 'px'}
185         ], {
186           duration: animationTime,
187           delay: 0
188         });
190         animation.onfinish = function(e) {
191           animation = null;
193           // The <extensionoptions> element is ready to place back in the
194           // overlay. Make sure that it's sized to take up the full width/height
195           // of the overlay.
196           overlayGuest.style.position = '';
197           overlayGuest.style.left = '';
198           overlayGuest.style.width = newOverlayWidth + 'px';
199           // |newOverlayHeight| includes the header height, so it needs to be
200           // subtracted to get the new guest height.
201           overlayGuest.style.height =
202               (newOverlayHeight - overlayHeader.offsetHeight) + 'px';
204           if (shownCallback) {
205             shownCallback();
206             shownCallback = null;
207           }
208         };
209       }.bind(this);
211       // Move the <extensionoptions> off screen until the overlay is ready.
212       overlayGuest.style.position = 'fixed';
213       overlayGuest.style.left = window.outerWidth + 'px';
214       // Begin rendering at the default dimensions. This is also necessary to
215       // cancel any width/height set on a previous render.
216       // TODO(kalman): This causes a visual jag where the overlay guest shows
217       // up briefly. It would be better to render this off-screen first, then
218       // swap it in place. See crbug.com/408274.
219       // This may also solve crbug.com/431001 (width is 0 on initial render).
220       overlayGuest.style.width = '';
221       overlayGuest.style.height = '';
223       overlayGuest.appendChild(extensionoptions);
224     },
226     /**
227      * Dispatches a 'cancelOverlay' event on the $('overlay') element.
228      */
229     close: function() {
230       cr.dispatchSimpleEvent($('overlay'), 'cancelOverlay');
231     },
233     /**
234      * Returns extension id that this options page set.
235      * @return {string}
236      */
237     getExtensionId: function() {
238       return this.extensionId_;
239     },
241     /**
242      * Toggles the visibility of the ExtensionOptionsOverlay.
243      * @param {boolean} isVisible Whether the overlay should be visible.
244      * @private
245      */
246     setVisible_: function(isVisible) {
247       this.showOverlay_(isVisible ?
248           /** @type {HTMLDivElement} */($('extension-options-overlay')) :
249           null);
250     }
251   };
253   // Export
254   return {
255     ExtensionOptionsOverlay: ExtensionOptionsOverlay
256   };