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 * Provides a mechanism to configure and launch Firefox in a subprocess for
137 * use with WebDriver.
138 *
139 * @param {string=} opt_exe Path to the Firefox binary to use. If not
140 * specified, will attempt to locate Firefox on the current system.
141 * @constructor
142 * @extends {Serializable.<string>}
143 */
144var Binary = function(opt_exe) {
145 Serializable.call(this);
146
147 /** @private {(string|undefined)} */
148 this.exe_ = opt_exe;
149
150 /** @private {!Array.<string>} */
151 this.args_ = [];
152
153 /** @private {!Object.<string, string>} */
154 this.env_ = {};
155 Object.keys(process.env).forEach(function(key) {
156 this.env_[key] = process.env[key];
157 }.bind(this));
158 this.env_['MOZ_CRASHREPORTER_DISABLE'] = '1';
159 this.env_['MOZ_NO_REMOTE'] = '1';
160 this.env_['NO_EM_RESTART'] = '1';
161};
162util.inherits(Binary, Serializable);
163
164
165/**
166 * Add arguments to the command line used to start Firefox.
167 * @param {...(string|!Array.<string>)} var_args Either the arguments to add as
168 * varargs, or the arguments as an array.
169 */
170Binary.prototype.addArguments = function(var_args) {
171 for (var i = 0; i < arguments.length; i++) {
172 if (util.isArray(arguments[i])) {
173 this.args_ = this.args_.concat(arguments[i]);
174 } else {
175 this.args_.push(arguments[i]);
176 }
177 }
178};
179
180
181/**
182 * Launches Firefox and returns a promise that will be fulfilled when the
183 * process terminates.
184 * @param {string} profile Path to the profile directory to use.
185 * @return {!promise.Promise.<!exec.Command>} A promise for the handle to the
186 * started subprocess.
187 */
188Binary.prototype.launch = function(profile) {
189 var env = {};
190 Object.keys(this.env_).forEach(function(key) {
191 env[key] = this.env_[key];
192 }.bind(this));
193 env['XRE_PROFILE_PATH'] = profile;
194
195 var args = ['-foreground'].concat(this.args_);
196
197 return promise.when(this.exe_ || findFirefox(), function(firefox) {
198 if (process.platform === 'win32' || process.platform === 'darwin') {
199 return exec(firefox, {args: args, env: env});
200 }
201 return installNoFocusLibs(profile).then(function(ldLibraryPath) {
202 env['LD_LIBRARY_PATH'] = ldLibraryPath + ':' + env['LD_LIBRARY_PATH'];
203 env['LD_PRELOAD'] = X_IGNORE_NO_FOCUS_LIB;
204 return exec(firefox, {args: args, env: env});
205 });
206 });
207};
208
209
210/**
211 * Returns a promise for the wire representation of this binary. Note: the
212 * FirefoxDriver only supports passing the path to the binary executable over
213 * the wire; all command line arguments and environment variables will be
214 * discarded.
215 *
216 * @return {!promise.Promise.<string>} A promise for this binary's wire
217 * representation.
218 * @override
219 */
220Binary.prototype.serialize = function() {
221 return promise.when(this.exe_ || findFirefox());
222};
223
224
225// PUBLIC API
226
227
228exports.Binary = Binary;
229