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 renderViewObserverNatives
= requireNative('renderViewObserverNatives');
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
.viewId
) {
122 view
= appWindowNatives
.GetView(
123 windowParams
.viewId
, 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 view
.chrome
.app
.window
.initializeAppWindow(windowParams
);
152 renderViewObserverNatives
.OnDocumentElementCreated(
156 callback(view
.chrome
.app
.window
.current());
167 apiFunctions
.setHandleRequest('current', function() {
168 if (!currentAppWindow
) {
169 console
.error('The JavaScript context calling ' +
170 'chrome.app.window.current() has no associated AppWindow.');
173 return currentAppWindow
;
176 apiFunctions
.setHandleRequest('getAll', function() {
177 var views
= runtimeNatives
.GetExtensionViews(-1, 'APP_WINDOW');
178 return $Array
.map(views
, function(win
) {
179 return win
.chrome
.app
.window
.current();
183 apiFunctions
.setHandleRequest('get', function(id
) {
184 var windows
= $Array
.filter(chrome
.app
.window
.getAll(), function(win
) {
187 return windows
.length
> 0 ? windows
[0] : null;
190 apiFunctions
.setHandleRequest('canSetVisibleOnAllWorkspaces', function() {
191 return /Mac/.test(navigator
.platform
) || /Linux/.test(navigator
.userAgent
);
194 // This is an internal function, but needs to be bound into a closure
195 // so the correct JS context is used for global variables such as
196 // currentWindowInternal, appWindowData, etc.
197 apiFunctions
.setHandleRequest('initializeAppWindow', function(params
) {
198 currentWindowInternal
=
199 Binding
.create('app.currentWindowInternal').generate();
200 var AppWindow = function() {
201 this.innerBounds
= new Bounds('innerBounds');
202 this.outerBounds
= new Bounds('outerBounds');
204 forEach(currentWindowInternal
, function(key
, value
) {
205 // Do not add internal functions that should not appear in the AppWindow
206 // interface. They are called by Bounds mutators.
207 if (key
!== kSetBoundsFunction
&& key
!== kSetSizeConstraintsFunction
)
208 AppWindow
.prototype[key
] = value
;
210 AppWindow
.prototype.moveTo
= $Function
.bind(window
.moveTo
, window
);
211 AppWindow
.prototype.resizeTo
= $Function
.bind(window
.resizeTo
, window
);
212 AppWindow
.prototype.contentWindow
= window
;
213 AppWindow
.prototype.onClosed
= new Event();
214 AppWindow
.prototype.onWindowFirstShownForTests
= new Event();
215 AppWindow
.prototype.close = function() {
216 this.contentWindow
.close();
218 AppWindow
.prototype.getBounds = function() {
219 // This is to maintain backcompatibility with a bug on Windows and
220 // ChromeOS, which returns the position of the window but the size of
222 var innerBounds
= appWindowData
.innerBounds
;
223 var outerBounds
= appWindowData
.outerBounds
;
224 return { left
: outerBounds
.left
, top
: outerBounds
.top
,
225 width
: innerBounds
.width
, height
: innerBounds
.height
};
227 AppWindow
.prototype.setBounds = function(bounds
) {
228 updateBounds('bounds', bounds
);
230 AppWindow
.prototype.isFullscreen = function() {
231 return appWindowData
.fullscreen
;
233 AppWindow
.prototype.isMinimized = function() {
234 return appWindowData
.minimized
;
236 AppWindow
.prototype.isMaximized = function() {
237 return appWindowData
.maximized
;
239 AppWindow
.prototype.isAlwaysOnTop = function() {
240 return appWindowData
.alwaysOnTop
;
242 AppWindow
.prototype.alphaEnabled = function() {
243 return appWindowData
.alphaEnabled
;
245 AppWindow
.prototype.handleWindowFirstShownForTests = function(callback
) {
246 // This allows test apps to get have their callback run even if they
247 // call this after the first show has happened.
248 if (this.firstShowHasHappened
) {
252 this.onWindowFirstShownForTests
.addListener(callback
);
255 Object
.defineProperty(AppWindow
.prototype, 'id', {get: function() {
256 return appWindowData
.id
;
259 // These properties are for testing.
260 Object
.defineProperty(
261 AppWindow
.prototype, 'hasFrameColor', {get: function() {
262 return appWindowData
.hasFrameColor
;
265 Object
.defineProperty(AppWindow
.prototype, 'activeFrameColor',
267 return appWindowData
.activeFrameColor
;
270 Object
.defineProperty(AppWindow
.prototype, 'inactiveFrameColor',
272 return appWindowData
.inactiveFrameColor
;
278 left
: params
.innerBounds
.left
,
279 top
: params
.innerBounds
.top
,
280 width
: params
.innerBounds
.width
,
281 height
: params
.innerBounds
.height
,
283 minWidth
: params
.innerBounds
.minWidth
,
284 minHeight
: params
.innerBounds
.minHeight
,
285 maxWidth
: params
.innerBounds
.maxWidth
,
286 maxHeight
: params
.innerBounds
.maxHeight
289 left
: params
.outerBounds
.left
,
290 top
: params
.outerBounds
.top
,
291 width
: params
.outerBounds
.width
,
292 height
: params
.outerBounds
.height
,
294 minWidth
: params
.outerBounds
.minWidth
,
295 minHeight
: params
.outerBounds
.minHeight
,
296 maxWidth
: params
.outerBounds
.maxWidth
,
297 maxHeight
: params
.outerBounds
.maxHeight
299 fullscreen
: params
.fullscreen
,
300 minimized
: params
.minimized
,
301 maximized
: params
.maximized
,
302 alwaysOnTop
: params
.alwaysOnTop
,
303 hasFrameColor
: params
.hasFrameColor
,
304 activeFrameColor
: params
.activeFrameColor
,
305 inactiveFrameColor
: params
.inactiveFrameColor
,
306 alphaEnabled
: params
.alphaEnabled
308 currentAppWindow
= new AppWindow
;
312 function boundsEqual(bounds1
, bounds2
) {
313 if (!bounds1
|| !bounds2
)
315 return (bounds1
.left
== bounds2
.left
&& bounds1
.top
== bounds2
.top
&&
316 bounds1
.width
== bounds2
.width
&& bounds1
.height
== bounds2
.height
);
319 function dispatchEventIfExists(target
, name
) {
320 // Sometimes apps like to put their own properties on the window which
321 // break our assumptions.
322 var event
= target
[name
];
323 if (event
&& (typeof event
.dispatch
== 'function'))
326 console
.warn('Could not dispatch ' + name
+ ', event has been clobbered');
329 function updateAppWindowProperties(update
) {
333 var oldData
= appWindowData
;
334 update
.id
= oldData
.id
;
335 appWindowData
= update
;
337 var currentWindow
= currentAppWindow
;
339 if (!boundsEqual(oldData
.innerBounds
, update
.innerBounds
))
340 dispatchEventIfExists(currentWindow
, "onBoundsChanged");
342 if (!oldData
.fullscreen
&& update
.fullscreen
)
343 dispatchEventIfExists(currentWindow
, "onFullscreened");
344 if (!oldData
.minimized
&& update
.minimized
)
345 dispatchEventIfExists(currentWindow
, "onMinimized");
346 if (!oldData
.maximized
&& update
.maximized
)
347 dispatchEventIfExists(currentWindow
, "onMaximized");
349 if ((oldData
.fullscreen
&& !update
.fullscreen
) ||
350 (oldData
.minimized
&& !update
.minimized
) ||
351 (oldData
.maximized
&& !update
.maximized
))
352 dispatchEventIfExists(currentWindow
, "onRestored");
354 if (oldData
.alphaEnabled
!== update
.alphaEnabled
)
355 dispatchEventIfExists(currentWindow
, "onAlphaEnabledChanged");
358 function onAppWindowShownForTests() {
359 if (!currentAppWindow
)
362 if (!currentAppWindow
.firstShowHasHappened
)
363 dispatchEventIfExists(currentAppWindow
, "onWindowFirstShownForTests");
365 currentAppWindow
.firstShowHasHappened
= true;
368 function onAppWindowClosed() {
369 if (!currentAppWindow
)
371 dispatchEventIfExists(currentAppWindow
, "onClosed");
374 function updateBounds(boundsType
, bounds
) {
375 if (!currentWindowInternal
)
378 currentWindowInternal
.setBounds(boundsType
, bounds
);
381 function updateSizeConstraints(boundsType
, constraints
) {
382 if (!currentWindowInternal
)
385 forEach(constraints
, function(key
, value
) {
386 // From the perspective of the API, null is used to reset constraints.
387 // We need to convert this to 0 because a value of null is interpreted
388 // the same as undefined in the browser and leaves the constraint unchanged.
390 constraints
[key
] = 0;
393 currentWindowInternal
.setSizeConstraints(boundsType
, constraints
);
396 exports
.binding
= appWindow
.generate();
397 exports
.onAppWindowClosed
= onAppWindowClosed
;
398 exports
.updateAppWindowProperties
= updateAppWindowProperties
;
399 exports
.appWindowShownForTests
= onAppWindowShownForTests
;