utils.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this
  3. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. 'use strict';
  5. module.metadata = {
  6. 'stability': 'unstable'
  7. };
  8. const { Cc, Ci } = require('chrome');
  9. const array = require('../util/array');
  10. const { defer } = require('sdk/core/promise');
  11. const windowWatcher = Cc['@mozilla.org/embedcomp/window-watcher;1'].
  12. getService(Ci.nsIWindowWatcher);
  13. const appShellService = Cc['@mozilla.org/appshell/appShellService;1'].
  14. getService(Ci.nsIAppShellService);
  15. const WM = Cc['@mozilla.org/appshell/window-mediator;1'].
  16. getService(Ci.nsIWindowMediator);
  17. const io = Cc['@mozilla.org/network/io-service;1'].
  18. getService(Ci.nsIIOService);
  19. const XUL_NS = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
  20. const BROWSER = 'navigator:browser',
  21. URI_BROWSER = 'chrome://browser/content/browser.xul',
  22. NAME = '_blank',
  23. FEATURES = 'chrome,all,dialog=no,non-private';
  24. function isWindowPrivate(win) {
  25. if (!win)
  26. return false;
  27. // if the pbService is undefined, the PrivateBrowsingUtils.jsm is available,
  28. // and the app is Firefox, then assume per-window private browsing is
  29. // enabled.
  30. try {
  31. return win.QueryInterface(Ci.nsIInterfaceRequestor)
  32. .getInterface(Ci.nsIWebNavigation)
  33. .QueryInterface(Ci.nsILoadContext)
  34. .usePrivateBrowsing;
  35. }
  36. catch(e) {}
  37. // Sometimes the input is not a nsIDOMWindow.. but it is still a winodw.
  38. try {
  39. return !!win.docShell.QueryInterface(Ci.nsILoadContext).usePrivateBrowsing;
  40. }
  41. catch (e) {}
  42. return false;
  43. }
  44. exports.isWindowPrivate = isWindowPrivate;
  45. function getMostRecentBrowserWindow() {
  46. return getMostRecentWindow(BROWSER);
  47. }
  48. exports.getMostRecentBrowserWindow = getMostRecentBrowserWindow;
  49. function getHiddenWindow() {
  50. return appShellService.hiddenDOMWindow;
  51. }
  52. exports.getHiddenWindow = getHiddenWindow;
  53. function getMostRecentWindow(type) {
  54. return WM.getMostRecentWindow(type);
  55. }
  56. exports.getMostRecentWindow = getMostRecentWindow;
  57. /**
  58. * Returns the ID of the window's current inner window.
  59. */
  60. function getInnerId(window) {
  61. return window.QueryInterface(Ci.nsIInterfaceRequestor).
  62. getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
  63. };
  64. exports.getInnerId = getInnerId;
  65. /**
  66. * Returns the ID of the window's outer window.
  67. */
  68. function getOuterId(window) {
  69. return window.QueryInterface(Ci.nsIInterfaceRequestor).
  70. getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
  71. };
  72. exports.getOuterId = getOuterId;
  73. /**
  74. * Returns window by the outer window id.
  75. */
  76. const getByOuterId = WM.getOuterWindowWithId;
  77. exports.getByOuterId = getByOuterId;
  78. const getByInnerId = WM.getCurrentInnerWindowWithId;
  79. exports.getByInnerId = getByInnerId;
  80. /**
  81. * Returns `nsIXULWindow` for the given `nsIDOMWindow`.
  82. */
  83. function getXULWindow(window) {
  84. return window.QueryInterface(Ci.nsIInterfaceRequestor).
  85. getInterface(Ci.nsIWebNavigation).
  86. QueryInterface(Ci.nsIDocShellTreeItem).
  87. treeOwner.QueryInterface(Ci.nsIInterfaceRequestor).
  88. getInterface(Ci.nsIXULWindow);
  89. };
  90. exports.getXULWindow = getXULWindow;
  91. function getDOMWindow(xulWindow) {
  92. return xulWindow.QueryInterface(Ci.nsIInterfaceRequestor).
  93. getInterface(Ci.nsIDOMWindow);
  94. }
  95. exports.getDOMWindow = getDOMWindow;
  96. /**
  97. * Returns `nsIBaseWindow` for the given `nsIDOMWindow`.
  98. */
  99. function getBaseWindow(window) {
  100. return window.QueryInterface(Ci.nsIInterfaceRequestor).
  101. getInterface(Ci.nsIWebNavigation).
  102. QueryInterface(Ci.nsIDocShell).
  103. QueryInterface(Ci.nsIDocShellTreeItem).
  104. treeOwner.
  105. QueryInterface(Ci.nsIBaseWindow);
  106. }
  107. exports.getBaseWindow = getBaseWindow;
  108. /**
  109. * Returns the `nsIDOMWindow` toplevel window for any child/inner window
  110. */
  111. function getToplevelWindow(window) {
  112. return window.QueryInterface(Ci.nsIInterfaceRequestor)
  113. .getInterface(Ci.nsIWebNavigation)
  114. .QueryInterface(Ci.nsIDocShellTreeItem)
  115. .rootTreeItem
  116. .QueryInterface(Ci.nsIInterfaceRequestor)
  117. .getInterface(Ci.nsIDOMWindow);
  118. }
  119. exports.getToplevelWindow = getToplevelWindow;
  120. function getWindowDocShell(window) window.gBrowser.docShell;
  121. exports.getWindowDocShell = getWindowDocShell;
  122. function getWindowLoadingContext(window) {
  123. return getWindowDocShell(window).
  124. QueryInterface(Ci.nsILoadContext);
  125. }
  126. exports.getWindowLoadingContext = getWindowLoadingContext;
  127. const isTopLevel = window => window && getToplevelWindow(window) === window;
  128. exports.isTopLevel = isTopLevel;
  129. /**
  130. * Takes hash of options and serializes it to a features string that
  131. * can be used passed to `window.open`. For more details on features string see:
  132. * https://developer.mozilla.org/en/DOM/window.open#Position_and_size_features
  133. */
  134. function serializeFeatures(options) {
  135. return Object.keys(options).reduce(function(result, name) {
  136. let value = options[name];
  137. // the chrome and private features are special
  138. if ((name == 'private' || name == 'chrome'))
  139. return result + ((value === true) ? ',' + name : '');
  140. return result + ',' + name + '=' +
  141. (value === true ? 'yes' : value === false ? 'no' : value);
  142. }, '').substr(1);
  143. }
  144. /**
  145. * Opens a top level window and returns it's `nsIDOMWindow` representation.
  146. * @params {String} uri
  147. * URI of the document to be loaded into window.
  148. * @params {nsIDOMWindow} options.parent
  149. * Used as parent for the created window.
  150. * @params {String} options.name
  151. * Optional name that is assigned to the window.
  152. * @params {Object} options.features
  153. * Map of key, values like: `{ width: 10, height: 15, chrome: true, private: true }`.
  154. */
  155. function open(uri, options) {
  156. uri = uri || URI_BROWSER;
  157. options = options || {};
  158. if (['chrome', 'resource', 'data'].indexOf(io.newURI(uri, null, null).scheme) < 0)
  159. throw new Error('only chrome, resource and data uris are allowed');
  160. let newWindow = windowWatcher.
  161. openWindow(options.parent || null,
  162. uri,
  163. options.name || null,
  164. options.features ? serializeFeatures(options.features) : null,
  165. options.args || null);
  166. return newWindow;
  167. }
  168. exports.open = open;
  169. function onFocus(window) {
  170. let { resolve, promise } = defer();
  171. if (isFocused(window)) {
  172. resolve(window);
  173. }
  174. else {
  175. window.addEventListener("focus", function focusListener() {
  176. window.removeEventListener("focus", focusListener, true);
  177. resolve(window);
  178. }, true);
  179. }
  180. return promise;
  181. }
  182. exports.onFocus = onFocus;
  183. function isFocused(window) {
  184. const FM = Cc["@mozilla.org/focus-manager;1"].
  185. getService(Ci.nsIFocusManager);
  186. let childTargetWindow = {};
  187. FM.getFocusedElementForWindow(window, true, childTargetWindow);
  188. childTargetWindow = childTargetWindow.value;
  189. let focusedChildWindow = {};
  190. if (FM.activeWindow) {
  191. FM.getFocusedElementForWindow(FM.activeWindow, true, focusedChildWindow);
  192. focusedChildWindow = focusedChildWindow.value;
  193. }
  194. return (focusedChildWindow === childTargetWindow);
  195. }
  196. exports.isFocused = isFocused;
  197. /**
  198. * Opens a top level window and returns it's `nsIDOMWindow` representation.
  199. * Same as `open` but with more features
  200. * @param {Object} options
  201. *
  202. */
  203. function openDialog(options) {
  204. options = options || {};
  205. let features = options.features || FEATURES;
  206. let featureAry = features.toLowerCase().split(',');
  207. if (!!options.private) {
  208. // add private flag if private window is desired
  209. if (!array.has(featureAry, 'private')) {
  210. featureAry.push('private');
  211. }
  212. // remove the non-private flag ig a private window is desired
  213. let nonPrivateIndex = featureAry.indexOf('non-private');
  214. if (nonPrivateIndex >= 0) {
  215. featureAry.splice(nonPrivateIndex, 1);
  216. }
  217. features = featureAry.join(',');
  218. }
  219. let browser = getMostRecentBrowserWindow();
  220. // if there is no browser then do nothing
  221. if (!browser)
  222. return undefined;
  223. let newWindow = browser.openDialog.apply(
  224. browser,
  225. array.flatten([
  226. options.url || URI_BROWSER,
  227. options.name || NAME,
  228. features,
  229. options.args || null
  230. ])
  231. );
  232. return newWindow;
  233. }
  234. exports.openDialog = openDialog;
  235. /**
  236. * Returns an array of all currently opened windows.
  237. * Note that these windows may still be loading.
  238. */
  239. function windows(type, options) {
  240. options = options || {};
  241. let list = [];
  242. let winEnum = WM.getEnumerator(type);
  243. while (winEnum.hasMoreElements()) {
  244. let window = winEnum.getNext().QueryInterface(Ci.nsIDOMWindow);
  245. // Only add non-private windows when pb permission isn't set,
  246. // unless an option forces the addition of them.
  247. if (!window.closed && (options.includePrivate || !isWindowPrivate(window))) {
  248. list.push(window);
  249. }
  250. }
  251. return list;
  252. }
  253. exports.windows = windows;
  254. /**
  255. * Check if the given window is interactive.
  256. * i.e. if its "DOMContentLoaded" event has already been fired.
  257. * @params {nsIDOMWindow} window
  258. */
  259. const isInteractive = window =>
  260. window.document.readyState === "interactive" ||
  261. isDocumentLoaded(window) ||
  262. // XUL documents stays '"uninitialized"' until it's `readyState` becomes
  263. // `"complete"`.
  264. isXULDocumentWindow(window) && window.document.readyState === "interactive";
  265. exports.isInteractive = isInteractive;
  266. const isXULDocumentWindow = ({document}) =>
  267. document.documentElement &&
  268. document.documentElement.namespaceURI === XUL_NS;
  269. /**
  270. * Check if the given window is completely loaded.
  271. * i.e. if its "load" event has already been fired and all possible DOM content
  272. * is done loading (the whole DOM document, images content, ...)
  273. * @params {nsIDOMWindow} window
  274. */
  275. function isDocumentLoaded(window) {
  276. return window.document.readyState == "complete";
  277. }
  278. exports.isDocumentLoaded = isDocumentLoaded;
  279. function isBrowser(window) {
  280. try {
  281. return window.document.documentElement.getAttribute("windowtype") === BROWSER;
  282. }
  283. catch (e) {}
  284. return false;
  285. };
  286. exports.isBrowser = isBrowser;
  287. function getWindowTitle(window) {
  288. return window && window.document ? window.document.title : null;
  289. }
  290. exports.getWindowTitle = getWindowTitle;
  291. function isXULBrowser(window) {
  292. return !!(isBrowser(window) && window.XULBrowserWindow);
  293. }
  294. exports.isXULBrowser = isXULBrowser;
  295. /**
  296. * Returns the most recent focused window
  297. */
  298. function getFocusedWindow() {
  299. let window = WM.getMostRecentWindow(BROWSER);
  300. return window ? window.document.commandDispatcher.focusedWindow : null;
  301. }
  302. exports.getFocusedWindow = getFocusedWindow;
  303. /**
  304. * Returns the focused element in the most recent focused window
  305. */
  306. function getFocusedElement() {
  307. let window = WM.getMostRecentWindow(BROWSER);
  308. return window ? window.document.commandDispatcher.focusedElement : null;
  309. }
  310. exports.getFocusedElement = getFocusedElement;
  311. function getFrames(window) {
  312. return Array.slice(window.frames).reduce(function(frames, frame) {
  313. return frames.concat(frame, getFrames(frame));
  314. }, []);
  315. }
  316. exports.getFrames = getFrames;
  317. function getScreenPixelsPerCSSPixel(window) {
  318. return window.QueryInterface(Ci.nsIInterfaceRequestor).
  319. getInterface(Ci.nsIDOMWindowUtils).screenPixelsPerCSSPixel;
  320. }
  321. exports.getScreenPixelsPerCSSPixel = getScreenPixelsPerCSSPixel;
  322. function getOwnerBrowserWindow(node) {
  323. /**
  324. Takes DOM node and returns browser window that contains it.
  325. **/
  326. let window = getToplevelWindow(node.ownerDocument.defaultView);
  327. // If anchored window is browser then it's target browser window.
  328. return isBrowser(window) ? window : null;
  329. }
  330. exports.getOwnerBrowserWindow = getOwnerBrowserWindow;
  331. function getParentWindow(window) {
  332. try {
  333. return window.QueryInterface(Ci.nsIInterfaceRequestor)
  334. .getInterface(Ci.nsIWebNavigation)
  335. .QueryInterface(Ci.nsIDocShellTreeItem).parent
  336. .QueryInterface(Ci.nsIInterfaceRequestor)
  337. .getInterface(Ci.nsIDOMWindow);
  338. }
  339. catch (e) {}
  340. return null;
  341. }
  342. exports.getParentWindow = getParentWindow;
  343. function getParentFrame(window) {
  344. try {
  345. return window.QueryInterface(Ci.nsIInterfaceRequestor)
  346. .getInterface(Ci.nsIWebNavigation)
  347. .QueryInterface(Ci.nsIDocShellTreeItem).parent
  348. .QueryInterface(Ci.nsIInterfaceRequestor)
  349. .getInterface(Ci.nsIDOMWindow);
  350. }
  351. catch (e) {}
  352. return null;
  353. }
  354. exports.getParentWindow = getParentWindow;
  355. // The element in which the window is embedded, or `null`
  356. // if the window is top-level. Similar to `window.frameElement`
  357. // but can cross chrome-content boundries.
  358. const getFrameElement = target =>
  359. (target instanceof Ci.nsIDOMDocument ? target.defaultView : target).
  360. QueryInterface(Ci.nsIInterfaceRequestor).
  361. getInterface(Ci.nsIDOMWindowUtils).
  362. containerElement;
  363. exports.getFrameElement = getFrameElement;