Reland the ULONG -> SIZE_T change from 317177
[chromium-blink-merge.git] / extensions / renderer / resources / guest_view / guest_view.js
blob8ff4f3c52b30d52bce6d408d72adbc25404e505b
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 // This module implements a wrapper for a guestview that manages its
6 // creation, attaching, and destruction.
8 var EventBindings = require('event_bindings');
9 var GuestViewInternal =
10     require('binding').Binding.create('guestViewInternal').generate();
11 var GuestViewInternalNatives = requireNative('guest_view_internal');
13 // Events.
14 var CreateEvent = function(name) {
15   var eventOpts = {supportsListeners: true, supportsFilters: true};
16   return new EventBindings.Event(name, undefined, eventOpts);
18 var ResizeEvent = CreateEvent('guestViewInternal.onResize');
20 // Possible states.
21 var GUEST_STATE_ATTACHED = 2;
22 var GUEST_STATE_CREATED = 1;
23 var GUEST_STATE_START = 0;
25 // Error messages.
26 var ERROR_MSG_ALREADY_ATTACHED = 'The guest has already been attached.';
27 var ERROR_MSG_ALREADY_CREATED = 'The guest has already been created.';
28 var ERROR_MSG_INVALID_STATE = 'The guest is in an invalid state.';
29 var ERROR_MSG_NOT_ATTACHED = 'The guest is not attached.';
30 var ERROR_MSG_NOT_CREATED = 'The guest has not been created.';
32 // Properties.
33 var PROPERTY_ON_RESIZE = 'onResize';
35 // Contains and hides the internal implementation details of |GuestView|,
36 // including maintaining its state and enforcing the proper usage of its API
37 // fucntions.
38 function GuestViewImpl(guestView, viewType, guestInstanceId) {
39   if (guestInstanceId) {
40     this.id = guestInstanceId;
41     this.state = GUEST_STATE_CREATED;
42   } else {
43     this.id = 0;
44     this.state = GUEST_STATE_START;
45   }
46   this.actionQueue = [];
47   this.contentWindow = null;
48   this.guestView = guestView;
49   this.pendingAction = null;
50   this.viewType = viewType;
51   this.internalInstanceId = 0;
53   this.setupOnResize();
56 // Sets up the onResize property on the GuestView.
57 GuestViewImpl.prototype.setupOnResize = function() {
58   Object.defineProperty(this.guestView, PROPERTY_ON_RESIZE, {
59     get: function() {
60       return this[PROPERTY_ON_RESIZE];
61     }.bind(this),
62     set: function(value) {
63       this[PROPERTY_ON_RESIZE] = value;
64     }.bind(this),
65     enumerable: true
66   });
68   this.callOnResize = function(e) {
69     if (!this[PROPERTY_ON_RESIZE]) {
70       return;
71     }
72     this[PROPERTY_ON_RESIZE](e.oldWidth, e.oldHeight, e.newWidth, e.newHeight);
73   }.bind(this);
76 // Callback wrapper that is used to call the callback of the pending action (if
77 // one exists), and then performs the next action in the queue.
78 GuestViewImpl.prototype.handleCallback = function(callback) {
79   if (callback) {
80     callback();
81   }
82   this.pendingAction = null;
83   this.performNextAction();
86 // Perform the next action in the queue, if one exists.
87 GuestViewImpl.prototype.performNextAction = function() {
88   // Make sure that there is not already an action in progress, and that there
89   // exists a queued action to perform.
90   if (!this.pendingAction && this.actionQueue.length) {
91     this.pendingAction = this.actionQueue.shift();
92     this.pendingAction();
93   }
96 // Check the current state to see if the proposed action is valid. Returns false
97 // if invalid.
98 GuestViewImpl.prototype.checkState = function(action) {
99   // Create an error prefix based on the proposed action.
100   var errorPrefix = 'Error calling ' + action + ': ';
102   // Check that the current state is valid.
103   if (!(this.state >= 0 && this.state <= 2)) {
104     window.console.error(errorPrefix + ERROR_MSG_INVALID_STATE);
105     return false;
106   }
108   // Map of possible errors for each action. For each action, the errors are
109   // listed for states in the order: GUEST_STATE_START, GUEST_STATE_CREATED,
110   // GUEST_STATE_ATTACHED.
111   var errors = {
112     'attach': [ERROR_MSG_NOT_CREATED, null, ERROR_MSG_ALREADY_ATTACHED],
113     'create': [null, ERROR_MSG_ALREADY_CREATED, ERROR_MSG_ALREADY_CREATED],
114     'destroy': [null, null, null],
115     'detach': [ERROR_MSG_NOT_ATTACHED, ERROR_MSG_NOT_ATTACHED, null],
116     'setSize': [ERROR_MSG_NOT_CREATED, null, null]
117   };
119   // Check that the proposed action is a real action.
120   if (errors[action] == undefined) {
121     window.console.error(errorPrefix + ERROR_MSG_INVALID_ACTION);
122     return false;
123   }
125   // Report the error if the proposed action is found to be invalid for the
126   // current state.
127   var error;
128   if (error = errors[action][this.state]) {
129     window.console.error(errorPrefix + error);
130     return false;
131   }
133   return true;
136 // Internal implementation of attach().
137 GuestViewImpl.prototype.attachImpl = function(
138     internalInstanceId, viewInstanceId, attachParams, callback) {
139   // Check the current state.
140   if (!this.checkState('attach')) {
141     this.handleCallback(callback);
142     return;
143   }
145   // Callback wrapper function to store the contentWindow from the attachGuest()
146   // callback, handle potential attaching failure, register an automatic detach,
147   // and advance the queue.
148   var callbackWrapper = function(callback, contentWindow) {
149     this.contentWindow = contentWindow;
151     // Check if attaching failed.
152     if (this.contentWindow === null) {
153       this.state = GUEST_STATE_CREATED;
154     } else {
155       // Detach automatically when the container is destroyed.
156       GuestViewInternalNatives.RegisterDestructionCallback(internalInstanceId,
157                                                            function() {
158         if (this.state == GUEST_STATE_ATTACHED) {
159           this.contentWindow = null;
160           this.internalInstanceId = 0;
161           this.state = GUEST_STATE_CREATED;
162         }
163       }.bind(this));
164     }
166     this.handleCallback(callback);
167   };
169   attachParams['instanceId'] = viewInstanceId;
170   GuestViewInternalNatives.AttachGuest(internalInstanceId,
171                                        this.id,
172                                        attachParams,
173                                        callbackWrapper.bind(this, callback));
175   this.internalInstanceId = internalInstanceId;
176   this.state = GUEST_STATE_ATTACHED;
179 // Internal implementation of create().
180 GuestViewImpl.prototype.createImpl = function(createParams, callback) {
181   // Check the current state.
182   if (!this.checkState('create')) {
183     this.handleCallback(callback);
184     return;
185   }
187   // Callback wrapper function to store the guestInstanceId from the
188   // createGuest() callback, handle potential creation failure, and advance the
189   // queue.
190   var callbackWrapper = function(callback, guestInstanceId) {
191     this.id = guestInstanceId;
193     // Check if creation failed.
194     if (this.id === 0) {
195       this.state = GUEST_STATE_START;
196     }
198     ResizeEvent.addListener(this.callOnResize, {instanceId: this.id});
199     this.handleCallback(callback);
200   };
202   GuestViewInternal.createGuest(this.viewType,
203                                 createParams,
204                                 callbackWrapper.bind(this, callback));
206   this.state = GUEST_STATE_CREATED;
209 // Internal implementation of destroy().
210 GuestViewImpl.prototype.destroyImpl = function(callback) {
211   // Check the current state.
212   if (!this.checkState('destroy')) {
213     this.handleCallback(callback);
214     return;
215   }
217   if (this.state == GUEST_STATE_START) {
218     // destroy() does nothing in this case.
219     this.handleCallback(callback);
220     return;
221   }
223   // If this guest is attached, then detach it first.
224   if (!!this.internalInstanceId) {
225     GuestViewInternalNatives.DetachGuest(this.internalInstanceId);
226   }
228   GuestViewInternal.destroyGuest(this.id,
229                                  this.handleCallback.bind(this, callback));
231   // Reset the state of the destroyed guest;
232   this.contentWindow = null;
233   this.id = 0;
234   this.internalInstanceId = 0;
235   this.state = GUEST_STATE_START;
236   if (ResizeEvent.hasListener(this.callOnResize)) {
237     ResizeEvent.removeListener(this.callOnResize);
238   }
241 // Internal implementation of detach().
242 GuestViewImpl.prototype.detachImpl = function(callback) {
243   // Check the current state.
244   if (!this.checkState('detach')) {
245     this.handleCallback(callback);
246     return;
247   }
249   GuestViewInternalNatives.DetachGuest(
250       this.internalInstanceId,
251       this.handleCallback.bind(this, callback));
253   this.contentWindow = null;
254   this.internalInstanceId = 0;
255   this.state = GUEST_STATE_CREATED;
258 // Internal implementation of setSize().
259 GuestViewImpl.prototype.setSizeImpl = function(sizeParams, callback) {
260   // Check the current state.
261   if (!this.checkState('setSize')) {
262     this.handleCallback(callback);
263     return;
264   }
266   GuestViewInternal.setSize(this.id, sizeParams,
267                             this.handleCallback.bind(this, callback));
270 // The exposed interface to a guestview. Exposes in its API the functions
271 // attach(), create(), destroy(), and getId(). All other implementation details
272 // are hidden.
273 function GuestView(viewType, guestInstanceId) {
274   privates(this).internal = new GuestViewImpl(this, viewType, guestInstanceId);
277 // Attaches the guestview to the container with ID |internalInstanceId|.
278 GuestView.prototype.attach = function(
279     internalInstanceId, viewInstanceId, attachParams, callback) {
280   var internal = privates(this).internal;
281   internal.actionQueue.push(internal.attachImpl.bind(
282       internal, internalInstanceId, viewInstanceId, attachParams, callback));
283   internal.performNextAction();
286 // Creates the guestview.
287 GuestView.prototype.create = function(createParams, callback) {
288   var internal = privates(this).internal;
289   internal.actionQueue.push(internal.createImpl.bind(
290       internal, createParams, callback));
291   internal.performNextAction();
294 // Destroys the guestview. Nothing can be done with the guestview after it has
295 // been destroyed.
296 GuestView.prototype.destroy = function(callback) {
297   var internal = privates(this).internal;
298   internal.actionQueue.push(internal.destroyImpl.bind(internal, callback));
299   internal.performNextAction();
302 // Detaches the guestview from its container.
303 // Note: This is not currently used.
304 GuestView.prototype.detach = function(callback) {
305   var internal = privates(this).internal;
306   internal.actionQueue.push(internal.detachImpl.bind(internal, callback));
307   internal.performNextAction();
310 // Adjusts the guestview's sizing parameters.
311 GuestView.prototype.setSize = function(sizeParams, callback) {
312   var internal = privates(this).internal;
313   internal.actionQueue.push(internal.setSizeImpl.bind(
314       internal, sizeParams, callback));
315   internal.performNextAction();
318 // Returns the contentWindow for this guestview.
319 GuestView.prototype.getContentWindow = function() {
320   var internal = privates(this).internal;
321   return internal.contentWindow;
324 // Returns the ID for this guestview.
325 GuestView.prototype.getId = function() {
326   var internal = privates(this).internal;
327   return internal.id;
330 // Exports
331 exports.GuestView = GuestView;