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
;