OLD | NEW |
| (Empty) |
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. | |
4 | |
5 // Custom binding for the app_window API. | |
6 | |
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'); | |
13 | |
14 var appWindowData = null; | |
15 var currentAppWindow = null; | |
16 var currentWindowInternal = null; | |
17 | |
18 var kSetBoundsFunction = 'setBounds'; | |
19 var kSetSizeConstraintsFunction = 'setSizeConstraints'; | |
20 | |
21 // Bounds class definition. | |
22 var Bounds = function(boundsKey) { | |
23 privates(this).boundsKey_ = boundsKey; | |
24 }; | |
25 Object.defineProperty(Bounds.prototype, 'left', { | |
26 get: function() { | |
27 return appWindowData[privates(this).boundsKey_].left; | |
28 }, | |
29 set: function(left) { | |
30 this.setPosition(left, null); | |
31 }, | |
32 enumerable: true | |
33 }); | |
34 Object.defineProperty(Bounds.prototype, 'top', { | |
35 get: function() { | |
36 return appWindowData[privates(this).boundsKey_].top; | |
37 }, | |
38 set: function(top) { | |
39 this.setPosition(null, top); | |
40 }, | |
41 enumerable: true | |
42 }); | |
43 Object.defineProperty(Bounds.prototype, 'width', { | |
44 get: function() { | |
45 return appWindowData[privates(this).boundsKey_].width; | |
46 }, | |
47 set: function(width) { | |
48 this.setSize(width, null); | |
49 }, | |
50 enumerable: true | |
51 }); | |
52 Object.defineProperty(Bounds.prototype, 'height', { | |
53 get: function() { | |
54 return appWindowData[privates(this).boundsKey_].height; | |
55 }, | |
56 set: function(height) { | |
57 this.setSize(null, height); | |
58 }, | |
59 enumerable: true | |
60 }); | |
61 Object.defineProperty(Bounds.prototype, 'minWidth', { | |
62 get: function() { | |
63 return appWindowData[privates(this).boundsKey_].minWidth; | |
64 }, | |
65 set: function(minWidth) { | |
66 updateSizeConstraints(privates(this).boundsKey_, { minWidth: minWidth }); | |
67 }, | |
68 enumerable: true | |
69 }); | |
70 Object.defineProperty(Bounds.prototype, 'maxWidth', { | |
71 get: function() { | |
72 return appWindowData[privates(this).boundsKey_].maxWidth; | |
73 }, | |
74 set: function(maxWidth) { | |
75 updateSizeConstraints(privates(this).boundsKey_, { maxWidth: maxWidth }); | |
76 }, | |
77 enumerable: true | |
78 }); | |
79 Object.defineProperty(Bounds.prototype, 'minHeight', { | |
80 get: function() { | |
81 return appWindowData[privates(this).boundsKey_].minHeight; | |
82 }, | |
83 set: function(minHeight) { | |
84 updateSizeConstraints(privates(this).boundsKey_, { minHeight: minHeight }); | |
85 }, | |
86 enumerable: true | |
87 }); | |
88 Object.defineProperty(Bounds.prototype, 'maxHeight', { | |
89 get: function() { | |
90 return appWindowData[privates(this).boundsKey_].maxHeight; | |
91 }, | |
92 set: function(maxHeight) { | |
93 updateSizeConstraints(privates(this).boundsKey_, { maxHeight: maxHeight }); | |
94 }, | |
95 enumerable: true | |
96 }); | |
97 Bounds.prototype.setPosition = function(left, top) { | |
98 updateBounds(privates(this).boundsKey_, { left: left, top: top }); | |
99 }; | |
100 Bounds.prototype.setSize = function(width, height) { | |
101 updateBounds(privates(this).boundsKey_, { width: width, height: height }); | |
102 }; | |
103 Bounds.prototype.setMinimumSize = function(minWidth, minHeight) { | |
104 updateSizeConstraints(privates(this).boundsKey_, | |
105 { minWidth: minWidth, minHeight: minHeight }); | |
106 }; | |
107 Bounds.prototype.setMaximumSize = function(maxWidth, maxHeight) { | |
108 updateSizeConstraints(privates(this).boundsKey_, | |
109 { maxWidth: maxWidth, maxHeight: maxHeight }); | |
110 }; | |
111 | |
112 var appWindow = Binding.create('app.window'); | |
113 appWindow.registerCustomHook(function(bindingsAPI) { | |
114 var apiFunctions = bindingsAPI.apiFunctions; | |
115 | |
116 apiFunctions.setCustomCallback('create', | |
117 function(name, request, windowParams) { | |
118 var view = null; | |
119 | |
120 // When window creation fails, |windowParams| will be undefined. | |
121 if (windowParams && windowParams.viewId) { | |
122 view = appWindowNatives.GetView( | |
123 windowParams.viewId, windowParams.injectTitlebar); | |
124 } | |
125 | |
126 if (!view) { | |
127 // No route to created window. If given a callback, trigger it with an | |
128 // undefined object. | |
129 if (request.callback) { | |
130 request.callback(); | |
131 delete request.callback; | |
132 } | |
133 return; | |
134 } | |
135 | |
136 if (windowParams.existingWindow) { | |
137 // Not creating a new window, but activating an existing one, so trigger | |
138 // callback with existing window and don't do anything else. | |
139 if (request.callback) { | |
140 request.callback(view.chrome.app.window.current()); | |
141 delete request.callback; | |
142 } | |
143 return; | |
144 } | |
145 | |
146 // Initialize appWindowData in the newly created JS context | |
147 view.chrome.app.window.initializeAppWindow(windowParams); | |
148 | |
149 var callback = request.callback; | |
150 if (callback) { | |
151 delete request.callback; | |
152 if (!view) { | |
153 callback(undefined); | |
154 return; | |
155 } | |
156 | |
157 var willCallback = | |
158 renderViewObserverNatives.OnDocumentElementCreated( | |
159 windowParams.viewId, | |
160 function(success) { | |
161 if (success) { | |
162 callback(view.chrome.app.window.current()); | |
163 } else { | |
164 callback(undefined); | |
165 } | |
166 }); | |
167 if (!willCallback) { | |
168 callback(undefined); | |
169 } | |
170 } | |
171 }); | |
172 | |
173 apiFunctions.setHandleRequest('current', function() { | |
174 if (!currentAppWindow) { | |
175 console.error('The JavaScript context calling ' + | |
176 'chrome.app.window.current() has no associated AppWindow.'); | |
177 return null; | |
178 } | |
179 return currentAppWindow; | |
180 }); | |
181 | |
182 apiFunctions.setHandleRequest('getAll', function() { | |
183 var views = runtimeNatives.GetExtensionViews(-1, 'APP_WINDOW'); | |
184 return $Array.map(views, function(win) { | |
185 return win.chrome.app.window.current(); | |
186 }); | |
187 }); | |
188 | |
189 apiFunctions.setHandleRequest('get', function(id) { | |
190 var windows = $Array.filter(chrome.app.window.getAll(), function(win) { | |
191 return win.id == id; | |
192 }); | |
193 return windows.length > 0 ? windows[0] : null; | |
194 }); | |
195 | |
196 // This is an internal function, but needs to be bound into a closure | |
197 // so the correct JS context is used for global variables such as | |
198 // currentWindowInternal, appWindowData, etc. | |
199 apiFunctions.setHandleRequest('initializeAppWindow', function(params) { | |
200 currentWindowInternal = | |
201 Binding.create('app.currentWindowInternal').generate(); | |
202 var AppWindow = function() { | |
203 this.innerBounds = new Bounds('innerBounds'); | |
204 this.outerBounds = new Bounds('outerBounds'); | |
205 }; | |
206 forEach(currentWindowInternal, function(key, value) { | |
207 // Do not add internal functions that should not appear in the AppWindow | |
208 // interface. They are called by Bounds mutators. | |
209 if (key !== kSetBoundsFunction && key !== kSetSizeConstraintsFunction) | |
210 AppWindow.prototype[key] = value; | |
211 }); | |
212 AppWindow.prototype.moveTo = $Function.bind(window.moveTo, window); | |
213 AppWindow.prototype.resizeTo = $Function.bind(window.resizeTo, window); | |
214 AppWindow.prototype.contentWindow = window; | |
215 AppWindow.prototype.onClosed = new Event(); | |
216 AppWindow.prototype.onWindowFirstShownForTests = new Event(); | |
217 AppWindow.prototype.close = function() { | |
218 this.contentWindow.close(); | |
219 }; | |
220 AppWindow.prototype.getBounds = function() { | |
221 // This is to maintain backcompatibility with a bug on Windows and | |
222 // ChromeOS, which returns the position of the window but the size of | |
223 // the content. | |
224 var innerBounds = appWindowData.innerBounds; | |
225 var outerBounds = appWindowData.outerBounds; | |
226 return { left: outerBounds.left, top: outerBounds.top, | |
227 width: innerBounds.width, height: innerBounds.height }; | |
228 }; | |
229 AppWindow.prototype.setBounds = function(bounds) { | |
230 updateBounds('bounds', bounds); | |
231 }; | |
232 AppWindow.prototype.isFullscreen = function() { | |
233 return appWindowData.fullscreen; | |
234 }; | |
235 AppWindow.prototype.isMinimized = function() { | |
236 return appWindowData.minimized; | |
237 }; | |
238 AppWindow.prototype.isMaximized = function() { | |
239 return appWindowData.maximized; | |
240 }; | |
241 AppWindow.prototype.isAlwaysOnTop = function() { | |
242 return appWindowData.alwaysOnTop; | |
243 }; | |
244 AppWindow.prototype.alphaEnabled = function() { | |
245 return appWindowData.alphaEnabled; | |
246 }; | |
247 AppWindow.prototype.handleWindowFirstShownForTests = function(callback) { | |
248 // This allows test apps to get have their callback run even if they | |
249 // call this after the first show has happened. | |
250 if (this.firstShowHasHappened) { | |
251 callback(); | |
252 return; | |
253 } | |
254 this.onWindowFirstShownForTests.addListener(callback); | |
255 } | |
256 | |
257 Object.defineProperty(AppWindow.prototype, 'id', {get: function() { | |
258 return appWindowData.id; | |
259 }}); | |
260 | |
261 // These properties are for testing. | |
262 Object.defineProperty( | |
263 AppWindow.prototype, 'hasFrameColor', {get: function() { | |
264 return appWindowData.hasFrameColor; | |
265 }}); | |
266 | |
267 Object.defineProperty(AppWindow.prototype, 'activeFrameColor', | |
268 {get: function() { | |
269 return appWindowData.activeFrameColor; | |
270 }}); | |
271 | |
272 Object.defineProperty(AppWindow.prototype, 'inactiveFrameColor', | |
273 {get: function() { | |
274 return appWindowData.inactiveFrameColor; | |
275 }}); | |
276 | |
277 appWindowData = { | |
278 id: params.id || '', | |
279 innerBounds: { | |
280 left: params.innerBounds.left, | |
281 top: params.innerBounds.top, | |
282 width: params.innerBounds.width, | |
283 height: params.innerBounds.height, | |
284 | |
285 minWidth: params.innerBounds.minWidth, | |
286 minHeight: params.innerBounds.minHeight, | |
287 maxWidth: params.innerBounds.maxWidth, | |
288 maxHeight: params.innerBounds.maxHeight | |
289 }, | |
290 outerBounds: { | |
291 left: params.outerBounds.left, | |
292 top: params.outerBounds.top, | |
293 width: params.outerBounds.width, | |
294 height: params.outerBounds.height, | |
295 | |
296 minWidth: params.outerBounds.minWidth, | |
297 minHeight: params.outerBounds.minHeight, | |
298 maxWidth: params.outerBounds.maxWidth, | |
299 maxHeight: params.outerBounds.maxHeight | |
300 }, | |
301 fullscreen: params.fullscreen, | |
302 minimized: params.minimized, | |
303 maximized: params.maximized, | |
304 alwaysOnTop: params.alwaysOnTop, | |
305 hasFrameColor: params.hasFrameColor, | |
306 activeFrameColor: params.activeFrameColor, | |
307 inactiveFrameColor: params.inactiveFrameColor, | |
308 alphaEnabled: params.alphaEnabled | |
309 }; | |
310 currentAppWindow = new AppWindow; | |
311 }); | |
312 }); | |
313 | |
314 function boundsEqual(bounds1, bounds2) { | |
315 if (!bounds1 || !bounds2) | |
316 return false; | |
317 return (bounds1.left == bounds2.left && bounds1.top == bounds2.top && | |
318 bounds1.width == bounds2.width && bounds1.height == bounds2.height); | |
319 } | |
320 | |
321 function dispatchEventIfExists(target, name) { | |
322 // Sometimes apps like to put their own properties on the window which | |
323 // break our assumptions. | |
324 var event = target[name]; | |
325 if (event && (typeof event.dispatch == 'function')) | |
326 event.dispatch(); | |
327 else | |
328 console.warn('Could not dispatch ' + name + ', event has been clobbered'); | |
329 } | |
330 | |
331 function updateAppWindowProperties(update) { | |
332 if (!appWindowData) | |
333 return; | |
334 | |
335 var oldData = appWindowData; | |
336 update.id = oldData.id; | |
337 appWindowData = update; | |
338 | |
339 var currentWindow = currentAppWindow; | |
340 | |
341 if (!boundsEqual(oldData.innerBounds, update.innerBounds)) | |
342 dispatchEventIfExists(currentWindow, "onBoundsChanged"); | |
343 | |
344 if (!oldData.fullscreen && update.fullscreen) | |
345 dispatchEventIfExists(currentWindow, "onFullscreened"); | |
346 if (!oldData.minimized && update.minimized) | |
347 dispatchEventIfExists(currentWindow, "onMinimized"); | |
348 if (!oldData.maximized && update.maximized) | |
349 dispatchEventIfExists(currentWindow, "onMaximized"); | |
350 | |
351 if ((oldData.fullscreen && !update.fullscreen) || | |
352 (oldData.minimized && !update.minimized) || | |
353 (oldData.maximized && !update.maximized)) | |
354 dispatchEventIfExists(currentWindow, "onRestored"); | |
355 | |
356 if (oldData.alphaEnabled !== update.alphaEnabled) | |
357 dispatchEventIfExists(currentWindow, "onAlphaEnabledChanged"); | |
358 }; | |
359 | |
360 function onAppWindowShownForTests() { | |
361 if (!currentAppWindow) | |
362 return; | |
363 | |
364 if (!currentAppWindow.firstShowHasHappened) | |
365 dispatchEventIfExists(currentAppWindow, "onWindowFirstShownForTests"); | |
366 | |
367 currentAppWindow.firstShowHasHappened = true; | |
368 } | |
369 | |
370 function onAppWindowClosed() { | |
371 if (!currentAppWindow) | |
372 return; | |
373 dispatchEventIfExists(currentAppWindow, "onClosed"); | |
374 } | |
375 | |
376 function updateBounds(boundsType, bounds) { | |
377 if (!currentWindowInternal) | |
378 return; | |
379 | |
380 currentWindowInternal.setBounds(boundsType, bounds); | |
381 } | |
382 | |
383 function updateSizeConstraints(boundsType, constraints) { | |
384 if (!currentWindowInternal) | |
385 return; | |
386 | |
387 forEach(constraints, function(key, value) { | |
388 // From the perspective of the API, null is used to reset constraints. | |
389 // We need to convert this to 0 because a value of null is interpreted | |
390 // the same as undefined in the browser and leaves the constraint unchanged. | |
391 if (value === null) | |
392 constraints[key] = 0; | |
393 }); | |
394 | |
395 currentWindowInternal.setSizeConstraints(boundsType, constraints); | |
396 } | |
397 | |
398 exports.binding = appWindow.generate(); | |
399 exports.onAppWindowClosed = onAppWindowClosed; | |
400 exports.updateAppWindowProperties = updateAppWindowProperties; | |
401 exports.appWindowShownForTests = onAppWindowShownForTests; | |
OLD | NEW |