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