Implemented explicit resizing from guestview.
[chromium-blink-merge.git] / chrome / renderer / resources / extensions / extension_options.js
blob0e4b7a919dce1111bb44397f0aeb52239fefc0f5
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 var DocumentNatives = requireNative('document_natives');
6 var ExtensionOptionsEvents =
7     require('extensionOptionsEvents').ExtensionOptionsEvents;
8 var GuestView = require('guestView').GuestView;
9 var GuestViewContainer = require('guestViewContainer').GuestViewContainer;
10 var GuestViewInternal =
11     require('binding').Binding.create('guestViewInternal').generate();
12 var IdGenerator = requireNative('id_generator');
13 var utils = require('utils');
15 // Mapping of the autosize attribute names to default values
16 var AUTO_SIZE_ATTRIBUTES = {
17   'autosize': 'on',
18   'maxheight': window.innerHeight,
19   'maxwidth': window.innerWidth,
20   'minheight': 32,
21   'minwidth': 32
24 function ExtensionOptionsImpl(extensionoptionsElement) {
25   GuestViewContainer.call(this, extensionoptionsElement, 'extensionoptions');
27   // on* Event handlers.
28   this.eventHandlers = {};
30   // setupEventProperty is normally called in extension_options_events.js to
31   // register events, but the createfailed event is registered here because
32   // the event is fired from here instead of through
33   // extension_options_events.js.
34   this.setupEventProperty('createfailed');
35   new ExtensionOptionsEvents(this, this.viewInstanceId);
37   this.autosizeDeferred = false;
39   this.setupElementProperties();
40   this.parseExtensionAttribute();
43 ExtensionOptionsImpl.prototype.__proto__ = GuestViewContainer.prototype;
45 ExtensionOptionsImpl.VIEW_TYPE = 'ExtensionOptions';
47 // Add extra functionality to |this.element|.
48 ExtensionOptionsImpl.setupElement = function(proto) {
49   var apiMethods = [
50     'setDeferAutoSize',
51     'resumeDeferredAutoSize'
52   ];
54   // Forward proto.foo* method calls to ExtensionOptionsImpl.foo*.
55   GuestViewContainer.forwardApiMethods(proto, apiMethods);
58 ExtensionOptionsImpl.prototype.onElementAttached = function() {
59   this.parseExtensionAttribute();
60   this.createGuest();
63 ExtensionOptionsImpl.prototype.buildAttachParams = function() {
64   var params = {
65     'autosize': this.element.hasAttribute('autosize'),
66     'maxheight': parseInt(this.maxheight || 0),
67     'maxwidth': parseInt(this.maxwidth || 0),
68     'minheight': parseInt(this.minheight || 0),
69     'minwidth': parseInt(this.minwidth || 0)
70   };
71   return params;
74 ExtensionOptionsImpl.prototype.createGuest = function() {
75   if (!this.elementAttached) {
76     return;
77   }
78   var params = {
79     'extensionId': this.extensionId,
80   };
82   this.guest.create(params, function() {
83     if (!this.guest.getId()) {
84       // Fire a createfailed event here rather than in ExtensionOptionsGuest
85       // because the guest will not be created, and cannot fire an event.
86       this.initCalled = false;
87       var createFailedEvent = new Event('createfailed', { bubbles: true });
88       this.dispatchEvent(createFailedEvent);
89     } else {
90       this.attachWindow();
91     }
92   }.bind(this));
95 ExtensionOptionsImpl.prototype.dispatchEvent =
96     function(extensionOptionsEvent) {
97   return this.element.dispatchEvent(extensionOptionsEvent);
100 ExtensionOptionsImpl.prototype.handleAttributeMutation =
101     function(name, oldValue, newValue) {
102   // We treat null attribute (attribute removed) and the empty string as
103   // one case.
104   oldValue = oldValue || '';
105   newValue = newValue || '';
107   if (oldValue === newValue)
108     return;
110   if (name == 'extension' && !oldValue && !!newValue) {
111     this.extensionId = newValue;
112     // If the browser plugin is not ready then don't create the guest until
113     // it is ready (in handleBrowserPluginAttributeMutation).
114     if (!this.internalInstanceId)
115       return;
117     // If a guest view does not exist then create one.
118     if (!this.guest.getId()) {
119       this.createGuest();
120       return;
121     }
122     // TODO(ericzeng): Implement navigation to another guest view if we want
123     // that functionality.
124   } else if (AUTO_SIZE_ATTRIBUTES.hasOwnProperty(name) > -1) {
125     this[name] = newValue;
126     this.resetSizeConstraintsIfInvalid();
128     if (!this.guest.getId())
129       return;
131     this.guest.setSize({
132       'enableAutoSize': this.element.hasAttribute('autosize'),
133       'min': {
134         'width': parseInt(this.minwidth || 0),
135         'height': parseInt(this.minheight || 0)
136       },
137       'max': {
138         'width': parseInt(this.maxwidth || 0),
139         'height': parseInt(this.maxheight || 0)
140       }
141     });
142   }
145 ExtensionOptionsImpl.prototype.onSizeChanged =
146     function(newWidth, newHeight, oldWidth, oldHeight) {
147   if (this.autosizeDeferred) {
148     this.deferredAutoSizeState = {
149       newWidth: newWidth,
150       newHeight: newHeight,
151       oldWidth: oldWidth,
152       oldHeight: oldHeight
153     };
154   } else {
155     this.resize(newWidth, newHeight, oldWidth, oldHeight);
156   }
159 ExtensionOptionsImpl.prototype.parseExtensionAttribute = function() {
160   if (this.element.hasAttribute('extension')) {
161     this.extensionId = this.element.getAttribute('extension');
162     return true;
163   }
164   return false;
167 ExtensionOptionsImpl.prototype.resize =
168     function(newWidth, newHeight, oldWidth, oldHeight) {
169   this.element.style.width = newWidth + 'px';
170   this.element.style.height = newHeight + 'px';
172   // Do not allow the options page's dimensions to shrink. This ensures that the
173   // options page has a consistent UI. If the new size is larger than the
174   // minimum, make that the new minimum size.
175   if (newWidth > this.minwidth)
176     this.minwidth = newWidth;
177   if (newHeight > this.minheight)
178     this.minheight = newHeight;
180   this.guest.setSize({
181     'enableAutoSize': this.element.hasAttribute('autosize'),
182     'min': {
183       'width': parseInt(this.minwidth || 0),
184       'height': parseInt(this.minheight || 0)
185     },
186     'max': {
187       'width': parseInt(this.maxwidth || 0),
188       'height': parseInt(this.maxheight || 0)
189     }
190   });
193 // Adds an 'on<event>' property on the view, which can be used to set/unset
194 // an event handler.
195 ExtensionOptionsImpl.prototype.setupEventProperty = function(eventName) {
196   var propertyName = 'on' + eventName.toLowerCase();
197   var element = this.element;
198   Object.defineProperty(element, propertyName, {
199     get: function() {
200       return this.eventHandlers[propertyName];
201     }.bind(this),
202     set: function(value) {
203       if (this.eventHandlers[propertyName])
204         element.removeEventListener(
205             eventName, this.eventHandlers[propertyName]);
206       this.eventHandlers[propertyName] = value;
207       if (value)
208         element.addEventListener(eventName, value);
209     }.bind(this),
210     enumerable: true
211   });
214 ExtensionOptionsImpl.prototype.setupElementProperties = function() {
215   utils.forEach(AUTO_SIZE_ATTRIBUTES, function(attributeName) {
216     // Get the size constraints from the <extensionoptions> tag, or use the
217     // defaults if not specified
218     if (this.element.hasAttribute(attributeName)) {
219       this[attributeName] =
220           this.element.getAttribute(attributeName);
221     } else {
222       this[attributeName] = AUTO_SIZE_ATTRIBUTES[attributeName];
223     }
225     Object.defineProperty(this.element, attributeName, {
226       get: function() {
227         return this[attributeName];
228       }.bind(this),
229       set: function(value) {
230         this.element.setAttribute(attributeName, value);
231       }.bind(this),
232       enumerable: true
233     });
234   }, this);
236   this.resetSizeConstraintsIfInvalid();
238   Object.defineProperty(this.element, 'extension', {
239     get: function() {
240       return this.extensionId;
241     }.bind(this),
242     set: function(value) {
243       this.element.setAttribute('extension', value);
244     }.bind(this),
245     enumerable: true
246   });
249 ExtensionOptionsImpl.prototype.resetSizeConstraintsIfInvalid = function () {
250   if (this.minheight > this.maxheight || this.minheight < 0) {
251     this.minheight = AUTO_SIZE_ATTRIBUTES.minheight;
252     this.maxheight = AUTO_SIZE_ATTRIBUTES.maxheight;
253   }
254   if (this.minwidth > this.maxwidth || this.minwidth < 0) {
255     this.minwidth = AUTO_SIZE_ATTRIBUTES.minwidth;
256     this.maxwidth = AUTO_SIZE_ATTRIBUTES.maxwidth;
257   }
261  * Toggles whether the element should automatically resize to its preferred
262  * size. If set to true, when the element receives new autosize dimensions,
263  * it passes them to the embedder in a sizechanged event, but does not resize
264  * itself to those dimensions until the embedder calls resumeDeferredAutoSize.
265  * This allows the embedder to defer the resizing until it is ready.
266  * When set to false, the element resizes whenever it receives new autosize
267  * dimensions.
268  */
269 ExtensionOptionsImpl.prototype.setDeferAutoSize = function(value) {
270   if (!value)
271     resumeDeferredAutoSize();
272   this.autosizeDeferred = value;
276  * Allows the element to resize to most recent set of autosize dimensions if
277  * autosizing is being deferred.
278  */
279 ExtensionOptionsImpl.prototype.resumeDeferredAutoSize = function() {
280   if (this.autosizeDeferred) {
281     this.resize(this.deferredAutoSizeState.newWidth,
282                 this.deferredAutoSizeState.newHeight,
283                 this.deferredAutoSizeState.oldWidth,
284                 this.deferredAutoSizeState.oldHeight);
285   }
288 GuestViewContainer.registerElement(ExtensionOptionsImpl);