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 7var appWindowNatives = requireNative('app_window_natives'); 8var runtimeNatives = requireNative('runtime'); 9var Binding = require('binding').Binding; 10var Event = require('event_bindings').Event; 11var forEach = require('utils').forEach; 12var renderViewObserverNatives = requireNative('renderViewObserverNatives'); 13var sendRequest = require('sendRequest').sendRequest; 14 15var appWindowData = null; 16var currentAppWindow = null; 17 18var appWindow = Binding.create('app.window'); 19appWindow.registerCustomHook(function(bindingsAPI) { 20 var apiFunctions = bindingsAPI.apiFunctions; 21 22 apiFunctions.setCustomCallback('create', 23 function(name, request, windowParams) { 24 var view = null; 25 if (windowParams.viewId) { 26 view = appWindowNatives.GetView( 27 windowParams.viewId, windowParams.injectTitlebar); 28 } 29 30 if (!view) { 31 // No route to created window. If given a callback, trigger it with an 32 // undefined object. 33 if (request.callback) { 34 request.callback(); 35 delete request.callback; 36 } 37 return; 38 } 39 40 if (windowParams.existingWindow) { 41 // Not creating a new window, but activating an existing one, so trigger 42 // callback with existing window and don't do anything else. 43 if (request.callback) { 44 request.callback(view.chrome.app.window.current()); 45 delete request.callback; 46 } 47 return; 48 } 49 50 // Initialize appWindowData in the newly created JS context 51 view.chrome.app.window.initializeAppWindow(windowParams); 52 53 var callback = request.callback; 54 if (callback) { 55 delete request.callback; 56 if (!view) { 57 callback(undefined); 58 return; 59 } 60 61 var willCallback = 62 renderViewObserverNatives.OnDocumentElementCreated( 63 windowParams.viewId, 64 function(success) { 65 if (success) { 66 callback(view.chrome.app.window.current()); 67 } else { 68 callback(undefined); 69 } 70 }); 71 if (!willCallback) { 72 callback(undefined); 73 } 74 } 75 }); 76 77 apiFunctions.setHandleRequest('current', function() { 78 if (!currentAppWindow) { 79 console.error('The JavaScript context calling ' + 80 'chrome.app.window.current() has no associated AppWindow.'); 81 return null; 82 } 83 return currentAppWindow; 84 }); 85 86 apiFunctions.setHandleRequest('getAll', function() { 87 var views = runtimeNatives.GetExtensionViews(-1, 'SHELL'); 88 return $Array.map(views, function(win) { 89 return win.chrome.app.window.current(); 90 }); 91 }); 92 93 apiFunctions.setHandleRequest('get', function(id) { 94 var windows = $Array.filter(chrome.app.window.getAll(), function(win) { 95 return win.id == id; 96 }); 97 return windows.length > 0 ? windows[0] : null; 98 }); 99 100 // This is an internal function, but needs to be bound with setHandleRequest 101 // because it is called from a different JS context. 102 apiFunctions.setHandleRequest('initializeAppWindow', function(params) { 103 var currentWindowInternal = 104 Binding.create('app.currentWindowInternal').generate(); 105 var AppWindow = function() {}; 106 forEach(currentWindowInternal, function(key, value) { 107 AppWindow.prototype[key] = value; 108 }); 109 AppWindow.prototype.moveTo = $Function.bind(window.moveTo, window); 110 AppWindow.prototype.resizeTo = $Function.bind(window.resizeTo, window); 111 AppWindow.prototype.contentWindow = window; 112 AppWindow.prototype.onClosed = new Event(); 113 AppWindow.prototype.close = function() { 114 this.contentWindow.close(); 115 }; 116 AppWindow.prototype.getBounds = function() { 117 var bounds = appWindowData.bounds; 118 return { left: bounds.left, top: bounds.top, 119 width: bounds.width, height: bounds.height }; 120 }; 121 AppWindow.prototype.getMinWidth = function() { 122 return appWindowData.minWidth; 123 }; 124 AppWindow.prototype.getMinHeight = function() { 125 return appWindowData.minHeight; 126 }; 127 AppWindow.prototype.getMaxWidth = function() { 128 return appWindowData.maxWidth; 129 }; 130 AppWindow.prototype.getMaxHeight = function() { 131 return appWindowData.maxHeight; 132 }; 133 AppWindow.prototype.isFullscreen = function() { 134 return appWindowData.fullscreen; 135 }; 136 AppWindow.prototype.isMinimized = function() { 137 return appWindowData.minimized; 138 }; 139 AppWindow.prototype.isMaximized = function() { 140 return appWindowData.maximized; 141 }; 142 AppWindow.prototype.isAlwaysOnTop = function() { 143 return appWindowData.alwaysOnTop; 144 }; 145 146 Object.defineProperty(AppWindow.prototype, 'id', {get: function() { 147 return appWindowData.id; 148 }}); 149 150 appWindowData = { 151 id: params.id || '', 152 bounds: { left: params.bounds.left, top: params.bounds.top, 153 width: params.bounds.width, height: params.bounds.height }, 154 minWidth: params.minWidth, 155 minHeight: params.minHeight, 156 maxWidth: params.maxWidth, 157 maxHeight: params.maxHeight, 158 fullscreen: params.fullscreen, 159 minimized: params.minimized, 160 maximized: params.maximized, 161 alwaysOnTop: params.alwaysOnTop 162 }; 163 currentAppWindow = new AppWindow; 164 }); 165}); 166 167function boundsEqual(bounds1, bounds2) { 168 if (!bounds1 || !bounds2) 169 return false; 170 return (bounds1.left == bounds2.left && bounds1.top == bounds2.top && 171 bounds1.width == bounds2.width && bounds1.height == bounds2.height); 172} 173 174function dispatchEventIfExists(target, name) { 175 // Sometimes apps like to put their own properties on the window which 176 // break our assumptions. 177 var event = target[name]; 178 if (event && (typeof event.dispatch == 'function')) 179 event.dispatch(); 180 else 181 console.warn('Could not dispatch ' + name + ', event has been clobbered'); 182} 183 184function updateAppWindowProperties(update) { 185 if (!appWindowData) 186 return; 187 188 var oldData = appWindowData; 189 update.id = oldData.id; 190 appWindowData = update; 191 192 var currentWindow = currentAppWindow; 193 194 if (!boundsEqual(oldData.bounds, update.bounds)) 195 dispatchEventIfExists(currentWindow, "onBoundsChanged"); 196 197 if (!oldData.fullscreen && update.fullscreen) 198 dispatchEventIfExists(currentWindow, "onFullscreened"); 199 if (!oldData.minimized && update.minimized) 200 dispatchEventIfExists(currentWindow, "onMinimized"); 201 if (!oldData.maximized && update.maximized) 202 dispatchEventIfExists(currentWindow, "onMaximized"); 203 204 if ((oldData.fullscreen && !update.fullscreen) || 205 (oldData.minimized && !update.minimized) || 206 (oldData.maximized && !update.maximized)) 207 dispatchEventIfExists(currentWindow, "onRestored"); 208}; 209 210function onAppWindowClosed() { 211 if (!currentAppWindow) 212 return; 213 dispatchEventIfExists(currentAppWindow, "onClosed"); 214} 215 216exports.binding = appWindow.generate(); 217exports.onAppWindowClosed = onAppWindowClosed; 218exports.updateAppWindowProperties = updateAppWindowProperties; 219