# 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 |
25 | var child = require('child_process'),
26 | fs = require('fs'),
27 | path = require('path'),
28 | util = require('util');
29 |
30 | var 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 */
39 | var 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 */
44 | var 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 |
48 | var X_IGNORE_NO_FOCUS_LIB = 'x_ignore_nofocus.so';
49 |
50 | var 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 | */
58 | function 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 | */
78 | function 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 | */
109 | function 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 | */
144 | var 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 | };
162 | util.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 | */
170 | Binary.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 | */
188 | Binary.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 | */
220 | Binary.prototype.serialize = function() {
221 | return promise.when(this.exe_ || findFirefox());
222 | };
223 |
224 |
225 | // PUBLIC API
226 |
227 |
228 | exports.Binary = Binary;
229 |