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');
15 var ResizeEvent = CreateEvent('guestViewInternal.onResize');
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.';
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
30 function GuestViewImpl(guestView, viewType, guestInstanceId) {
31 if (guestInstanceId) {
32 this.id = guestInstanceId;
33 this.state = GuestViewImpl.GuestState.GUEST_STATE_CREATED;
36 this.state = GuestViewImpl.GuestState.GUEST_STATE_START;
38 this.actionQueue = [];
39 this.contentWindow = null;
40 this.guestView = guestView;
41 this.pendingAction = null;
42 this.viewType = viewType;
43 this.internalInstanceId = 0;
49 GuestViewImpl.GuestState = {
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, {
59 return this[PROPERTY_ON_RESIZE];
61 set: function(value) {
62 this[PROPERTY_ON_RESIZE] = value;
67 this.callOnResize = function(e) {
68 if (!this[PROPERTY_ON_RESIZE]) {
71 this[PROPERTY_ON_RESIZE](e);
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) {
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();
95 // Check the current state to see if the proposed action is valid. Returns false
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);
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.
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]
118 // Check that the proposed action is a real action.
119 if (errors[action] == undefined) {
120 window.console.error(errorPrefix + ERROR_MSG_INVALID_ACTION);
124 // Report the error if the proposed action is found to be invalid for the
127 if (error = errors[action][this.state]) {
128 window.console.error(errorPrefix + error);
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) {
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));
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);
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;
167 // Only update the contentWindow if attaching is successful.
168 this.contentWindow = contentWindow;
171 this.handleCallback(callback);
174 attachParams['instanceId'] = viewInstanceId;
175 GuestViewInternalNatives.AttachGuest(internalInstanceId,
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) {
191 this.internalInstanceId = 0;
192 this.state = GuestViewImpl.GuestState.GUEST_STATE_CREATED;
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);
204 // Callback wrapper function to store the guestInstanceId from the
205 // createGuest() callback, handle potential creation failure, and advance the
207 var callbackWrapper = function(callback, guestInfo) {
208 this.id = guestInfo.id;
210 GuestViewInternalNatives.GetContentWindow(guestInfo.contentWindowId);
212 // Check if creation failed.
214 this.state = GuestViewImpl.GuestState.GUEST_STATE_START;
215 this.contentWindow = null;
218 ResizeEvent.addListener(this.callOnResize, {instanceId: this.id});
219 this.handleCallback(callback);
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);
240 if (this.state == GuestViewImpl.GuestState.GUEST_STATE_START) {
241 // destroy() does nothing in this case.
242 this.handleCallback(callback);
246 // If this guest is attached, then detach it first.
247 if (!!this.internalInstanceId) {
248 GuestViewInternalNatives.DetachGuest(this.internalInstanceId);
251 GuestViewInternal.destroyGuest(this.id,
252 this.handleCallback.bind(this, callback));
254 // Reset the state of the destroyed guest;
255 this.contentWindow = null;
257 this.internalInstanceId = 0;
258 this.state = GuestViewImpl.GuestState.GUEST_STATE_START;
259 if (ResizeEvent.hasListener(this.callOnResize)) {
260 ResizeEvent.removeListener(this.callOnResize);
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);
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);
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
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
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;
353 exports.GuestView = GuestView;
354 exports.GuestViewImpl = GuestViewImpl;
355 exports.ResizeEvent = ResizeEvent;