# firefox/binary.js

    1// Copyright 2014 Selenium committers2// Copyright 2014 Software Freedom Conservancy3//4// Licensed under the Apache License, Version 2.0 (the "License");5// you may not use this file except in compliance with the License.6//     You may obtain a copy of the License at7//8// http://www.apache.org/licenses/LICENSE-2.09//10// Unless required by applicable law or agreed to in writing, software11// distributed under the License is distributed on an "AS IS" BASIS,12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.13// See the License for the specific language governing permissions and14// limitations under the License.1516'use strict';1718var child = require('child_process'),19    fs = require('fs'),20    path = require('path'),21    util = require('util');2223var promise = require('..').promise,24    _base = require('../_base'),25    io = require('../io'),26    exec = require('../io/exec');27282930/** @const */31var NO_FOCUS_LIB_X86 = _base.isDevMode() ?32    path.join(__dirname, '../../../../cpp/prebuilt/i386/libnoblur.so') :33    path.join(__dirname, '../lib/firefox/i386/libnoblur.so') ;3435/** @const */36var NO_FOCUS_LIB_AMD64 = _base.isDevMode() ?37    path.join(__dirname, '../../../../cpp/prebuilt/amd64/libnoblur64.so') :38    path.join(__dirname, '../lib/firefox/amd64/libnoblur64.so') ;3940var X_IGNORE_NO_FOCUS_LIB = 'x_ignore_nofocus.so';4142var foundBinary = null;434445/**46 * Checks the default Windows Firefox locations in Program Files.47 * @return {!promise.Promise.<?string>} A promise for the located executable.48 *     The promise will resolve to {@code null} if Fireox was not found.49 */50function defaultWindowsLocation() {51  var files = [52    process.env['PROGRAMFILES'] || 'C:\\Program Files',53    process.env['PROGRAMFILES(X86)'] || 'C:\\Program Files (x86)'54  ].map(function(prefix) {55    return path.join(prefix, 'Mozilla Firefox\\firefox.exe');56  });57  return io.exists(files[0]).then(function(exists) {58    return exists ? files[0] : io.exists(files[1]).then(function(exists) {59      return exists ? files[1] : null;60    });61  });62}636465/**66 * Locates the Firefox binary for the current system.67 * @return {!promise.Promise.<string>} A promise for the located binary. The68 *     promise will be rejected if Firefox cannot be located.69 */70function findFirefox() {71  if (foundBinary) {72    return foundBinary;73  }7475  if (process.platform === 'darwin') {76    var osxExe =  '/Applications/Firefox.app/Contents/MacOS/firefox-bin';77    foundBinary = io.exists(osxExe).then(function(exists) {78      return exists ? osxExe : null;79    });80  } else if (process.platform === 'win32') {81    foundBinary = defaultWindowsLocation();82  } else {83    foundBinary = promise.fulfilled(io.findInPath('firefox'));84  }8586  return foundBinary = foundBinary.then(function(found) {87    if (found) {88      return found;89    }90    throw Error('Could not locate Firefox on the current system');91  });92}939495/**96 * Copies the no focus libs into the given profile directory.97 * @param {string} profileDir Path to the profile directory to install into.98 * @return {!promise.Promise.<string>} The LD_LIBRARY_PATH prefix string to use99 *     for the installed libs.100 */101function installNoFocusLibs(profileDir) {102  var x86 = path.join(profileDir, 'x86');103  var amd64 = path.join(profileDir, 'amd64');104105  return mkdir(x86)106      .then(copyLib.bind(null, NO_FOCUS_LIB_X86, x86))107      .then(mkdir.bind(null, amd64))108      .then(copyLib.bind(null, NO_FOCUS_LIB_AMD64, amd64))109      .then(function() {110        return x86 + ':' + amd64;111      });112113  function mkdir(dir) {114    return io.exists(dir).then(function(exists) {115      if (!exists) {116        return promise.checkedNodeCall(fs.mkdir, dir);117      }118    });119  }120121  function copyLib(src, dir) {122    return io.copy(src, path.join(dir, X_IGNORE_NO_FOCUS_LIB));123  }124}125126127/**128 * Silently runs Firefox to install a profile directory (which is assumed to be129 * defined in the given environment variables).130 * @param {string} firefox Path to the Firefox executable.131 * @param {!Object.<string, string>} env The environment variables to use.132 * @return {!promise.Promise} A promise for when the profile has been installed.133 */134function installProfile(firefox, env) {135  var installed = promise.defer();136  child.exec(firefox + ' -silent', {env: env, timeout: 180 * 1000},137      function(err) {138        if (err) {139          installed.reject(new Error(140              'Failed to install Firefox profile: ' + err));141          return;142        }143        installed.fulfill();144      });145  return installed.promise;146}147148149/**150 * Manages a Firefox subprocess configured for use with WebDriver.151 * @param {string=} opt_exe Path to the Firefox binary to use. If not152 *     specified, will attempt to locate Firefox on the current system.153 * @constructor154 */155var Binary = function(opt_exe) {156  /** @private {(string|undefined)} */157  this.exe_ = opt_exe;158159  /** @private {!Array.<string>} */160  this.args_ = [];161162  /** @private {!Object.<string, string>} */163  this.env_ = {};164  Object.keys(process.env).forEach(function(key) {165    this.env_[key] = process.env[key];166  }.bind(this));167  this.env_['MOZ_CRASHREPORTER_DISABLE'] = '1';168  this.env_['MOZ_NO_REMOTE'] = '1';169  this.env_['NO_EM_RESTART'] = '1';170171  /** @private {promise.Promise.<!exec.Command>} */172  this.command_ = null;173};174175176/**177 * Add arguments to the command line used to start Firefox.178 * @param {...(string|!Array.<string>)} var_args Either the arguments to add as179 *     varargs, or the arguments as an array.180 */181Binary.prototype.addArguments = function(var_args) {182  for (var i = 0; i < arguments.length; i++) {183    if (util.isArray(arguments[i])) {184      this.args_ = this.args_.concat(arguments[i]);185    } else {186      this.args_.push(arguments[i]);187    }188  }189};190191192/**193 * Launches Firefox and eturns a promise that will be fulfilled when the process194 * terminates.195 * @param {string} profile Path to the profile directory to use.196 * @return {!promise.Promise.<!exec.Result>} A promise for the process result.197 * @throws {Error} If this instance has already been started.198 */199Binary.prototype.launch = function(profile) {200  if (this.command_) {201    throw Error('Firefox is already running');202  }203204  var env = {};205  Object.keys(this.env_).forEach(function(key) {206    env[key] = this.env_[key];207  }.bind(this));208  env['XRE_PROFILE_PATH'] = profile;209210  var args = ['-foreground'].concat(this.args_);211212  var self = this;213214  this.command_ = promise.when(this.exe_ || findFirefox(), function(firefox) {215    if (process.platform === 'win32' || process.platform === 'darwin') {216      return firefox;217    }218    return installNoFocusLibs(profile).then(function(ldLibraryPath) {219      env['LD_LIBRARY_PATH'] = ldLibraryPath + ':' + env['LD_LIBRARY_PATH'];220      env['LD_PRELOAD'] = X_IGNORE_NO_FOCUS_LIB;221      return firefox;222    });223  }).then(function(firefox) {224    var install = exec(firefox, {args: ['-silent'], env: env});225    return install.result().then(function(result) {226      if (result.code !== 0) {227        throw Error(228            'Failed to install profile; firefox terminated with ' + result);229      }230231      return exec(firefox, {args: args, env: env});232    });233  });234235  return this.command_.then(function() {236    // Don't return the actual command handle, just a promise to signal it has237    // been started.238  });239};240241242/**243 * Kills the managed Firefox process.244 * @return {!promise.Promise} A promise for when the process has terminated.245 */246Binary.prototype.kill = function() {247  if (!this.command_) {248    return promise.defer();  // Not running.249  }250  return this.command_.then(function(command) {251    command.kill();252    return command.result();253  });254};255256257// PUBLIC API258259260exports.Binary = Binary;261