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 = {
17 'maxheight': window.innerHeight,
18 'maxwidth': window.innerWidth,
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,
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)
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) {
78 if (this.guestInstanceId != 0) {
83 'extensionId': this.extensionId,
85 GuestViewInternal.createGuest(
88 function(guestInstanceId) {
89 this.pendingGuestCreation = false;
90 if (guestInstanceId && !this.elementAttached) {
91 GuestViewInternal.destroyGuest(guestInstanceId);
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);
101 this.guestInstanceId = guestInstanceId;
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
118 oldValue = oldValue || '';
119 newValue = newValue || '';
121 if (oldValue === newValue)
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)
131 // If a guest view does not exist then create one.
132 if (!this.guestInstanceId) {
133 this.createGuestIfNecessary();
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)
145 GuestViewInternal.setAutoSize(this.guestInstanceId, {
146 'enableAutoSize': this.extensionoptionsNode.hasAttribute('autosize'),
148 'width': parseInt(this.minwidth || 0),
149 'height': parseInt(this.minheight || 0)
152 'width': parseInt(this.maxwidth || 0),
153 'height': parseInt(this.maxheight || 0)
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();
171 ExtensionOptionsInternal.prototype.onSizeChanged =
172 function(newWidth, newHeight, oldWidth, oldHeight) {
173 if (this.autosizeDeferred) {
174 this.deferredAutoSizeState = {
176 newHeight: newHeight,
181 this.resize(newWidth, newHeight, oldWidth, oldHeight);
185 ExtensionOptionsInternal.prototype.parseExtensionAttribute = function() {
186 if (this.extensionoptionsNode.hasAttribute('extension')) {
187 this.extensionId = this.extensionoptionsNode.getAttribute('extension');
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'),
209 'width': parseInt(this.minwidth || 0),
210 'height': parseInt(this.minheight || 0)
213 'width': parseInt(this.maxwidth || 0),
214 'height': parseInt(this.maxheight || 0)
219 // Adds an 'on<event>' property on the view, which can be used to set/unset
221 ExtensionOptionsInternal.prototype.setupEventProperty = function(eventName) {
222 var propertyName = 'on' + eventName.toLowerCase();
223 var extensionoptionsNode = this.extensionoptionsNode;
224 Object.defineProperty(extensionoptionsNode, propertyName, {
226 return this.eventHandlers[propertyName];
228 set: function(value) {
229 if (this.eventHandlers[propertyName])
230 extensionoptionsNode.removeEventListener(
231 eventName, this.eventHandlers[propertyName]);
232 this.eventHandlers[propertyName] = value;
234 extensionoptionsNode.addEventListener(eventName, value);
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);
248 this[attributeName] = AUTO_SIZE_ATTRIBUTES[attributeName];
251 Object.defineProperty(this.extensionoptionsNode, attributeName, {
253 return this[attributeName];
255 set: function(value) {
256 this.extensionoptionsNode.setAttribute(attributeName, value);
262 this.resetSizeConstraintsIfInvalid();
264 Object.defineProperty(this.extensionoptionsNode, 'extension', {
266 return this.extensionId;
268 set: function(value) {
269 this.extensionoptionsNode.setAttribute('extension', value);
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;
280 if (this.minwidth > this.maxwidth || this.minwidth < 0) {
281 this.minwidth = AUTO_SIZE_ATTRIBUTES.minwidth;
282 this.maxwidth = AUTO_SIZE_ATTRIBUTES.maxwidth;
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
295 ExtensionOptionsInternal.prototype.setDeferAutoSize = function(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.
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);
314 ExtensionOptionsInternal.prototype.reset = function() {
315 if (this.guestInstanceId) {
316 GuestViewInternal.destroyGuest(this.guestInstanceId);
317 this.guestInstanceId = undefined;
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%';
330 proto.attributeChangedCallback = function(name, oldValue, newValue) {
331 var internal = privates(this).internal;
335 internal.handleBrowserPluginAttributeMutation(name, oldValue, newValue);
338 proto.attachedCallback = function() {
339 // Load the plugin immediately.
340 var unused = this.nonExistentAttribute;
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);
359 proto.detachedCallback = function() {
360 var internal = privates(this).internal;
364 internal.elementAttached = false;
368 proto.attributeChangedCallback = function(name, oldValue, newValue) {
369 var internal = privates(this).internal;
373 internal.handleExtensionOptionsAttributeMutation(name, oldValue, newValue);
378 'resumeDeferredAutoSize'
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);
389 proto[methods[i]] = createHandler(methods[i]);
392 window.ExtensionOptions =
393 DocumentNatives.RegisterElement('extensionoptions', {prototype: proto});
395 // Delete the callbacks so developers cannot call them and produce unexpected
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')
408 registerBrowserPluginElement();
409 registerExtensionOptionsElement();
410 window.removeEventListener(event.type, listener, useCapture);