Explicitly add python-numpy dependency to install-build-deps.
[chromium-blink-merge.git] / chrome / renderer / resources / extensions / extension_options.js
blob38ca8456cabcaf6183b1d28631e394669887e0de
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 GuestViewInternal =
9     require('binding').Binding.create('guestViewInternal').generate();
10 var IdGenerator = requireNative('id_generator');
11 var utils = require('utils');
12 var guestViewInternalNatives = requireNative('guest_view_internal');
14 // Mapping of the autosize attribute names to default values
15 var AUTO_SIZE_ATTRIBUTES = {
16   'autosize': 'on',
17   'maxheight': window.innerHeight,
18   'maxwidth': window.innerWidth,
19   'minheight': 32,
20   'minwidth': 32
23 function ExtensionOptionsInternal(extensionoptionsNode) {
24   privates(extensionoptionsNode).internal = this;
25   this.extensionoptionsNode = extensionoptionsNode;
26   this.viewInstanceId = IdGenerator.GetNextId();
27   this.guestInstanceId = 0;
28   this.elementAttached = false;
29   this.pendingGuestCreation = false;
31   this.autosizeDeferred = false;
33   // on* Event handlers.
34   this.eventHandlers = {};
36   // setupEventProperty is normally called in extension_options_events.js to
37   // register events, but the createfailed event is registered here because
38   // the event is fired from here instead of through
39   // extension_options_events.js.
40   this.setupEventProperty('createfailed');
41   new ExtensionOptionsEvents(this, this.viewInstanceId);
43   this.setupNodeProperties();
45   this.parseExtensionAttribute();
47   // Once the browser plugin has been created, the guest view will be created
48   // and attached. See handleBrowserPluginAttributeMutation().
49   this.browserPluginNode = this.createBrowserPluginNode();
50   var shadowRoot = this.extensionoptionsNode.createShadowRoot();
51   shadowRoot.appendChild(this.browserPluginNode);
54 ExtensionOptionsInternal.prototype.attachWindow = function() {
55   return guestViewInternalNatives.AttachGuest(
56       this.internalInstanceId,
57       this.guestInstanceId,
58       {
59         'autosize': this.extensionoptionsNode.hasAttribute('autosize'),
60         'instanceId': this.viewInstanceId,
61         'maxheight': parseInt(this.maxheight || 0),
62         'maxwidth': parseInt(this.maxwidth || 0),
63         'minheight': parseInt(this.minheight || 0),
64         'minwidth': parseInt(this.minwidth || 0)
65       });
68 ExtensionOptionsInternal.prototype.createBrowserPluginNode = function() {
69   var browserPluginNode = new ExtensionOptionsInternal.BrowserPlugin();
70   privates(browserPluginNode).internal = this;
71   return browserPluginNode;
74 ExtensionOptionsInternal.prototype.createGuestIfNecessary = function() {
75   if (!this.elementAttached || this.pendingGuestCreation) {
76     return;
77   }
78   if (this.guestInstanceId != 0) {
79     this.attachWindow();
80     return;
81   }
82   var params = {
83     'extensionId': this.extensionId,
84   };
85   GuestViewInternal.createGuest(
86       'extensionoptions',
87       params,
88       function(guestInstanceId) {
89         this.pendingGuestCreation = false;
90         if (guestInstanceId && !this.elementAttached) {
91           GuestViewInternal.destroyGuest(guestInstanceId);
92           guestInstanceId = 0;
93         }
94         if (guestInstanceId == 0) {
95           // Fire a createfailed event here rather than in ExtensionOptionsGuest
96           // because the guest will not be created, and cannot fire an event.
97           this.initCalled = false;
98           var createFailedEvent = new Event('createfailed', { bubbles: true });
99           this.dispatchEvent(createFailedEvent);
100         } else {
101           this.guestInstanceId = guestInstanceId;
102           this.attachWindow();
103         }
104       }.bind(this)
105   );
106   this.pendingGuestCreation = true;
109 ExtensionOptionsInternal.prototype.dispatchEvent =
110     function(extensionOptionsEvent) {
111   return this.extensionoptionsNode.dispatchEvent(extensionOptionsEvent);
114 ExtensionOptionsInternal.prototype.handleExtensionOptionsAttributeMutation =
115     function(name, oldValue, newValue) {
116   // We treat null attribute (attribute removed) and the empty string as
117   // one case.
118   oldValue = oldValue || '';
119   newValue = newValue || '';
121   if (oldValue === newValue)
122     return;
124   if (name == 'extension' && !oldValue && newValue) {
125     this.extensionId = newValue;
126     // If the browser plugin is not ready then don't create the guest until
127     // it is ready (in handleBrowserPluginAttributeMutation).
128     if (!this.internalInstanceId)
129       return;
131     // If a guest view does not exist then create one.
132     if (!this.guestInstanceId) {
133       this.createGuestIfNecessary();
134       return;
135     }
136     // TODO(ericzeng): Implement navigation to another guest view if we want
137     // that functionality.
138   } else if (AUTO_SIZE_ATTRIBUTES.hasOwnProperty(name) > -1) {
139     this[name] = newValue;
140     this.resetSizeConstraintsIfInvalid();
142     if (!this.guestInstanceId)
143       return;
145     GuestViewInternal.setAutoSize(this.guestInstanceId, {
146       'enableAutoSize': this.extensionoptionsNode.hasAttribute('autosize'),
147       'min': {
148         'width': parseInt(this.minwidth || 0),
149         'height': parseInt(this.minheight || 0)
150       },
151       'max': {
152         'width': parseInt(this.maxwidth || 0),
153         'height': parseInt(this.maxheight || 0)
154       }
155     });
156   }
159 ExtensionOptionsInternal.prototype.handleBrowserPluginAttributeMutation =
160     function(name, oldValue, newValue) {
161   if (name == 'internalinstanceid' && !oldValue && !!newValue) {
162     this.elementAttached = true;
163     this.internalInstanceId = parseInt(newValue);
164     this.browserPluginNode.removeAttribute('internalinstanceid');
165     if (this.extensionId)
166       this.createGuestIfNecessary();
168   }
171 ExtensionOptionsInternal.prototype.onSizeChanged =
172     function(newWidth, newHeight, oldWidth, oldHeight) {
173   if (this.autosizeDeferred) {
174     this.deferredAutoSizeState = {
175       newWidth: newWidth,
176       newHeight: newHeight,
177       oldWidth: oldWidth,
178       oldHeight: oldHeight
179     };
180   } else {
181     this.resize(newWidth, newHeight, oldWidth, oldHeight);
182   }
185 ExtensionOptionsInternal.prototype.parseExtensionAttribute = function() {
186   if (this.extensionoptionsNode.hasAttribute('extension')) {
187     this.extensionId = this.extensionoptionsNode.getAttribute('extension');
188     return true;
189   }
190   return false;
193 ExtensionOptionsInternal.prototype.resize =
194     function(newWidth, newHeight, oldWidth, oldHeight) {
195   this.browserPluginNode.style.width = newWidth + 'px';
196   this.browserPluginNode.style.height = newHeight + 'px';
198   // Do not allow the options page's dimensions to shrink so that the options
199   // page has a consistent UI. If the new size is larger than the minimum,
200   // make that the new minimum size.
201   if (newWidth > this.minwidth)
202     this.minwidth = newWidth;
203   if (newHeight > this.minheight)
204     this.minheight = newHeight;
206   GuestViewInternal.setAutoSize(this.guestInstanceId, {
207     'enableAutoSize': this.extensionoptionsNode.hasAttribute('autosize'),
208     'min': {
209       'width': parseInt(this.minwidth || 0),
210       'height': parseInt(this.minheight || 0)
211     },
212     'max': {
213       'width': parseInt(this.maxwidth || 0),
214       'height': parseInt(this.maxheight || 0)
215     }
216   });
219 // Adds an 'on<event>' property on the view, which can be used to set/unset
220 // an event handler.
221 ExtensionOptionsInternal.prototype.setupEventProperty = function(eventName) {
222   var propertyName = 'on' + eventName.toLowerCase();
223   var extensionoptionsNode = this.extensionoptionsNode;
224   Object.defineProperty(extensionoptionsNode, propertyName, {
225     get: function() {
226       return this.eventHandlers[propertyName];
227     }.bind(this),
228     set: function(value) {
229       if (this.eventHandlers[propertyName])
230         extensionoptionsNode.removeEventListener(
231             eventName, this.eventHandlers[propertyName]);
232       this.eventHandlers[propertyName] = value;
233       if (value)
234         extensionoptionsNode.addEventListener(eventName, value);
235     }.bind(this),
236     enumerable: true
237   });
240 ExtensionOptionsInternal.prototype.setupNodeProperties = function() {
241   utils.forEach(AUTO_SIZE_ATTRIBUTES, function(attributeName) {
242     // Get the size constraints from the <extensionoptions> tag, or use the
243     // defaults if not specified
244     if (this.extensionoptionsNode.hasAttribute(attributeName)) {
245       this[attributeName] =
246           this.extensionoptionsNode.getAttribute(attributeName);
247     } else {
248       this[attributeName] = AUTO_SIZE_ATTRIBUTES[attributeName];
249     }
251     Object.defineProperty(this.extensionoptionsNode, attributeName, {
252       get: function() {
253         return this[attributeName];
254       }.bind(this),
255       set: function(value) {
256         this.extensionoptionsNode.setAttribute(attributeName, value);
257       }.bind(this),
258       enumerable: true
259     });
260   }, this);
262   this.resetSizeConstraintsIfInvalid();
264   Object.defineProperty(this.extensionoptionsNode, 'extension', {
265     get: function() {
266       return this.extensionId;
267     }.bind(this),
268     set: function(value) {
269       this.extensionoptionsNode.setAttribute('extension', value);
270     }.bind(this),
271     enumerable: true
272   });
275 ExtensionOptionsInternal.prototype.resetSizeConstraintsIfInvalid = function () {
276   if (this.minheight > this.maxheight || this.minheight < 0) {
277     this.minheight = AUTO_SIZE_ATTRIBUTES.minheight;
278     this.maxheight = AUTO_SIZE_ATTRIBUTES.maxheight;
279   }
280   if (this.minwidth > this.maxwidth || this.minwidth < 0) {
281     this.minwidth = AUTO_SIZE_ATTRIBUTES.minwidth;
282     this.maxwidth = AUTO_SIZE_ATTRIBUTES.maxwidth;
283   }
287  * Toggles whether the element should automatically resize to its preferred
288  * size. If set to true, when the element receives new autosize dimensions,
289  * it passes them to the embedder in a sizechanged event, but does not resize
290  * itself to those dimensions until the embedder calls resumeDeferredAutoSize.
291  * This allows the embedder to defer the resizing until it is ready.
292  * When set to false, the element resizes whenever it receives new autosize
293  * dimensions.
294  */
295 ExtensionOptionsInternal.prototype.setDeferAutoSize = function(value) {
296   if (!value)
297     resumeDeferredAutoSize();
298   this.autosizeDeferred = value;
302  * Allows the element to resize to most recent set of autosize dimensions if
303  * autosizing is being deferred.
304  */
305 ExtensionOptionsInternal.prototype.resumeDeferredAutoSize = function() {
306   if (this.autosizeDeferred) {
307     this.resize(this.deferredAutoSizeState.newWidth,
308                 this.deferredAutoSizeState.newHeight,
309                 this.deferredAutoSizeState.oldWidth,
310                 this.deferredAutoSizeState.oldHeight);
311   }
314 ExtensionOptionsInternal.prototype.reset = function() {
315   if (this.guestInstanceId) {
316     GuestViewInternal.destroyGuest(this.guestInstanceId);
317     this.guestInstanceId = undefined;
318   }
321 function registerBrowserPluginElement() {
322   var proto = Object.create(HTMLObjectElement.prototype);
324   proto.createdCallback = function() {
325     this.setAttribute('type', 'application/browser-plugin');
326     this.style.width = '100%';
327     this.style.height = '100%';
328   };
330   proto.attributeChangedCallback = function(name, oldValue, newValue) {
331     var internal = privates(this).internal;
332     if (!internal) {
333       return;
334     }
335     internal.handleBrowserPluginAttributeMutation(name, oldValue, newValue);
336   };
338   proto.attachedCallback = function() {
339     // Load the plugin immediately.
340     var unused = this.nonExistentAttribute;
341   };
343   ExtensionOptionsInternal.BrowserPlugin =
344       DocumentNatives.RegisterElement('extensionoptionsplugin',
345                                       {extends: 'object', prototype: proto});
346   delete proto.createdCallback;
347   delete proto.attachedCallback;
348   delete proto.detachedCallback;
349   delete proto.attributeChangedCallback;
352 function registerExtensionOptionsElement() {
353   var proto = Object.create(HTMLElement.prototype);
355   proto.createdCallback = function() {
356     new ExtensionOptionsInternal(this);
357   };
359   proto.detachedCallback = function() {
360     var internal = privates(this).internal;
361     if (!internal) {
362       return;
363     }
364     internal.elementAttached = false;
365     internal.reset();
366   };
368   proto.attributeChangedCallback = function(name, oldValue, newValue) {
369     var internal = privates(this).internal;
370     if (!internal) {
371       return;
372     }
373     internal.handleExtensionOptionsAttributeMutation(name, oldValue, newValue);
374   };
376   var methods = [
377     'setDeferAutoSize',
378     'resumeDeferredAutoSize'
379   ];
381   // Forward proto.foo* method calls to ExtensionOptionsInternal.foo*.
382   for (var i = 0; methods[i]; ++i) {
383     var createHandler = function(m) {
384       return function(var_args) {
385         var internal = privates(this).internal;
386         return $Function.apply(internal[m], internal, arguments);
387       };
388     };
389     proto[methods[i]] = createHandler(methods[i]);
390   }
392   window.ExtensionOptions =
393       DocumentNatives.RegisterElement('extensionoptions', {prototype: proto});
395   // Delete the callbacks so developers cannot call them and produce unexpected
396   // behavior.
397   delete proto.createdCallback;
398   delete proto.attachedCallback;
399   delete proto.detachedCallback;
400   delete proto.attributeChangedCallback;
403 var useCapture = true;
404 window.addEventListener('readystatechange', function listener(event) {
405   if (document.readyState == 'loading')
406     return;
408   registerBrowserPluginElement();
409   registerExtensionOptionsElement();
410   window.removeEventListener(event.type, listener, useCapture);
411 }, useCapture);