| 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 |