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');
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');
21 var GUEST_STATE_ATTACHED = 2;
22 var GUEST_STATE_CREATED = 1;
23 var GUEST_STATE_START = 0;
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.';
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
38 function GuestViewImpl(guestView, viewType, guestInstanceId) {
39 if (guestInstanceId) {
40 this.id = guestInstanceId;
41 this.state = GUEST_STATE_CREATED;
44 this.state = GUEST_STATE_START;
46 this.actionQueue = [];
47 this.contentWindow = null;
48 this.guestView = guestView;
49 this.pendingAction = null;
50 this.viewType = viewType;
51 this.internalInstanceId = 0;
56 // Sets up the onResize property on the GuestView.
57 GuestViewImpl.prototype.setupOnResize = function() {
58 Object.defineProperty(this.guestView, PROPERTY_ON_RESIZE, {
60 return this[PROPERTY_ON_RESIZE];
62 set: function(value) {
63 this[PROPERTY_ON_RESIZE] = value;
68 this.callOnResize = function(e) {
69 if (!this[PROPERTY_ON_RESIZE]) {
72 this[PROPERTY_ON_RESIZE](e.oldWidth, e.oldHeight, e.newWidth, e.newHeight);
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) {
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();
96 // Check the current state to see if the proposed action is valid. Returns false
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);
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.
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]
119 // Check that the proposed action is a real action.
120 if (errors[action] == undefined) {
121 window.console.error(errorPrefix + ERROR_MSG_INVALID_ACTION);
125 // Report the error if the proposed action is found to be invalid for the
128 if (error = errors[action][this.state]) {
129 window.console.error(errorPrefix + error);
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);
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;
155 // Detach automatically when the container is destroyed.
156 GuestViewInternalNatives.RegisterDestructionCallback(internalInstanceId,
158 if (this.state == GUEST_STATE_ATTACHED) {
159 this.contentWindow = null;
160 this.internalInstanceId = 0;
161 this.state = GUEST_STATE_CREATED;
166 this.handleCallback(callback);
169 attachParams['instanceId'] = viewInstanceId;
170 GuestViewInternalNatives.AttachGuest(internalInstanceId,
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);
187 // Callback wrapper function to store the guestInstanceId from the
188 // createGuest() callback, handle potential creation failure, and advance the
190 var callbackWrapper = function(callback, guestInstanceId) {
191 this.id = guestInstanceId;
193 // Check if creation failed.
195 this.state = GUEST_STATE_START;
198 ResizeEvent.addListener(this.callOnResize, {instanceId: this.id});
199 this.handleCallback(callback);
202 GuestViewInternal.createGuest(this.viewType,
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);
217 if (this.state == GUEST_STATE_START) {
218 // destroy() does nothing in this case.
219 this.handleCallback(callback);
223 // If this guest is attached, then detach it first.
224 if (!!this.internalInstanceId) {
225 GuestViewInternalNatives.DetachGuest(this.internalInstanceId);
228 GuestViewInternal.destroyGuest(this.id,
229 this.handleCallback.bind(this, callback));
231 // Reset the state of the destroyed guest;
232 this.contentWindow = null;
234 this.internalInstanceId = 0;
235 this.state = GUEST_STATE_START;
236 if (ResizeEvent.hasListener(this.callOnResize)) {
237 ResizeEvent.removeListener(this.callOnResize);
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);
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);
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
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
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;
331 exports.GuestView = GuestView;