firefox/binary.js

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 selenium-webdriver/firefox}.
21 */
22
23'use strict';
24
25var child = require('child_process'),
26 fs = require('fs'),
27 path = require('path'),
28 util = require('util');
29
30var Serializable = require('..').Serializable,
31 promise = require('..').promise,
32 _base = require('../_base'),
33 io = require('../io'),
34 exec = require('../io/exec');
35
36
37
38/** @const */
39var NO_FOCUS_LIB_X86 = _base.isDevMode() ?
40 path.join(__dirname, '../../../../cpp/prebuilt/i386/libnoblur.so') :
41 path.join(__dirname, '../lib/firefox/i386/libnoblur.so') ;
42
43/** @const */
44var NO_FOCUS_LIB_AMD64 = _base.isDevMode() ?
45 path.join(__dirname, '../../../../cpp/prebuilt/amd64/libnoblur64.so') :
46 path.join(__dirname, '../lib/firefox/amd64/libnoblur64.so') ;
47
48var X_IGNORE_NO_FOCUS_LIB = 'x_ignore_nofocus.so';
49
50var foundBinary = null;
51
52
53/**
54 * Checks the default Windows Firefox locations in Program Files.
55 * @return {!promise.Promise.<?string>} A promise for the located executable.
56 * The promise will resolve to {@code null} if Fireox was not found.
57 */
58function defaultWindowsLocation() {
59 var files = [
60 process.env['PROGRAMFILES'] || 'C:\\Program Files',
61 process.env['PROGRAMFILES(X86)'] || 'C:\\Program Files (x86)'
62 ].map(function(prefix) {
63 return path.join(prefix, 'Mozilla Firefox\\firefox.exe');
64 });
65 return io.exists(files[0]).then(function(exists) {
66 return exists ? files[0] : io.exists(files[1]).then(function(exists) {
67 return exists ? files[1] : null;
68 });
69 });
70}
71
72
73/**
74 * Locates the Firefox binary for the current system.
75 * @return {!promise.Promise.<string>} A promise for the located binary. The
76 * promise will be rejected if Firefox cannot be located.
77 */
78function findFirefox() {
79 if (foundBinary) {
80 return foundBinary;
81 }
82
83 if (process.platform === 'darwin') {
84 var osxExe = '/Applications/Firefox.app/Contents/MacOS/firefox-bin';
85 foundBinary = io.exists(osxExe).then(function(exists) {
86 return exists ? osxExe : null;
87 });
88 } else if (process.platform === 'win32') {
89 foundBinary = defaultWindowsLocation();
90 } else {
91 foundBinary = promise.fulfilled(io.findInPath('firefox'));
92 }
93
94 return foundBinary = foundBinary.then(function(found) {
95 if (found) {
96 return found;
97 }
98 throw Error('Could not locate Firefox on the current system');
99 });
100}
101
102
103/**
104 * Copies the no focus libs into the given profile directory.
105 * @param {string} profileDir Path to the profile directory to install into.
106 * @return {!promise.Promise.<string>} The LD_LIBRARY_PATH prefix string to use
107 * for the installed libs.
108 */
109function installNoFocusLibs(profileDir) {
110 var x86 = path.join(profileDir, 'x86');
111 var amd64 = path.join(profileDir, 'amd64');
112
113 return mkdir(x86)
114 .then(copyLib.bind(null, NO_FOCUS_LIB_X86, x86))
115 .then(mkdir.bind(null, amd64))
116 .then(copyLib.bind(null, NO_FOCUS_LIB_AMD64, amd64))
117 .then(function() {
118 return x86 + ':' + amd64;
119 });
120
121 function mkdir(dir) {
122 return io.exists(dir).then(function(exists) {
123 if (!exists) {
124 return promise.checkedNodeCall(fs.mkdir, dir);
125 }
126 });
127 }
128
129 function copyLib(src, dir) {
130 return io.copy(src, path.join(dir, X_IGNORE_NO_FOCUS_LIB));
131 }
132}
133
134
135/**
136 * Manages a Firefox subprocess configured for use with WebDriver.
137 *
138 * @param {string=} opt_exe Path to the Firefox binary to use. If not
139 * specified, will attempt to locate Firefox on the current system.
140 * @constructor
141 * @extends {Serializable.<string>}
142 */
143var Binary = function(opt_exe) {
144 Serializable.call(this);
145
146 /** @private {(string|undefined)} */
147 this.exe_ = opt_exe;
148
149 /** @private {!Array.<string>} */
150 this.args_ = [];
151
152 /** @private {!Object.<string, string>} */
153 this.env_ = {};
154 Object.keys(process.env).forEach(function(key) {
155 this.env_[key] = process.env[key];
156 }.bind(this));
157 this.env_['MOZ_CRASHREPORTER_DISABLE'] = '1';
158 this.env_['MOZ_NO_REMOTE'] = '1';
159 this.env_['NO_EM_RESTART'] = '1';
160
161 /** @private {promise.Promise.<!exec.Command>} */
162 this.command_ = null;
163};
164util.inherits(Binary, Serializable);
165
166
167/**
168 * Add arguments to the command line used to start Firefox.
169 * @param {...(string|!Array.<string>)} var_args Either the arguments to add as
170 * varargs, or the arguments as an array.
171 */
172Binary.prototype.addArguments = function(var_args) {
173 for (var i = 0; i < arguments.length; i++) {
174 if (util.isArray(arguments[i])) {
175 this.args_ = this.args_.concat(arguments[i]);
176 } else {
177 this.args_.push(arguments[i]);
178 }
179 }
180};
181
182
183/**
184 * Launches Firefox and eturns a promise that will be fulfilled when the process
185 * terminates.
186 * @param {string} profile Path to the profile directory to use.
187 * @return {!promise.Promise.<!exec.Result>} A promise for the process result.
188 * @throws {Error} If this instance has already been started.
189 */
190Binary.prototype.launch = function(profile) {
191 if (this.command_) {
192 throw Error('Firefox is already running');
193 }
194
195 var env = {};
196 Object.keys(this.env_).forEach(function(key) {
197 env[key] = this.env_[key];
198 }.bind(this));
199 env['XRE_PROFILE_PATH'] = profile;
200
201 var args = ['-foreground'].concat(this.args_);
202
203 this.command_ = promise.when(this.exe_ || findFirefox(), function(firefox) {
204 if (process.platform === 'win32' || process.platform === 'darwin') {
205 return exec(firefox, {args: args, env: env});
206 }
207 return installNoFocusLibs(profile).then(function(ldLibraryPath) {
208 env['LD_LIBRARY_PATH'] = ldLibraryPath + ':' + env['LD_LIBRARY_PATH'];
209 env['LD_PRELOAD'] = X_IGNORE_NO_FOCUS_LIB;
210 return exec(firefox, {args: args, env: env});
211 });
212 });
213
214 return this.command_.then(function() {
215 // Don't return the actual command handle, just a promise to signal it has
216 // been started.
217 });
218};
219
220
221/**
222 * Kills the managed Firefox process.
223 * @return {!promise.Promise} A promise for when the process has terminated.
224 */
225Binary.prototype.kill = function() {
226 if (!this.command_) {
227 return promise.defer(); // Not running.
228 }
229 return this.command_.then(function(command) {
230 command.kill();
231 return command.result();
232 });
233};
234
235
236/**
237 * Returns a promise for the wire representation of this binary. Note: the
238 * FirefoxDriver only supports passing the path to the binary executable over
239 * the wire; all command line arguments and environment variables will be
240 * discarded.
241 *
242 * @return {!promise.Promise.<string>} A promise for this binary's wire
243 * representation.
244 * @override
245 */
246Binary.prototype.serialize = function() {
247 return promise.when(this.exe_ || findFirefox());
248};
249
250
251// PUBLIC API
252
253
254exports.Binary = Binary;
255