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 | /**
|
19 | * @fileoverview Manages Firefox binaries. This module is considered internal;
|
20 | * users should use {@link ./firefox selenium-webdriver/firefox}.
|
21 | */
|
22 |
|
23 | ;
|
24 |
|
25 | const child = require('child_process'),
|
26 | fs = require('fs'),
|
27 | path = require('path'),
|
28 | util = require('util');
|
29 |
|
30 | const isDevMode = require('../lib/devmode'),
|
31 | Symbols = require('../lib/symbols'),
|
32 | io = require('../io'),
|
33 | exec = require('../io/exec');
|
34 |
|
35 |
|
36 |
|
37 | /** @const */
|
38 | const NO_FOCUS_LIB_X86 = isDevMode ?
|
39 | path.join(__dirname, '../../../../cpp/prebuilt/i386/libnoblur.so') :
|
40 | path.join(__dirname, '../lib/firefox/i386/libnoblur.so') ;
|
41 |
|
42 | /** @const */
|
43 | const NO_FOCUS_LIB_AMD64 = isDevMode ?
|
44 | path.join(__dirname, '../../../../cpp/prebuilt/amd64/libnoblur64.so') :
|
45 | path.join(__dirname, '../lib/firefox/amd64/libnoblur64.so') ;
|
46 |
|
47 | const X_IGNORE_NO_FOCUS_LIB = 'x_ignore_nofocus.so';
|
48 |
|
49 |
|
50 | let foundBinary = null;
|
51 | let foundDevBinary = null;
|
52 |
|
53 |
|
54 | /**
|
55 | * Checks the default Windows Firefox locations in Program Files.
|
56 | *
|
57 | * @param {boolean=} opt_dev Whether to find the Developer Edition.
|
58 | * @return {!Promise<?string>} A promise for the located executable.
|
59 | * The promise will resolve to {@code null} if Firefox was not found.
|
60 | */
|
61 | function defaultWindowsLocation(opt_dev) {
|
62 | var files = [
|
63 | process.env['PROGRAMFILES'] || 'C:\\Program Files',
|
64 | process.env['PROGRAMFILES(X86)'] || 'C:\\Program Files (x86)'
|
65 | ].map(function(prefix) {
|
66 | if (opt_dev) {
|
67 | return path.join(prefix, 'Firefox Developer Edition\\firefox.exe');
|
68 | }
|
69 | return path.join(prefix, 'Mozilla Firefox\\firefox.exe');
|
70 | });
|
71 | return io.exists(files[0]).then(function(exists) {
|
72 | return exists ? files[0] : io.exists(files[1]).then(function(exists) {
|
73 | return exists ? files[1] : null;
|
74 | });
|
75 | });
|
76 | }
|
77 |
|
78 |
|
79 | /**
|
80 | * Locates the Firefox binary for the current system.
|
81 | *
|
82 | * @param {boolean=} opt_dev Whether to find the Developer Edition. This only
|
83 | * used on Windows and OSX.
|
84 | * @return {!Promise<string>} A promise for the located binary. The promise will
|
85 | * be rejected if Firefox cannot be located.
|
86 | */
|
87 | function findFirefox(opt_dev) {
|
88 | if (opt_dev && foundDevBinary) {
|
89 | return foundDevBinary;
|
90 | }
|
91 |
|
92 | if (!opt_dev && foundBinary) {
|
93 | return foundBinary;
|
94 | }
|
95 |
|
96 | let found;
|
97 | if (process.platform === 'darwin') {
|
98 | let exe = opt_dev
|
99 | ? '/Applications/FirefoxDeveloperEdition.app/Contents/MacOS/firefox-bin'
|
100 | : '/Applications/Firefox.app/Contents/MacOS/firefox-bin';
|
101 | found = io.exists(exe).then(exists => exists ? exe : null);
|
102 |
|
103 | } else if (process.platform === 'win32') {
|
104 | found = defaultWindowsLocation(opt_dev);
|
105 |
|
106 | } else {
|
107 | found = Promise.resolve(io.findInPath('firefox'));
|
108 | }
|
109 |
|
110 | found = found.then(found => {
|
111 | if (found) {
|
112 | return found;
|
113 | }
|
114 | throw Error('Could not locate Firefox on the current system');
|
115 | });
|
116 |
|
117 | if (opt_dev) {
|
118 | return foundDevBinary = found;
|
119 | } else {
|
120 | return foundBinary = found;
|
121 | }
|
122 | }
|
123 |
|
124 |
|
125 | /**
|
126 | * Copies the no focus libs into the given profile directory.
|
127 | * @param {string} profileDir Path to the profile directory to install into.
|
128 | * @return {!Promise<string>} The LD_LIBRARY_PATH prefix string to use
|
129 | * for the installed libs.
|
130 | */
|
131 | function installNoFocusLibs(profileDir) {
|
132 | var x86 = path.join(profileDir, 'x86');
|
133 | var amd64 = path.join(profileDir, 'amd64');
|
134 |
|
135 | return io.mkdir(x86)
|
136 | .then(() => copyLib(NO_FOCUS_LIB_X86, x86))
|
137 | .then(() => io.mkdir(amd64))
|
138 | .then(() => copyLib(NO_FOCUS_LIB_AMD64, amd64))
|
139 | .then(function() {
|
140 | return x86 + ':' + amd64;
|
141 | });
|
142 |
|
143 | function copyLib(src, dir) {
|
144 | return io.copy(src, path.join(dir, X_IGNORE_NO_FOCUS_LIB));
|
145 | }
|
146 | }
|
147 |
|
148 |
|
149 | /**
|
150 | * Provides a mechanism to configure and launch Firefox in a subprocess for
|
151 | * use with WebDriver.
|
152 | *
|
153 | * If created _without_ a path for the Firefox binary to use, this class will
|
154 | * attempt to find Firefox when {@link #launch()} is called. For OSX and
|
155 | * Windows, this class will look for Firefox in the current platform's default
|
156 | * installation location (e.g. /Applications/Firefox.app on OSX). For all other
|
157 | * platforms, the Firefox executable must be available on your system `PATH`.
|
158 | *
|
159 | * @final
|
160 | */
|
161 | class Binary {
|
162 | /**
|
163 | * @param {string=} opt_exe Path to the Firefox binary to use.
|
164 | */
|
165 | constructor(opt_exe) {
|
166 | /** @private {(string|undefined)} */
|
167 | this.exe_ = opt_exe;
|
168 |
|
169 | /** @private {!Array.<string>} */
|
170 | this.args_ = [];
|
171 |
|
172 | /** @private {!Object<string, string>} */
|
173 | this.env_ = {};
|
174 | Object.assign(this.env_, process.env, {
|
175 | MOZ_CRASHREPORTER_DISABLE: '1',
|
176 | MOZ_NO_REMOTE: '1',
|
177 | NO_EM_RESTART: '1'
|
178 | });
|
179 |
|
180 | /** @private {boolean} */
|
181 | this.devEdition_ = false;
|
182 | }
|
183 |
|
184 | /**
|
185 | * @return {(string|undefined)} The path to the Firefox executable to use, or
|
186 | * `undefined` if WebDriver should attempt to locate Firefox automatically
|
187 | * on the current system.
|
188 | */
|
189 | getExe() {
|
190 | return this.exe_;
|
191 | }
|
192 |
|
193 | /**
|
194 | * Add arguments to the command line used to start Firefox.
|
195 | * @param {...(string|!Array.<string>)} var_args Either the arguments to add
|
196 | * as varargs, or the arguments as an array.
|
197 | */
|
198 | addArguments(var_args) {
|
199 | for (var i = 0; i < arguments.length; i++) {
|
200 | if (Array.isArray(arguments[i])) {
|
201 | this.args_ = this.args_.concat(arguments[i]);
|
202 | } else {
|
203 | this.args_.push(arguments[i]);
|
204 | }
|
205 | }
|
206 | }
|
207 |
|
208 | /**
|
209 | * @return {!Array<string>} The command line arguments to use when starting
|
210 | * the browser.
|
211 | */
|
212 | getArguments() {
|
213 | return this.args_;
|
214 | }
|
215 |
|
216 | /**
|
217 | * Specifies whether to use Firefox Developer Edition instead of the normal
|
218 | * stable channel. Setting this option has no effect if this instance was
|
219 | * created with a path to a specific Firefox binary.
|
220 | *
|
221 | * This method has no effect on Unix systems where the Firefox application
|
222 | * has the same (default) name regardless of version.
|
223 | *
|
224 | * @param {boolean=} opt_use Whether to use the developer edition. Defaults to
|
225 | * true.
|
226 | */
|
227 | useDevEdition(opt_use) {
|
228 | this.devEdition_ = opt_use === undefined || !!opt_use;
|
229 | }
|
230 |
|
231 | /**
|
232 | * Returns a promise for the Firefox executable used by this instance. The
|
233 | * returned promise will be immediately resolved if the user supplied an
|
234 | * executable path when this instance was created. Otherwise, an attempt will
|
235 | * be made to find Firefox on the current system.
|
236 | *
|
237 | * @return {!Promise<string>} a promise for the path to the Firefox executable
|
238 | * used by this instance.
|
239 | */
|
240 | locate() {
|
241 | return Promise.resolve(this.exe_ || findFirefox(this.devEdition_));
|
242 | }
|
243 |
|
244 | /**
|
245 | * Launches Firefox and returns a promise that will be fulfilled when the
|
246 | * process terminates.
|
247 | * @param {string} profile Path to the profile directory to use.
|
248 | * @return {!Promise<!exec.Command>} A promise for the handle to the started
|
249 | * subprocess.
|
250 | */
|
251 | launch(profile) {
|
252 | let env = {};
|
253 | Object.assign(env, this.env_, {XRE_PROFILE_PATH: profile});
|
254 |
|
255 | let args = ['-foreground'].concat(this.args_);
|
256 |
|
257 | return this.locate().then(function(firefox) {
|
258 | if (process.platform === 'win32' || process.platform === 'darwin') {
|
259 | return exec(firefox, {args: args, env: env});
|
260 | }
|
261 | return installNoFocusLibs(profile).then(function(ldLibraryPath) {
|
262 | env['LD_LIBRARY_PATH'] = ldLibraryPath + ':' + env['LD_LIBRARY_PATH'];
|
263 | env['LD_PRELOAD'] = X_IGNORE_NO_FOCUS_LIB;
|
264 | return exec(firefox, {args: args, env: env});
|
265 | });
|
266 | });
|
267 | }
|
268 |
|
269 | /**
|
270 | * Returns a promise for the wire representation of this binary. Note: the
|
271 | * FirefoxDriver only supports passing the path to the binary executable over
|
272 | * the wire; all command line arguments and environment variables will be
|
273 | * discarded.
|
274 | *
|
275 | * @return {!Promise<string>} A promise for this binary's wire representation.
|
276 | */
|
277 | [Symbols.serialize]() {
|
278 | return this.locate();
|
279 | }
|
280 | }
|
281 |
|
282 |
|
283 | // PUBLIC API
|
284 |
|
285 |
|
286 | exports.Binary = Binary;
|
287 |
|