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();
28 this.autosizeDeferred = false;
30 // on* Event handlers.
31 this.eventHandlers = {};
33 // setupEventProperty is normally called in extension_options_events.js to
34 // register events, but the createfailed event is registered here because
35 // the event is fired from here instead of through
36 // extension_options_events.js.
37 this.setupEventProperty('createfailed');
38 new ExtensionOptionsEvents(this, this.viewInstanceId);
40 this.setupNodeProperties();
42 this.parseExtensionAttribute();
44 // Once the browser plugin has been created, the guest view will be created
45 // and attached. See handleBrowserPluginAttributeMutation().
46 this.browserPluginNode = this.createBrowserPluginNode();
47 var shadowRoot = this.extensionoptionsNode.createShadowRoot();
48 shadowRoot.appendChild(this.browserPluginNode);
51 ExtensionOptionsInternal.prototype.attachWindow = function() {
52 return guestViewInternalNatives.AttachGuest(
53 this.internalInstanceId,
56 'autosize': this.extensionoptionsNode.hasAttribute('autosize'),
57 'instanceId': this.viewInstanceId,
58 'maxheight': parseInt(this.maxheight || 0),
59 'maxwidth': parseInt(this.maxwidth || 0),
60 'minheight': parseInt(this.minheight || 0),
61 'minwidth': parseInt(this.minwidth || 0)
65 ExtensionOptionsInternal.prototype.createBrowserPluginNode = function() {
66 var browserPluginNode = new ExtensionOptionsInternal.BrowserPlugin();
67 privates(browserPluginNode).internal = this;
68 return browserPluginNode;
71 ExtensionOptionsInternal.prototype.createGuest = function() {
73 'extensionId': this.extensionId,
75 GuestViewInternal.createGuest(
78 function(guestInstanceId) {
79 if (guestInstanceId == 0) {
80 // Fire a createfailed event here rather than in ExtensionOptionsGuest
81 // because the guest will not be created, and cannot fire an event.
82 this.initCalled = false;
83 var createFailedEvent = new Event('createfailed', { bubbles: true });
84 this.dispatchEvent(createFailedEvent);
86 this.guestInstanceId = guestInstanceId;
92 ExtensionOptionsInternal.prototype.dispatchEvent =
93 function(extensionOptionsEvent) {
94 return this.extensionoptionsNode.dispatchEvent(extensionOptionsEvent);
97 ExtensionOptionsInternal.prototype.handleExtensionOptionsAttributeMutation =
98 function(name, oldValue, newValue) {
99 // We treat null attribute (attribute removed) and the empty string as
101 oldValue = oldValue || '';
102 newValue = newValue || '';
104 if (oldValue === newValue)
107 if (name == 'extension' && !oldValue && newValue) {
108 this.extensionId = newValue;
109 // If the browser plugin is not ready then don't create the guest until
110 // it is ready (in handleBrowserPluginAttributeMutation).
111 if (!this.internalInstanceId)
114 // If a guest view does not exist then create one.
115 if (!this.guestInstanceId) {
119 // TODO(ericzeng): Implement navigation to another guest view if we want
120 // that functionality.
121 } else if (AUTO_SIZE_ATTRIBUTES.hasOwnProperty(name) > -1) {
122 this[name] = newValue;
123 this.resetSizeConstraintsIfInvalid();
125 if (!this.guestInstanceId)
128 GuestViewInternal.setAutoSize(this.guestInstanceId, {
129 'enableAutoSize': this.extensionoptionsNode.hasAttribute('autosize'),
131 'width': parseInt(this.minwidth || 0),
132 'height': parseInt(this.minheight || 0)
135 'width': parseInt(this.maxwidth || 0),
136 'height': parseInt(this.maxheight || 0)
142 ExtensionOptionsInternal.prototype.handleBrowserPluginAttributeMutation =
143 function(name, oldValue, newValue) {
144 if (name == 'internalinstanceid' && !oldValue && !!newValue) {
145 this.internalInstanceId = parseInt(newValue);
146 this.browserPluginNode.removeAttribute('internalinstanceid');
147 if (this.extensionId)
153 ExtensionOptionsInternal.prototype.onSizeChanged =
154 function(newWidth, newHeight, oldWidth, oldHeight) {
155 if (this.autosizeDeferred) {
156 this.deferredAutoSizeState = {
158 newHeight: newHeight,
163 this.resize(newWidth, newHeight, oldWidth, oldHeight);
167 ExtensionOptionsInternal.prototype.parseExtensionAttribute = function() {
168 if (this.extensionoptionsNode.hasAttribute('extension')) {
169 this.extensionId = this.extensionoptionsNode.getAttribute('extension');
175 ExtensionOptionsInternal.prototype.resize =
176 function(newWidth, newHeight, oldWidth, oldHeight) {
177 this.browserPluginNode.style.width = newWidth + 'px';
178 this.browserPluginNode.style.height = newHeight + 'px';
180 // Do not allow the options page's dimensions to shrink so that the options
181 // page has a consistent UI. If the new size is larger than the minimum,
182 // make that the new minimum size.
183 if (newWidth > this.minwidth)
184 this.minwidth = newWidth;
185 if (newHeight > this.minheight)
186 this.minheight = newHeight;
188 GuestViewInternal.setAutoSize(this.guestInstanceId, {
189 'enableAutoSize': this.extensionoptionsNode.hasAttribute('autosize'),
191 'width': parseInt(this.minwidth || 0),
192 'height': parseInt(this.minheight || 0)
195 'width': parseInt(this.maxwidth || 0),
196 'height': parseInt(this.maxheight || 0)
201 // Adds an 'on<event>' property on the view, which can be used to set/unset
203 ExtensionOptionsInternal.prototype.setupEventProperty = function(eventName) {
204 var propertyName = 'on' + eventName.toLowerCase();
205 var extensionoptionsNode = this.extensionoptionsNode;
206 Object.defineProperty(extensionoptionsNode, propertyName, {
208 return this.eventHandlers[propertyName];
210 set: function(value) {
211 if (this.eventHandlers[propertyName])
212 extensionoptionsNode.removeEventListener(
213 eventName, this.eventHandlers[propertyName]);
214 this.eventHandlers[propertyName] = value;
216 extensionoptionsNode.addEventListener(eventName, value);
222 ExtensionOptionsInternal.prototype.setupNodeProperties = function() {
223 utils.forEach(AUTO_SIZE_ATTRIBUTES, function(attributeName) {
224 // Get the size constraints from the <extensionoptions> tag, or use the
225 // defaults if not specified
226 if (this.extensionoptionsNode.hasAttribute(attributeName)) {
227 this[attributeName] =
228 this.extensionoptionsNode.getAttribute(attributeName);
230 this[attributeName] = AUTO_SIZE_ATTRIBUTES[attributeName];
233 Object.defineProperty(this.extensionoptionsNode, attributeName, {
235 return this[attributeName];
237 set: function(value) {
238 this.extensionoptionsNode.setAttribute(attributeName, value);
244 this.resetSizeConstraintsIfInvalid();
246 Object.defineProperty(this.extensionoptionsNode, 'extension', {
248 return this.extensionId;
250 set: function(value) {
251 this.extensionoptionsNode.setAttribute('extension', value);
257 ExtensionOptionsInternal.prototype.resetSizeConstraintsIfInvalid = function () {
258 if (this.minheight > this.maxheight || this.minheight < 0) {
259 this.minheight = AUTO_SIZE_ATTRIBUTES.minheight;
260 this.maxheight = AUTO_SIZE_ATTRIBUTES.maxheight;
262 if (this.minwidth > this.maxwidth || this.minwidth < 0) {
263 this.minwidth = AUTO_SIZE_ATTRIBUTES.minwidth;
264 this.maxwidth = AUTO_SIZE_ATTRIBUTES.maxwidth;
269 * Toggles whether the element should automatically resize to its preferred
270 * size. If set to true, when the element receives new autosize dimensions,
271 * it passes them to the embedder in a sizechanged event, but does not resize
272 * itself to those dimensions until the embedder calls resumeDeferredAutoSize.
273 * This allows the embedder to defer the resizing until it is ready.
274 * When set to false, the element resizes whenever it receives new autosize
277 ExtensionOptionsInternal.prototype.setDeferAutoSize = function(value) {
279 resumeDeferredAutoSize();
280 this.autosizeDeferred = value;
284 * Allows the element to resize to most recent set of autosize dimensions if
285 * autosizing is being deferred.
287 ExtensionOptionsInternal.prototype.resumeDeferredAutoSize = function() {
288 if (this.autosizeDeferred) {
289 this.resize(this.deferredAutoSizeState.newWidth,
290 this.deferredAutoSizeState.newHeight,
291 this.deferredAutoSizeState.oldWidth,
292 this.deferredAutoSizeState.oldHeight);
296 function registerBrowserPluginElement() {
297 var proto = Object.create(HTMLObjectElement.prototype);
299 proto.createdCallback = function() {
300 this.setAttribute('type', 'application/browser-plugin');
301 this.style.width = '100%';
302 this.style.height = '100%';
305 proto.attributeChangedCallback = function(name, oldValue, newValue) {
306 var internal = privates(this).internal;
310 internal.handleBrowserPluginAttributeMutation(name, oldValue, newValue);
313 proto.attachedCallback = function() {
314 // Load the plugin immediately.
315 var unused = this.nonExistentAttribute;
318 ExtensionOptionsInternal.BrowserPlugin =
319 DocumentNatives.RegisterElement('extensionoptionsplugin',
320 {extends: 'object', prototype: proto});
321 delete proto.createdCallback;
322 delete proto.attachedCallback;
323 delete proto.detachedCallback;
324 delete proto.attributeChangedCallback;
327 function registerExtensionOptionsElement() {
328 var proto = Object.create(HTMLElement.prototype);
330 proto.createdCallback = function() {
331 new ExtensionOptionsInternal(this);
334 proto.attributeChangedCallback = function(name, oldValue, newValue) {
335 var internal = privates(this).internal;
338 internal.handleExtensionOptionsAttributeMutation(name, oldValue, newValue);
343 'resumeDeferredAutoSize'
346 // Forward proto.foo* method calls to ExtensionOptionsInternal.foo*.
347 for (var i = 0; methods[i]; ++i) {
348 var createHandler = function(m) {
349 return function(var_args) {
350 var internal = privates(this).internal;
351 return $Function.apply(internal[m], internal, arguments);
354 proto[methods[i]] = createHandler(methods[i]);
357 window.ExtensionOptions =
358 DocumentNatives.RegisterElement('extensionoptions', {prototype: proto});
360 // Delete the callbacks so developers cannot call them and produce unexpected
362 delete proto.createdCallback;
363 delete proto.attachedCallback;
364 delete proto.detachedCallback;
365 delete proto.attributeChangedCallback;
368 var useCapture = true;
369 window.addEventListener('readystatechange', function listener(event) {
370 if (document.readyState == 'loading')
373 registerBrowserPluginElement();
374 registerExtensionOptionsElement();
375 window.removeEventListener(event.type, listener, useCapture);