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() {
28 * The ExtensionOptionsOverlay will show an extension's options page using
29 * an <extensionoptions> element.
32 function ExtensionOptionsOverlay() {}
34 cr
.addSingletonGetter(ExtensionOptionsOverlay
);
36 ExtensionOptionsOverlay
.prototype = {
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.
46 * The id of the extension that this options page display.
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.
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
;
69 setInitialFocus: function() {
70 this.getExtensionOptions_().focus();
77 getExtensionOptions_: function() {
78 return $('extension-options-overlay-guest').querySelector(
83 * Handles a click on the close button.
84 * @param {Event} event The click event.
87 handleDismiss_: function(event
) {
88 this.setVisible_(false);
89 var extensionoptions
= this.getExtensionOptions_();
91 $('extension-options-overlay-guest').removeChild(extensionoptions
);
93 $('extension-options-overlay-icon').removeAttribute('src');
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
105 * @suppress {checkTypes}
106 * TODO(vitalyp): remove the suppression after adding
107 * chrome/renderer/resources/extensions/extension_options.js
110 setExtensionAndShow: function(extensionId
,
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');
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;
148 * Resize the overlay if the <extensionoptions> changes preferred size.
149 * @param {{width: number, height: number}} evt
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();
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
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));
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'}
186 duration
: animationTime
,
190 animation
.onfinish = function(e
) {
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
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';
206 shownCallback
= null;
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
);
227 * Dispatches a 'cancelOverlay' event on the $('overlay') element.
230 cr
.dispatchSimpleEvent($('overlay'), 'cancelOverlay');
234 * Returns extension id that this options page set.
237 getExtensionId: function() {
238 return this.extensionId_
;
242 * Toggles the visibility of the ExtensionOptionsOverlay.
243 * @param {boolean} isVisible Whether the overlay should be visible.
246 setVisible_: function(isVisible
) {
247 this.showOverlay_(isVisible
?
248 /** @type {HTMLDivElement} */($('extension-options-overlay')) :
255 ExtensionOptionsOverlay
: ExtensionOptionsOverlay