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