Add the ability to code generated prepopulated static nested structs
[chromium-blink-merge.git] / extensions / renderer / resources / guest_view / guest_view.js
blob66d48a2d3ba2c1219cd7af8fa35ef12e8d3faddc
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 CreateEvent = require('guestViewEvents').CreateEvent;
9 var EventBindings = require('event_bindings');
10 var GuestViewInternal =
11     require('binding').Binding.create('guestViewInternal').generate();
12 var GuestViewInternalNatives = requireNative('guest_view_internal');
14 // Events.
15 var ResizeEvent = CreateEvent('guestViewInternal.onResize');
17 // Error messages.
18 var ERROR_MSG_ALREADY_ATTACHED = 'The guest has already been attached.';
19 var ERROR_MSG_ALREADY_CREATED = 'The guest has already been created.';
20 var ERROR_MSG_INVALID_STATE = 'The guest is in an invalid state.';
21 var ERROR_MSG_NOT_ATTACHED = 'The guest is not attached.';
22 var ERROR_MSG_NOT_CREATED = 'The guest has not been created.';
24 // Properties.
25 var PROPERTY_ON_RESIZE = 'onresize';
27 // Contains and hides the internal implementation details of |GuestView|,
28 // including maintaining its state and enforcing the proper usage of its API
29 // fucntions.
30 function GuestViewImpl(guestView, viewType, guestInstanceId) {
31   if (guestInstanceId) {
32     this.id = guestInstanceId;
33     this.state = GuestViewImpl.GuestState.GUEST_STATE_CREATED;
34   } else {
35     this.id = 0;
36     this.state = GuestViewImpl.GuestState.GUEST_STATE_START;
37   }
38   this.actionQueue = [];
39   this.contentWindow = null;
40   this.guestView = guestView;
41   this.pendingAction = null;
42   this.viewType = viewType;
43   this.internalInstanceId = 0;
45   this.setupOnResize();
48 // Possible states.
49 GuestViewImpl.GuestState = {
50   GUEST_STATE_START: 0,
51   GUEST_STATE_CREATED: 1,
52   GUEST_STATE_ATTACHED: 2
55 // Sets up the onResize property on the GuestView.
56 GuestViewImpl.prototype.setupOnResize = function() {
57   $Object.defineProperty(this.guestView, PROPERTY_ON_RESIZE, {
58     get: function() {
59       return this[PROPERTY_ON_RESIZE];
60     }.bind(this),
61     set: function(value) {
62       this[PROPERTY_ON_RESIZE] = value;
63     }.bind(this),
64     enumerable: true
65   });
67   this.callOnResize = function(e) {
68     if (!this[PROPERTY_ON_RESIZE]) {
69       return;
70     }
71     this[PROPERTY_ON_RESIZE](e);
72   }.bind(this);
75 // Callback wrapper that is used to call the callback of the pending action (if
76 // one exists), and then performs the next action in the queue.
77 GuestViewImpl.prototype.handleCallback = function(callback) {
78   if (callback) {
79     callback();
80   }
81   this.pendingAction = null;
82   this.performNextAction();
85 // Perform the next action in the queue, if one exists.
86 GuestViewImpl.prototype.performNextAction = function() {
87   // Make sure that there is not already an action in progress, and that there
88   // exists a queued action to perform.
89   if (!this.pendingAction && this.actionQueue.length) {
90     this.pendingAction = this.actionQueue.shift();
91     this.pendingAction();
92   }
95 // Check the current state to see if the proposed action is valid. Returns false
96 // if invalid.
97 GuestViewImpl.prototype.checkState = function(action) {
98   // Create an error prefix based on the proposed action.
99   var errorPrefix = 'Error calling ' + action + ': ';
101   // Check that the current state is valid.
102   if (!(this.state >= 0 && this.state <= 2)) {
103     window.console.error(errorPrefix + ERROR_MSG_INVALID_STATE);
104     return false;
105   }
107   // Map of possible errors for each action. For each action, the errors are
108   // listed for states in the order: GUEST_STATE_START, GUEST_STATE_CREATED,
109   // GUEST_STATE_ATTACHED.
110   var errors = {
111     'attach': [ERROR_MSG_NOT_CREATED, null, ERROR_MSG_ALREADY_ATTACHED],
112     'create': [null, ERROR_MSG_ALREADY_CREATED, ERROR_MSG_ALREADY_CREATED],
113     'destroy': [null, null, null],
114     'detach': [ERROR_MSG_NOT_ATTACHED, ERROR_MSG_NOT_ATTACHED, null],
115     'setSize': [ERROR_MSG_NOT_CREATED, null, null]
116   };
118   // Check that the proposed action is a real action.
119   if (errors[action] == undefined) {
120     window.console.error(errorPrefix + ERROR_MSG_INVALID_ACTION);
121     return false;
122   }
124   // Report the error if the proposed action is found to be invalid for the
125   // current state.
126   var error;
127   if (error = errors[action][this.state]) {
128     window.console.error(errorPrefix + error);
129     return false;
130   }
132   return true;
135 // Returns a wrapper function for |func| with a weak reference to |this|. This
136 // implementation of weakWrapper() requires a provided |viewInstanceId| since
137 // GuestViewImpl does not store this ID.
138 GuestViewImpl.prototype.weakWrapper = function(func, viewInstanceId) {
139   return function() {
140     var view = GuestViewInternalNatives.GetViewFromID(viewInstanceId);
141     if (view && view.guest) {
142       return $Function.apply(func,
143                              privates(view.guest).internal,
144                              $Array.slice(arguments));
145     }
146   };
149 // Internal implementation of attach().
150 GuestViewImpl.prototype.attachImpl$ = function(
151     internalInstanceId, viewInstanceId, attachParams, callback) {
152   // Check the current state.
153   if (!this.checkState('attach')) {
154     this.handleCallback(callback);
155     return;
156   }
158   // Callback wrapper function to store the contentWindow from the attachGuest()
159   // callback, handle potential attaching failure, register an automatic detach,
160   // and advance the queue.
161   var callbackWrapper = function(callback, contentWindow) {
162     // Check if attaching failed.
163     if (!contentWindow) {
164       this.state = GuestViewImpl.GuestState.GUEST_STATE_CREATED;
165       this.internalInstanceId = 0;
166     } else {
167       // Only update the contentWindow if attaching is successful.
168       this.contentWindow = contentWindow;
169     }
171     this.handleCallback(callback);
172   };
174   attachParams['instanceId'] = viewInstanceId;
175   GuestViewInternalNatives.AttachGuest(internalInstanceId,
176                                        this.id,
177                                        attachParams,
178                                        callbackWrapper.bind(this, callback));
180   this.internalInstanceId = internalInstanceId;
181   this.state = GuestViewImpl.GuestState.GUEST_STATE_ATTACHED;
183   // Detach automatically when the container is destroyed.
184   GuestViewInternalNatives.RegisterDestructionCallback(
185       internalInstanceId, this.weakWrapper(function() {
186     if (this.state != GuestViewImpl.GuestState.GUEST_STATE_ATTACHED ||
187         this.internalInstanceId != internalInstanceId) {
188       return;
189     }
191     this.internalInstanceId = 0;
192     this.state = GuestViewImpl.GuestState.GUEST_STATE_CREATED;
193   }, viewInstanceId));
196 // Internal implementation of create().
197 GuestViewImpl.prototype.createImpl$ = function(createParams, callback) {
198   // Check the current state.
199   if (!this.checkState('create')) {
200     this.handleCallback(callback);
201     return;
202   }
204   // Callback wrapper function to store the guestInstanceId from the
205   // createGuest() callback, handle potential creation failure, and advance the
206   // queue.
207   var callbackWrapper = function(callback, guestInfo) {
208     this.id = guestInfo.id;
209     this.contentWindow =
210         GuestViewInternalNatives.GetContentWindow(guestInfo.contentWindowId);
212     // Check if creation failed.
213     if (this.id === 0) {
214       this.state = GuestViewImpl.GuestState.GUEST_STATE_START;
215       this.contentWindow = null;
216     }
218     ResizeEvent.addListener(this.callOnResize, {instanceId: this.id});
219     this.handleCallback(callback);
220   };
222   this.sendCreateRequest(createParams, callbackWrapper.bind(this, callback));
224   this.state = GuestViewImpl.GuestState.GUEST_STATE_CREATED;
227 GuestViewImpl.prototype.sendCreateRequest = function(
228     createParams, boundCallback) {
229   GuestViewInternal.createGuest(this.viewType, createParams, boundCallback);
232 // Internal implementation of destroy().
233 GuestViewImpl.prototype.destroyImpl = function(callback) {
234   // Check the current state.
235   if (!this.checkState('destroy')) {
236     this.handleCallback(callback);
237     return;
238   }
240   if (this.state == GuestViewImpl.GuestState.GUEST_STATE_START) {
241     // destroy() does nothing in this case.
242     this.handleCallback(callback);
243     return;
244   }
246   // If this guest is attached, then detach it first.
247   if (!!this.internalInstanceId) {
248     GuestViewInternalNatives.DetachGuest(this.internalInstanceId);
249   }
251   GuestViewInternal.destroyGuest(this.id,
252                                  this.handleCallback.bind(this, callback));
254   // Reset the state of the destroyed guest;
255   this.contentWindow = null;
256   this.id = 0;
257   this.internalInstanceId = 0;
258   this.state = GuestViewImpl.GuestState.GUEST_STATE_START;
259   if (ResizeEvent.hasListener(this.callOnResize)) {
260     ResizeEvent.removeListener(this.callOnResize);
261   }
264 // Internal implementation of detach().
265 GuestViewImpl.prototype.detachImpl = function(callback) {
266   // Check the current state.
267   if (!this.checkState('detach')) {
268     this.handleCallback(callback);
269     return;
270   }
272   GuestViewInternalNatives.DetachGuest(
273       this.internalInstanceId,
274       this.handleCallback.bind(this, callback));
276   this.internalInstanceId = 0;
277   this.state = GuestViewImpl.GuestState.GUEST_STATE_CREATED;
280 // Internal implementation of setSize().
281 GuestViewImpl.prototype.setSizeImpl = function(sizeParams, callback) {
282   // Check the current state.
283   if (!this.checkState('setSize')) {
284     this.handleCallback(callback);
285     return;
286   }
288   GuestViewInternal.setSize(this.id, sizeParams,
289                             this.handleCallback.bind(this, callback));
292 // The exposed interface to a guestview. Exposes in its API the functions
293 // attach(), create(), destroy(), and getId(). All other implementation details
294 // are hidden.
295 function GuestView(viewType, guestInstanceId) {
296   privates(this).internal = new GuestViewImpl(this, viewType, guestInstanceId);
299 // Attaches the guestview to the container with ID |internalInstanceId|.
300 GuestView.prototype.attach = function(
301     internalInstanceId, viewInstanceId, attachParams, callback) {
302   var internal = privates(this).internal;
303   internal.actionQueue.push(internal.attachImpl$.bind(
304       internal, internalInstanceId, viewInstanceId, attachParams, callback));
305   internal.performNextAction();
308 // Creates the guestview.
309 GuestView.prototype.create = function(createParams, callback) {
310   var internal = privates(this).internal;
311   internal.actionQueue.push(internal.createImpl$.bind(
312       internal, createParams, callback));
313   internal.performNextAction();
316 // Destroys the guestview. Nothing can be done with the guestview after it has
317 // been destroyed.
318 GuestView.prototype.destroy = function(callback) {
319   var internal = privates(this).internal;
320   internal.actionQueue.push(internal.destroyImpl.bind(internal, callback));
321   internal.performNextAction();
324 // Detaches the guestview from its container.
325 // Note: This is not currently used.
326 GuestView.prototype.detach = function(callback) {
327   var internal = privates(this).internal;
328   internal.actionQueue.push(internal.detachImpl.bind(internal, callback));
329   internal.performNextAction();
332 // Adjusts the guestview's sizing parameters.
333 GuestView.prototype.setSize = function(sizeParams, callback) {
334   var internal = privates(this).internal;
335   internal.actionQueue.push(internal.setSizeImpl.bind(
336       internal, sizeParams, callback));
337   internal.performNextAction();
340 // Returns the contentWindow for this guestview.
341 GuestView.prototype.getContentWindow = function() {
342   var internal = privates(this).internal;
343   return internal.contentWindow;
346 // Returns the ID for this guestview.
347 GuestView.prototype.getId = function() {
348   var internal = privates(this).internal;
349   return internal.id;
352 // Exports
353 exports.GuestView = GuestView;
354 exports.GuestViewImpl = GuestViewImpl;
355 exports.ResizeEvent = ResizeEvent;