1 // Copyright (c) 2012 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 // Custom binding for the app_window API.
7 var appWindowNatives
= requireNative('app_window_natives');
8 var runtimeNatives
= requireNative('runtime');
9 var Binding
= require('binding').Binding
;
10 var Event
= require('event_bindings').Event
;
11 var forEach
= require('utils').forEach
;
12 var renderFrameObserverNatives
= requireNative('renderFrameObserverNatives');
14 var appWindowData
= null;
15 var currentAppWindow
= null;
16 var currentWindowInternal
= null;
18 var kSetBoundsFunction
= 'setBounds';
19 var kSetSizeConstraintsFunction
= 'setSizeConstraints';
21 // Bounds class definition.
22 var Bounds = function(boundsKey
) {
23 privates(this).boundsKey_
= boundsKey
;
25 Object
.defineProperty(Bounds
.prototype, 'left', {
27 return appWindowData
[privates(this).boundsKey_
].left
;
30 this.setPosition(left
, null);
34 Object
.defineProperty(Bounds
.prototype, 'top', {
36 return appWindowData
[privates(this).boundsKey_
].top
;
39 this.setPosition(null, top
);
43 Object
.defineProperty(Bounds
.prototype, 'width', {
45 return appWindowData
[privates(this).boundsKey_
].width
;
47 set: function(width
) {
48 this.setSize(width
, null);
52 Object
.defineProperty(Bounds
.prototype, 'height', {
54 return appWindowData
[privates(this).boundsKey_
].height
;
56 set: function(height
) {
57 this.setSize(null, height
);
61 Object
.defineProperty(Bounds
.prototype, 'minWidth', {
63 return appWindowData
[privates(this).boundsKey_
].minWidth
;
65 set: function(minWidth
) {
66 updateSizeConstraints(privates(this).boundsKey_
, { minWidth
: minWidth
});
70 Object
.defineProperty(Bounds
.prototype, 'maxWidth', {
72 return appWindowData
[privates(this).boundsKey_
].maxWidth
;
74 set: function(maxWidth
) {
75 updateSizeConstraints(privates(this).boundsKey_
, { maxWidth
: maxWidth
});
79 Object
.defineProperty(Bounds
.prototype, 'minHeight', {
81 return appWindowData
[privates(this).boundsKey_
].minHeight
;
83 set: function(minHeight
) {
84 updateSizeConstraints(privates(this).boundsKey_
, { minHeight
: minHeight
});
88 Object
.defineProperty(Bounds
.prototype, 'maxHeight', {
90 return appWindowData
[privates(this).boundsKey_
].maxHeight
;
92 set: function(maxHeight
) {
93 updateSizeConstraints(privates(this).boundsKey_
, { maxHeight
: maxHeight
});
97 Bounds
.prototype.setPosition = function(left
, top
) {
98 updateBounds(privates(this).boundsKey_
, { left
: left
, top
: top
});
100 Bounds
.prototype.setSize = function(width
, height
) {
101 updateBounds(privates(this).boundsKey_
, { width
: width
, height
: height
});
103 Bounds
.prototype.setMinimumSize = function(minWidth
, minHeight
) {
104 updateSizeConstraints(privates(this).boundsKey_
,
105 { minWidth
: minWidth
, minHeight
: minHeight
});
107 Bounds
.prototype.setMaximumSize = function(maxWidth
, maxHeight
) {
108 updateSizeConstraints(privates(this).boundsKey_
,
109 { maxWidth
: maxWidth
, maxHeight
: maxHeight
});
112 var appWindow
= Binding
.create('app.window');
113 appWindow
.registerCustomHook(function(bindingsAPI
) {
114 var apiFunctions
= bindingsAPI
.apiFunctions
;
116 apiFunctions
.setCustomCallback('create',
117 function(name
, request
, callback
, windowParams
) {
120 // When window creation fails, |windowParams| will be undefined.
121 if (windowParams
&& windowParams
.frameId
) {
122 view
= appWindowNatives
.GetFrame(
123 windowParams
.frameId
, windowParams
.injectTitlebar
);
127 // No route to created window. If given a callback, trigger it with an
134 if (windowParams
.existingWindow
) {
135 // Not creating a new window, but activating an existing one, so trigger
136 // callback with existing window and don't do anything else.
138 callback(view
.chrome
.app
.window
.current());
142 // Initialize appWindowData in the newly created JS context
143 if (view
.chrome
.app
) {
144 view
.chrome
.app
.window
.initializeAppWindow(windowParams
);
146 var sandbox_window_message
= 'Creating sandboxed window, it doesn\'t ' +
147 'have access to the chrome.app API.';
149 sandbox_window_message
= sandbox_window_message
+
150 ' The chrome.app.window.create callback will be called, but ' +
151 'there will be no object provided for the sandboxed window.';
153 console
.warn(sandbox_window_message
);
157 if (!view
|| !view
.chrome
.app
/* sandboxed window */) {
163 renderFrameObserverNatives
.OnDocumentElementCreated(
164 windowParams
.frameId
,
167 callback(view
.chrome
.app
.window
.current());
178 apiFunctions
.setHandleRequest('current', function() {
179 if (!currentAppWindow
) {
180 console
.error('The JavaScript context calling ' +
181 'chrome.app.window.current() has no associated AppWindow.');
184 return currentAppWindow
;
187 apiFunctions
.setHandleRequest('getAll', function() {
188 var views
= runtimeNatives
.GetExtensionViews(-1, 'APP_WINDOW');
189 return $Array
.map(views
, function(win
) {
190 return win
.chrome
.app
.window
.current();
194 apiFunctions
.setHandleRequest('get', function(id
) {
195 var windows
= $Array
.filter(chrome
.app
.window
.getAll(), function(win
) {
198 return windows
.length
> 0 ? windows
[0] : null;
201 apiFunctions
.setHandleRequest('canSetVisibleOnAllWorkspaces', function() {
202 return /Mac/.test(navigator
.platform
) || /Linux/.test(navigator
.userAgent
);
205 // This is an internal function, but needs to be bound into a closure
206 // so the correct JS context is used for global variables such as
207 // currentWindowInternal, appWindowData, etc.
208 apiFunctions
.setHandleRequest('initializeAppWindow', function(params
) {
209 currentWindowInternal
=
210 Binding
.create('app.currentWindowInternal').generate();
211 var AppWindow = function() {
212 this.innerBounds
= new Bounds('innerBounds');
213 this.outerBounds
= new Bounds('outerBounds');
215 forEach(currentWindowInternal
, function(key
, value
) {
216 // Do not add internal functions that should not appear in the AppWindow
217 // interface. They are called by Bounds mutators.
218 if (key
!== kSetBoundsFunction
&& key
!== kSetSizeConstraintsFunction
)
219 AppWindow
.prototype[key
] = value
;
221 AppWindow
.prototype.moveTo
= $Function
.bind(window
.moveTo
, window
);
222 AppWindow
.prototype.resizeTo
= $Function
.bind(window
.resizeTo
, window
);
223 AppWindow
.prototype.contentWindow
= window
;
224 AppWindow
.prototype.onClosed
= new Event();
225 AppWindow
.prototype.onWindowFirstShownForTests
= new Event();
226 AppWindow
.prototype.close = function() {
227 this.contentWindow
.close();
229 AppWindow
.prototype.getBounds = function() {
230 // This is to maintain backcompatibility with a bug on Windows and
231 // ChromeOS, which returns the position of the window but the size of
233 var innerBounds
= appWindowData
.innerBounds
;
234 var outerBounds
= appWindowData
.outerBounds
;
235 return { left
: outerBounds
.left
, top
: outerBounds
.top
,
236 width
: innerBounds
.width
, height
: innerBounds
.height
};
238 AppWindow
.prototype.setBounds = function(bounds
) {
239 updateBounds('bounds', bounds
);
241 AppWindow
.prototype.isFullscreen = function() {
242 return appWindowData
.fullscreen
;
244 AppWindow
.prototype.isMinimized = function() {
245 return appWindowData
.minimized
;
247 AppWindow
.prototype.isMaximized = function() {
248 return appWindowData
.maximized
;
250 AppWindow
.prototype.isAlwaysOnTop = function() {
251 return appWindowData
.alwaysOnTop
;
253 AppWindow
.prototype.alphaEnabled = function() {
254 return appWindowData
.alphaEnabled
;
256 AppWindow
.prototype.handleWindowFirstShownForTests = function(callback
) {
257 // This allows test apps to get have their callback run even if they
258 // call this after the first show has happened.
259 if (this.firstShowHasHappened
) {
263 this.onWindowFirstShownForTests
.addListener(callback
);
266 Object
.defineProperty(AppWindow
.prototype, 'id', {get: function() {
267 return appWindowData
.id
;
270 // These properties are for testing.
271 Object
.defineProperty(
272 AppWindow
.prototype, 'hasFrameColor', {get: function() {
273 return appWindowData
.hasFrameColor
;
276 Object
.defineProperty(AppWindow
.prototype, 'activeFrameColor',
278 return appWindowData
.activeFrameColor
;
281 Object
.defineProperty(AppWindow
.prototype, 'inactiveFrameColor',
283 return appWindowData
.inactiveFrameColor
;
289 left
: params
.innerBounds
.left
,
290 top
: params
.innerBounds
.top
,
291 width
: params
.innerBounds
.width
,
292 height
: params
.innerBounds
.height
,
294 minWidth
: params
.innerBounds
.minWidth
,
295 minHeight
: params
.innerBounds
.minHeight
,
296 maxWidth
: params
.innerBounds
.maxWidth
,
297 maxHeight
: params
.innerBounds
.maxHeight
300 left
: params
.outerBounds
.left
,
301 top
: params
.outerBounds
.top
,
302 width
: params
.outerBounds
.width
,
303 height
: params
.outerBounds
.height
,
305 minWidth
: params
.outerBounds
.minWidth
,
306 minHeight
: params
.outerBounds
.minHeight
,
307 maxWidth
: params
.outerBounds
.maxWidth
,
308 maxHeight
: params
.outerBounds
.maxHeight
310 fullscreen
: params
.fullscreen
,
311 minimized
: params
.minimized
,
312 maximized
: params
.maximized
,
313 alwaysOnTop
: params
.alwaysOnTop
,
314 hasFrameColor
: params
.hasFrameColor
,
315 activeFrameColor
: params
.activeFrameColor
,
316 inactiveFrameColor
: params
.inactiveFrameColor
,
317 alphaEnabled
: params
.alphaEnabled
319 currentAppWindow
= new AppWindow
;
323 function boundsEqual(bounds1
, bounds2
) {
324 if (!bounds1
|| !bounds2
)
326 return (bounds1
.left
== bounds2
.left
&& bounds1
.top
== bounds2
.top
&&
327 bounds1
.width
== bounds2
.width
&& bounds1
.height
== bounds2
.height
);
330 function dispatchEventIfExists(target
, name
) {
331 // Sometimes apps like to put their own properties on the window which
332 // break our assumptions.
333 var event
= target
[name
];
334 if (event
&& (typeof event
.dispatch
== 'function'))
337 console
.warn('Could not dispatch ' + name
+ ', event has been clobbered');
340 function updateAppWindowProperties(update
) {
344 var oldData
= appWindowData
;
345 update
.id
= oldData
.id
;
346 appWindowData
= update
;
348 var currentWindow
= currentAppWindow
;
350 if (!boundsEqual(oldData
.innerBounds
, update
.innerBounds
))
351 dispatchEventIfExists(currentWindow
, "onBoundsChanged");
353 if (!oldData
.fullscreen
&& update
.fullscreen
)
354 dispatchEventIfExists(currentWindow
, "onFullscreened");
355 if (!oldData
.minimized
&& update
.minimized
)
356 dispatchEventIfExists(currentWindow
, "onMinimized");
357 if (!oldData
.maximized
&& update
.maximized
)
358 dispatchEventIfExists(currentWindow
, "onMaximized");
360 if ((oldData
.fullscreen
&& !update
.fullscreen
) ||
361 (oldData
.minimized
&& !update
.minimized
) ||
362 (oldData
.maximized
&& !update
.maximized
))
363 dispatchEventIfExists(currentWindow
, "onRestored");
365 if (oldData
.alphaEnabled
!== update
.alphaEnabled
)
366 dispatchEventIfExists(currentWindow
, "onAlphaEnabledChanged");
369 function onAppWindowShownForTests() {
370 if (!currentAppWindow
)
373 if (!currentAppWindow
.firstShowHasHappened
)
374 dispatchEventIfExists(currentAppWindow
, "onWindowFirstShownForTests");
376 currentAppWindow
.firstShowHasHappened
= true;
379 function onAppWindowClosed() {
380 if (!currentAppWindow
)
382 dispatchEventIfExists(currentAppWindow
, "onClosed");
385 function updateBounds(boundsType
, bounds
) {
386 if (!currentWindowInternal
)
389 currentWindowInternal
.setBounds(boundsType
, bounds
);
392 function updateSizeConstraints(boundsType
, constraints
) {
393 if (!currentWindowInternal
)
396 forEach(constraints
, function(key
, value
) {
397 // From the perspective of the API, null is used to reset constraints.
398 // We need to convert this to 0 because a value of null is interpreted
399 // the same as undefined in the browser and leaves the constraint unchanged.
401 constraints
[key
] = 0;
404 currentWindowInternal
.setSizeConstraints(boundsType
, constraints
);
407 exports
.binding
= appWindow
.generate();
408 exports
.onAppWindowClosed
= onAppWindowClosed
;
409 exports
.updateAppWindowProperties
= updateAppWindowProperties
;
410 exports
.appWindowShownForTests
= onAppWindowShownForTests
;