app_window_custom_bindings.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  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. // Custom binding for the app_window API.
  5. var appWindowNatives = requireNative('app_window_natives');
  6. var runtimeNatives = requireNative('runtime');
  7. var forEach = require('utils').forEach;
  8. var renderFrameObserverNatives = requireNative('renderFrameObserverNatives');
  9. var appWindowData = null;
  10. var currentAppWindow = null;
  11. var currentWindowInternal = null;
  12. var kSetBoundsFunction = 'setBounds';
  13. var kSetSizeConstraintsFunction = 'setSizeConstraints';
  14. // Bounds class definition.
  15. var Bounds = function(boundsKey) {
  16. privates(this).boundsKey_ = boundsKey;
  17. };
  18. Object.defineProperty(Bounds.prototype, 'left', {
  19. get: function() {
  20. return appWindowData[privates(this).boundsKey_].left;
  21. },
  22. set: function(left) {
  23. this.setPosition(left, null);
  24. },
  25. enumerable: true
  26. });
  27. Object.defineProperty(Bounds.prototype, 'top', {
  28. get: function() {
  29. return appWindowData[privates(this).boundsKey_].top;
  30. },
  31. set: function(top) {
  32. this.setPosition(null, top);
  33. },
  34. enumerable: true
  35. });
  36. Object.defineProperty(Bounds.prototype, 'width', {
  37. get: function() {
  38. return appWindowData[privates(this).boundsKey_].width;
  39. },
  40. set: function(width) {
  41. this.setSize(width, null);
  42. },
  43. enumerable: true
  44. });
  45. Object.defineProperty(Bounds.prototype, 'height', {
  46. get: function() {
  47. return appWindowData[privates(this).boundsKey_].height;
  48. },
  49. set: function(height) {
  50. this.setSize(null, height);
  51. },
  52. enumerable: true
  53. });
  54. Object.defineProperty(Bounds.prototype, 'minWidth', {
  55. get: function() {
  56. return appWindowData[privates(this).boundsKey_].minWidth;
  57. },
  58. set: function(minWidth) {
  59. updateSizeConstraints(privates(this).boundsKey_, { minWidth: minWidth });
  60. },
  61. enumerable: true
  62. });
  63. Object.defineProperty(Bounds.prototype, 'maxWidth', {
  64. get: function() {
  65. return appWindowData[privates(this).boundsKey_].maxWidth;
  66. },
  67. set: function(maxWidth) {
  68. updateSizeConstraints(privates(this).boundsKey_, { maxWidth: maxWidth });
  69. },
  70. enumerable: true
  71. });
  72. Object.defineProperty(Bounds.prototype, 'minHeight', {
  73. get: function() {
  74. return appWindowData[privates(this).boundsKey_].minHeight;
  75. },
  76. set: function(minHeight) {
  77. updateSizeConstraints(privates(this).boundsKey_, { minHeight: minHeight });
  78. },
  79. enumerable: true
  80. });
  81. Object.defineProperty(Bounds.prototype, 'maxHeight', {
  82. get: function() {
  83. return appWindowData[privates(this).boundsKey_].maxHeight;
  84. },
  85. set: function(maxHeight) {
  86. updateSizeConstraints(privates(this).boundsKey_, { maxHeight: maxHeight });
  87. },
  88. enumerable: true
  89. });
  90. Bounds.prototype.setPosition = function(left, top) {
  91. updateBounds(privates(this).boundsKey_, { left: left, top: top });
  92. };
  93. Bounds.prototype.setSize = function(width, height) {
  94. updateBounds(privates(this).boundsKey_, { width: width, height: height });
  95. };
  96. Bounds.prototype.setMinimumSize = function(minWidth, minHeight) {
  97. updateSizeConstraints(privates(this).boundsKey_,
  98. { minWidth: minWidth, minHeight: minHeight });
  99. };
  100. Bounds.prototype.setMaximumSize = function(maxWidth, maxHeight) {
  101. updateSizeConstraints(privates(this).boundsKey_,
  102. { maxWidth: maxWidth, maxHeight: maxHeight });
  103. };
  104. apiBridge.registerCustomHook(function(bindingsAPI) {
  105. var apiFunctions = bindingsAPI.apiFunctions;
  106. apiFunctions.setCustomCallback('create',
  107. function(callback, windowParams) {
  108. // |callback| is optional.
  109. let maybeCallback = callback || function() {};
  110. // When window creation fails, windowParams is undefined. Return undefined
  111. // to the caller.
  112. if (!windowParams || !windowParams.frameId) {
  113. maybeCallback(undefined);
  114. return;
  115. }
  116. let view = appWindowNatives.GetFrame(windowParams.frameId,
  117. true /* notifyBrowser */);
  118. if (windowParams.existingWindow) {
  119. // Not creating a new window, but activating an existing one, so trigger
  120. // callback with existing window and don't do anything else.
  121. let windowResult = view ? view.chrome.app.window.current() : undefined;
  122. maybeCallback(windowResult);
  123. return;
  124. }
  125. // Handle the sandboxed page case.
  126. if (!view || !view.chrome.app) {
  127. var sandbox_window_message = 'Creating sandboxed window, it doesn\'t ' +
  128. 'have access to the chrome.app API.';
  129. if (callback) {
  130. sandbox_window_message = sandbox_window_message +
  131. ' The chrome.app.window.create callback will be called, but ' +
  132. 'there will be no object provided for the sandboxed window.';
  133. }
  134. console.warn(sandbox_window_message);
  135. maybeCallback(undefined);
  136. return;
  137. }
  138. // Handle error pages.
  139. // TODO(arthursonzogni): Figure out why view.chrome.app is defined for error
  140. // pages and stop doing it.
  141. if (!view.chrome.app.window) {
  142. maybeCallback(undefined);
  143. return;
  144. }
  145. // Initialize appWindowData in the newly created JS context
  146. view.chrome.app.window.initializeAppWindow(windowParams);
  147. var willCallback = renderFrameObserverNatives.OnDocumentElementCreated(
  148. windowParams.frameId, function(success) {
  149. let windowResult = success ? view.chrome.app.window.current()
  150. : undefined;
  151. maybeCallback(windowResult);
  152. });
  153. appWindowNatives.ResumeParser(windowParams.frameId);
  154. if (!willCallback)
  155. maybeCallback(undefined);
  156. });
  157. apiFunctions.setHandleRequest('current', function() {
  158. if (!currentAppWindow) {
  159. console.error('The JavaScript context calling ' +
  160. 'chrome.app.window.current() has no associated AppWindow.');
  161. return null;
  162. }
  163. return currentAppWindow;
  164. });
  165. apiFunctions.setHandleRequest('getAll', function() {
  166. var views = runtimeNatives.GetExtensionViews(-1, -1, 'APP_WINDOW');
  167. views = $Array.filter(views, (w) => {
  168. if (!w.chrome.app.window.current()) {
  169. // Even though the script context has been created, the initialization
  170. // hasn't finished. chrome.app.window.current() is based upon the
  171. // completion of the app.window.create() callback, and it's possible
  172. // this is called before that.
  173. // Treat not-fully-initialized windows as invisible; the app can query
  174. // them once the callback for app.window.create() is fired (which is
  175. // in-line with API expectations).
  176. // See https://crbug.com/1021014.
  177. return false;
  178. }
  179. return true;
  180. });
  181. return $Array.map(views, function(win) {
  182. return win.chrome.app.window.current();
  183. });
  184. });
  185. apiFunctions.setHandleRequest('get', function(id) {
  186. var windows = $Array.filter(chrome.app.window.getAll(), function(win) {
  187. return win.id == id;
  188. });
  189. return windows.length > 0 ? windows[0] : null;
  190. });
  191. apiFunctions.setHandleRequest('canSetVisibleOnAllWorkspaces', function() {
  192. return /Mac/.test(navigator.platform) || /Linux/.test(navigator.userAgent);
  193. });
  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 = getInternalApi('app.currentWindowInternal');
  199. var AppWindow = function() {
  200. this.innerBounds = new Bounds('innerBounds');
  201. this.outerBounds = new Bounds('outerBounds');
  202. };
  203. forEach(currentWindowInternal, function(key, value) {
  204. // Do not add internal functions that should not appear in the AppWindow
  205. // interface. They are called by Bounds mutators.
  206. if (key !== kSetBoundsFunction && key !== kSetSizeConstraintsFunction)
  207. AppWindow.prototype[key] = value;
  208. });
  209. AppWindow.prototype.moveTo = $Function.bind(window.moveTo, window);
  210. AppWindow.prototype.resizeTo = $Function.bind(window.resizeTo, window);
  211. AppWindow.prototype.contentWindow = window;
  212. var supportsFilters = false;
  213. var supportsLazyListeners = false;
  214. AppWindow.prototype.onClosed =
  215. bindingUtil.createCustomEvent(undefined /* name */,
  216. supportsFilters,
  217. supportsLazyListeners);
  218. AppWindow.prototype.close = function() {
  219. this.contentWindow.close();
  220. };
  221. AppWindow.prototype.getBounds = function() {
  222. // This is to maintain backcompatibility with a bug on Windows and
  223. // ChromeOS, which returns the position of the window but the size of
  224. // the content.
  225. var innerBounds = appWindowData.innerBounds;
  226. var outerBounds = appWindowData.outerBounds;
  227. return { left: outerBounds.left, top: outerBounds.top,
  228. width: innerBounds.width, height: innerBounds.height };
  229. };
  230. AppWindow.prototype.setBounds = function(bounds) {
  231. updateBounds('bounds', bounds);
  232. };
  233. AppWindow.prototype.isFullscreen = function() {
  234. return appWindowData.fullscreen;
  235. };
  236. AppWindow.prototype.isMinimized = function() {
  237. return appWindowData.minimized;
  238. };
  239. AppWindow.prototype.isMaximized = function() {
  240. return appWindowData.maximized;
  241. };
  242. AppWindow.prototype.isAlwaysOnTop = function() {
  243. return appWindowData.alwaysOnTop;
  244. };
  245. AppWindow.prototype.alphaEnabled = function() {
  246. return appWindowData.alphaEnabled;
  247. };
  248. Object.defineProperty(AppWindow.prototype, 'id', {get: function() {
  249. return appWindowData.id;
  250. }});
  251. // These properties are for testing.
  252. Object.defineProperty(
  253. AppWindow.prototype, 'hasFrameColor', {get: function() {
  254. return appWindowData.hasFrameColor;
  255. }});
  256. Object.defineProperty(AppWindow.prototype, 'activeFrameColor',
  257. {get: function() {
  258. return appWindowData.activeFrameColor;
  259. }});
  260. Object.defineProperty(AppWindow.prototype, 'inactiveFrameColor',
  261. {get: function() {
  262. return appWindowData.inactiveFrameColor;
  263. }});
  264. appWindowData = {
  265. id: params.id || '',
  266. innerBounds: {
  267. left: params.innerBounds.left,
  268. top: params.innerBounds.top,
  269. width: params.innerBounds.width,
  270. height: params.innerBounds.height,
  271. minWidth: params.innerBounds.minWidth,
  272. minHeight: params.innerBounds.minHeight,
  273. maxWidth: params.innerBounds.maxWidth,
  274. maxHeight: params.innerBounds.maxHeight
  275. },
  276. outerBounds: {
  277. left: params.outerBounds.left,
  278. top: params.outerBounds.top,
  279. width: params.outerBounds.width,
  280. height: params.outerBounds.height,
  281. minWidth: params.outerBounds.minWidth,
  282. minHeight: params.outerBounds.minHeight,
  283. maxWidth: params.outerBounds.maxWidth,
  284. maxHeight: params.outerBounds.maxHeight
  285. },
  286. fullscreen: params.fullscreen,
  287. minimized: params.minimized,
  288. maximized: params.maximized,
  289. alwaysOnTop: params.alwaysOnTop,
  290. hasFrameColor: params.hasFrameColor,
  291. activeFrameColor: params.activeFrameColor,
  292. inactiveFrameColor: params.inactiveFrameColor,
  293. alphaEnabled: params.alphaEnabled
  294. };
  295. currentAppWindow = new AppWindow;
  296. });
  297. });
  298. function boundsEqual(bounds1, bounds2) {
  299. if (!bounds1 || !bounds2)
  300. return false;
  301. return (bounds1.left == bounds2.left && bounds1.top == bounds2.top &&
  302. bounds1.width == bounds2.width && bounds1.height == bounds2.height);
  303. }
  304. function dispatchEventIfExists(target, name) {
  305. // Sometimes apps like to put their own properties on the window which
  306. // break our assumptions.
  307. var event = target[name];
  308. if (event && (typeof event.dispatch == 'function'))
  309. event.dispatch();
  310. else
  311. console.warn('Could not dispatch ' + name + ', event has been clobbered');
  312. }
  313. function updateAppWindowProperties(update) {
  314. if (!appWindowData)
  315. return;
  316. var oldData = appWindowData;
  317. update.id = oldData.id;
  318. appWindowData = update;
  319. var currentWindow = currentAppWindow;
  320. if (!boundsEqual(oldData.innerBounds, update.innerBounds))
  321. dispatchEventIfExists(currentWindow, "onBoundsChanged");
  322. if (!oldData.fullscreen && update.fullscreen)
  323. dispatchEventIfExists(currentWindow, "onFullscreened");
  324. if (!oldData.minimized && update.minimized)
  325. dispatchEventIfExists(currentWindow, "onMinimized");
  326. if (!oldData.maximized && update.maximized)
  327. dispatchEventIfExists(currentWindow, "onMaximized");
  328. if ((oldData.fullscreen && !update.fullscreen) ||
  329. (oldData.minimized && !update.minimized) ||
  330. (oldData.maximized && !update.maximized))
  331. dispatchEventIfExists(currentWindow, "onRestored");
  332. if (oldData.alphaEnabled !== update.alphaEnabled)
  333. dispatchEventIfExists(currentWindow, "onAlphaEnabledChanged");
  334. };
  335. function onAppWindowClosed() {
  336. if (!currentAppWindow)
  337. return;
  338. dispatchEventIfExists(currentAppWindow, "onClosed");
  339. }
  340. function updateBounds(boundsType, bounds) {
  341. if (!currentWindowInternal)
  342. return;
  343. currentWindowInternal.setBounds(boundsType, bounds);
  344. }
  345. function updateSizeConstraints(boundsType, constraints) {
  346. if (!currentWindowInternal)
  347. return;
  348. forEach(constraints, function(key, value) {
  349. // From the perspective of the API, null is used to reset constraints.
  350. // We need to convert this to 0 because a value of null is interpreted
  351. // the same as undefined in the browser and leaves the constraint unchanged.
  352. if (value === null)
  353. constraints[key] = 0;
  354. });
  355. currentWindowInternal.setSizeConstraints(boundsType, constraints);
  356. }
  357. exports.$set('onAppWindowClosed', onAppWindowClosed);
  358. exports.$set('updateAppWindowProperties', updateAppWindowProperties);