binary.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. // Licensed to the Software Freedom Conservancy (SFC) under one
  2. // or more contributor license agreements. See the NOTICE file
  3. // distributed with this work for additional information
  4. // regarding copyright ownership. The SFC licenses this file
  5. // to you under the Apache License, Version 2.0 (the
  6. // "License"); you may not use this file except in compliance
  7. // with the License. You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing,
  12. // software distributed under the License is distributed on an
  13. // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  14. // KIND, either express or implied. See the License for the
  15. // specific language governing permissions and limitations
  16. // under the License.
  17. /**
  18. * @fileoverview Manages Firefox binaries. This module is considered internal;
  19. * users should use {@link ./firefox selenium-webdriver/firefox}.
  20. */
  21. 'use strict';
  22. const child = require('child_process'),
  23. fs = require('fs'),
  24. path = require('path'),
  25. util = require('util');
  26. const isDevMode = require('../lib/devmode'),
  27. Symbols = require('../lib/symbols'),
  28. io = require('../io'),
  29. exec = require('../io/exec');
  30. /** @const */
  31. const NO_FOCUS_LIB_X86 = isDevMode ?
  32. path.join(__dirname, '../../../../cpp/prebuilt/i386/libnoblur.so') :
  33. path.join(__dirname, '../lib/firefox/i386/libnoblur.so') ;
  34. /** @const */
  35. const NO_FOCUS_LIB_AMD64 = isDevMode ?
  36. path.join(__dirname, '../../../../cpp/prebuilt/amd64/libnoblur64.so') :
  37. path.join(__dirname, '../lib/firefox/amd64/libnoblur64.so') ;
  38. const X_IGNORE_NO_FOCUS_LIB = 'x_ignore_nofocus.so';
  39. /**
  40. * @param {string} file Path to the file to find, relative to the program files
  41. * root.
  42. * @return {!Promise<?string>} A promise for the located executable.
  43. * The promise will resolve to {@code null} if Firefox was not found.
  44. */
  45. function findInProgramFiles(file) {
  46. let files = [
  47. process.env['PROGRAMFILES'] || 'C:\\Program Files',
  48. process.env['PROGRAMFILES(X86)'] || 'C:\\Program Files (x86)'
  49. ].map(prefix => path.join(prefix, file));
  50. return io.exists(files[0]).then(function(exists) {
  51. return exists ? files[0] : io.exists(files[1]).then(function(exists) {
  52. return exists ? files[1] : null;
  53. });
  54. });
  55. }
  56. /**
  57. * Provides methods for locating the executable for a Firefox release channel
  58. * on Windows and MacOS. For other systems (i.e. Linux), Firefox will always
  59. * be located on the system PATH.
  60. *
  61. * @final
  62. */
  63. class Channel {
  64. /**
  65. * @param {string} darwin The path to check when running on MacOS.
  66. * @param {string} win32 The path to check when running on Windows.
  67. */
  68. constructor(darwin, win32) {
  69. /** @private @const */ this.darwin_ = darwin;
  70. /** @private @const */ this.win32_ = win32;
  71. /** @private {Promise<string>} */
  72. this.found_ = null;
  73. }
  74. /**
  75. * Attempts to locate the Firefox executable for this release channel. This
  76. * will first check the default installation location for the channel before
  77. * checking the user's PATH. The returned promise will be rejected if Firefox
  78. * can not be found.
  79. *
  80. * @return {!Promise<string>} A promise for the location of the located
  81. * Firefox executable.
  82. */
  83. locate() {
  84. if (this.found_) {
  85. return this.found_;
  86. }
  87. let found;
  88. switch (process.platform) {
  89. case 'darwin':
  90. found = io.exists(this.darwin_)
  91. .then(exists => exists ? this.darwin_ : io.findInPath('firefox'));
  92. break;
  93. case 'win32':
  94. found = findInProgramFiles(this.win32_)
  95. .then(found => found || io.findInPath('firefox.exe'));
  96. break;
  97. default:
  98. found = Promise.resolve(io.findInPath('firefox'));
  99. break;
  100. }
  101. this.found_ = found.then(found => {
  102. if (found) {
  103. // TODO: verify version info.
  104. return found;
  105. }
  106. throw Error('Could not locate Firefox on the current system');
  107. });
  108. return this.found_;
  109. }
  110. }
  111. /**
  112. * Firefox's developer channel.
  113. * @const
  114. * @see <https://www.mozilla.org/en-US/firefox/channel/desktop/#aurora>
  115. */
  116. Channel.AURORA = new Channel(
  117. '/Applications/FirefoxDeveloperEdition.app/Contents/MacOS/firefox-bin',
  118. 'Firefox Developer Edition\\firefox.exe');
  119. /**
  120. * Firefox's beta channel. Note this is provided mainly for convenience as
  121. * the beta channel has the same installation location as the main release
  122. * channel.
  123. * @const
  124. * @see <https://www.mozilla.org/en-US/firefox/channel/desktop/#beta>
  125. */
  126. Channel.BETA = new Channel(
  127. '/Applications/Firefox.app/Contents/MacOS/firefox-bin',
  128. 'Mozilla Firefox\\firefox.exe');
  129. /**
  130. * Firefox's release channel.
  131. * @const
  132. * @see <https://www.mozilla.org/en-US/firefox/desktop/>
  133. */
  134. Channel.RELEASE = new Channel(
  135. '/Applications/Firefox.app/Contents/MacOS/firefox-bin',
  136. 'Mozilla Firefox\\firefox.exe');
  137. /**
  138. * Firefox's nightly release channel.
  139. * @const
  140. * @see <https://www.mozilla.org/en-US/firefox/channel/desktop/#nightly>
  141. */
  142. Channel.NIGHTLY = new Channel(
  143. '/Applications/FirefoxNightly.app/Contents/MacOS/firefox-bin',
  144. 'Nightly\\firefox.exe');
  145. /**
  146. * Copies the no focus libs into the given profile directory.
  147. * @param {string} profileDir Path to the profile directory to install into.
  148. * @return {!Promise<string>} The LD_LIBRARY_PATH prefix string to use
  149. * for the installed libs.
  150. */
  151. function installNoFocusLibs(profileDir) {
  152. var x86 = path.join(profileDir, 'x86');
  153. var amd64 = path.join(profileDir, 'amd64');
  154. return io.mkdir(x86)
  155. .then(() => copyLib(NO_FOCUS_LIB_X86, x86))
  156. .then(() => io.mkdir(amd64))
  157. .then(() => copyLib(NO_FOCUS_LIB_AMD64, amd64))
  158. .then(function() {
  159. return x86 + ':' + amd64;
  160. });
  161. function copyLib(src, dir) {
  162. return io.copy(src, path.join(dir, X_IGNORE_NO_FOCUS_LIB));
  163. }
  164. }
  165. /**
  166. * Provides a mechanism to configure and launch Firefox in a subprocess for
  167. * use with WebDriver.
  168. *
  169. * If created _without_ a path for the Firefox binary to use, this class will
  170. * attempt to find Firefox when {@link #launch()} is called. For MacOS and
  171. * Windows, this class will look for Firefox in the current platform's default
  172. * installation location (e.g. /Applications/Firefox.app on MacOS). For all
  173. * other platforms, the Firefox executable must be available on your system
  174. * `PATH`.
  175. *
  176. * @final
  177. * @deprecated This class will be removed in 4.0. Use the binary management
  178. * functions available on the {@link ./index.Options firefox.Options} class.
  179. */
  180. class Binary {
  181. /**
  182. * @param {?(string|Channel)=} opt_exeOrChannel Either the path to a specific
  183. * Firefox binary to use, or a {@link Channel} instance that describes
  184. * how to locate the desired Firefox version.
  185. */
  186. constructor(opt_exeOrChannel) {
  187. /** @private {?(string|Channel)} */
  188. this.exe_ = opt_exeOrChannel || null;
  189. /** @private {!Array.<string>} */
  190. this.args_ = [];
  191. /** @private {!Object<string, string>} */
  192. this.env_ = {};
  193. Object.assign(this.env_, process.env, {
  194. MOZ_CRASHREPORTER_DISABLE: '1',
  195. MOZ_NO_REMOTE: '1',
  196. NO_EM_RESTART: '1'
  197. });
  198. /** @private {boolean} */
  199. this.devEdition_ = false;
  200. }
  201. /**
  202. * @return {(string|undefined)} The path to the Firefox executable to use, or
  203. * `undefined` if WebDriver should attempt to locate Firefox automatically
  204. * on the current system.
  205. */
  206. getExe() {
  207. return typeof this.exe_ === 'string' ? this.exe_ : undefined;
  208. }
  209. /**
  210. * Add arguments to the command line used to start Firefox.
  211. * @param {...(string|!Array.<string>)} var_args Either the arguments to add
  212. * as varargs, or the arguments as an array.
  213. * @deprecated Use {@link ./index.Options#addArguments}.
  214. */
  215. addArguments(var_args) {
  216. for (var i = 0; i < arguments.length; i++) {
  217. if (Array.isArray(arguments[i])) {
  218. this.args_ = this.args_.concat(arguments[i]);
  219. } else {
  220. this.args_.push(arguments[i]);
  221. }
  222. }
  223. }
  224. /**
  225. * @return {!Array<string>} The command line arguments to use when starting
  226. * the browser.
  227. */
  228. getArguments() {
  229. return this.args_;
  230. }
  231. /**
  232. * Specifies whether to use Firefox Developer Edition instead of the normal
  233. * stable channel. Setting this option has no effect if this instance was
  234. * created with a path to a specific Firefox binary.
  235. *
  236. * This method has no effect on Unix systems where the Firefox application
  237. * has the same (default) name regardless of version.
  238. *
  239. * @param {boolean=} opt_use Whether to use the developer edition. Defaults to
  240. * true.
  241. * @deprecated Use the {@link Channel} class to indicate the desired Firefox
  242. * version when creating a new binary: `new Binary(Channel.AURORA)`.
  243. */
  244. useDevEdition(opt_use) {
  245. this.devEdition_ = opt_use === undefined || !!opt_use;
  246. }
  247. /**
  248. * Returns a promise for the Firefox executable used by this instance. The
  249. * returned promise will be immediately resolved if the user supplied an
  250. * executable path when this instance was created. Otherwise, an attempt will
  251. * be made to find Firefox on the current system.
  252. *
  253. * @return {!Promise<string>} a promise for the path to the Firefox executable
  254. * used by this instance.
  255. */
  256. locate() {
  257. if (typeof this.exe_ === 'string') {
  258. return Promise.resolve(this.exe_);
  259. } else if (this.exe_ instanceof Channel) {
  260. return this.exe_.locate();
  261. }
  262. let channel = this.devEdition_ ? Channel.AURORA : Channel.RELEASE;
  263. return channel.locate();
  264. }
  265. /**
  266. * Launches Firefox and returns a promise that will be fulfilled when the
  267. * process terminates.
  268. * @param {string} profile Path to the profile directory to use.
  269. * @return {!Promise<!exec.Command>} A promise for the handle to the started
  270. * subprocess.
  271. */
  272. launch(profile) {
  273. let env = {};
  274. Object.assign(env, this.env_, {XRE_PROFILE_PATH: profile});
  275. let args = ['-foreground'].concat(this.args_);
  276. return this.locate().then(function(firefox) {
  277. if (process.platform === 'win32' || process.platform === 'darwin') {
  278. return exec(firefox, {args: args, env: env});
  279. }
  280. return installNoFocusLibs(profile).then(function(ldLibraryPath) {
  281. env['LD_LIBRARY_PATH'] = ldLibraryPath + ':' + env['LD_LIBRARY_PATH'];
  282. env['LD_PRELOAD'] = X_IGNORE_NO_FOCUS_LIB;
  283. return exec(firefox, {args: args, env: env});
  284. });
  285. });
  286. }
  287. /**
  288. * Returns a promise for the wire representation of this binary. Note: the
  289. * FirefoxDriver only supports passing the path to the binary executable over
  290. * the wire; all command line arguments and environment variables will be
  291. * discarded.
  292. *
  293. * @return {!Promise<string>} A promise for this binary's wire representation.
  294. */
  295. [Symbols.serialize]() {
  296. return this.locate();
  297. }
  298. }
  299. // PUBLIC API
  300. exports.Binary = Binary;
  301. exports.Channel = Channel;