UNPKG

browser-launcher2

Version:
379 lines (333 loc) 9.97 kB
var headless = require( 'headless' ), mkdirp = require( 'mkdirp' ), os = require( 'os' ), fs = require( 'fs' ), path = require( 'path' ), Uid = require( 'uid' ), _ = require( 'lodash' ), Instance = require( './instance' ), setups = {}; /** * Setup procedure for Firefox browser: * - create a temporary directory * - create and write prefs.js file * - collect command line arguments necessary to launch the browser * @param {Object} browser Browser object * @param {String} uri URI to be opened * @param {Object} options Configuration options * @param {Function} callback Callback function */ setups.firefox = function( browser, uri, options, callback ) { var uid = Uid( 10 ), tempDir = path.join( os.tmpdir(), 'browser-launcher2' + uid ), file = path.join( tempDir, 'prefs.js' ), prefs = { 'browser.shell.checkDefaultBrowser': false, 'browser.bookmarks.restore_default_bookmarks': false, 'dom.disable_open_during_load': false, 'dom.max_script_run_time': 0 }; mkdirp.sync( tempDir ); options.options = options.options || []; options.tempDir = tempDir; if ( options.proxy ) { var match = /^(?:http:\/\/)?([^:\/]+)(?::(\d+))?/.exec( options.proxy ), host = JSON.stringify( match[ 1 ] ), port = match[ 2 ] || 80; _.extend( prefs, { 'network.proxy.http': host, 'network.proxy.http_port': +port, 'network.proxy.type': 1, 'browser.cache.disk.capacity': 0, 'browser.cache.disk.smart_size.enabled': false, 'browser.cache.disk.smart_size.first_run': false, 'browser.sessionstore.resume_from_crash': false, 'browser.startup.page': 0, 'network.proxy.no_proxies_on': JSON.stringify( options.noProxy || '' ) } ); } if ( options.prefs ) { _.extend( prefs, options.prefs ); } prefs = Object.keys( prefs ).map( function( name ) { return 'user_pref("' + name + '", ' + prefs[ name ] + ');'; } ).join( '\n' ); fs.writeFile( file, prefs, function( err ) { if ( err ) { callback( err ); } else { callback( null, options.options.concat( [ uri, '--no-remote', '-profile', tempDir ] ) ); } } ); }; /** * Setup procedure for IE and Safari browsers: * - pass the URI to the command line arguments * @param {Object} browser Browser object * @param {String} uri URI to be opened * @param {Object} options Configuration options * @param {Function} callback Callback function */ setups.ie = setups.safari = function( browser, uri, options, callback ) { callback( null, [ uri ] ); }; /** * Setup procedure for Chrome browser: * - collect command line arguments necessary to launch the browser * @param {Object} browser Browser object * @param {String} uri URI to be opened * @param {Object} options Configuration options * @param {Function} callback Callback function */ setups.chrome = function( browser, uri, options, callback ) { options.options = options.options || []; callback( null, options.options.concat( [ options.proxy ? '--proxy-server=' + options.proxy : null, browser.profile ? '--user-data-dir=' + browser.profile : null, '--disable-restore-session-state', '--no-default-browser-check', '--disable-popup-blocking', '--disable-translate', '--start-maximized', '--disable-default-apps', '--disable-sync', '--enable-fixed-layout', '--no-first-run', '--noerrdialogs ', uri ] ).filter( Boolean ) ); }; /** * Setup procedure for PhantomJS: * - configure PhantomJS to open res/phantom.js script * @param {Object} browser Browser object * @param {String} uri URI to be opened * @param {Object} options Configuration options * @param {Function} callback Callback function */ setups.phantom = function( browser, uri, options, callback ) { var phantomScript = path.join( __dirname, '../res/phantom.js' ); options.options = options.options || []; callback( null, options.options.concat( [ options.proxy ? '--proxy=' + options.proxy.replace( /^http:\/\//, '' ) : null, phantomScript, uri ] ).filter( Boolean ) ); }; /** * Setup procedure for Opera browser: * - copy the default preferences file depending on the Opera version * (res/operaprefs.ini or res/Preferences) to the profile directory * - collect command line arguments necessary to launch the browser * @param {Object} browser Browser object * @param {String} uri URI to be opened * @param {Object} options Configuration options * @param {Function} callback Callback function */ setups.opera = function( browser, uri, options, callback ) { var prefs = { old: 'operaprefs.ini', blink: 'Preferences' }, engine = { old: [ // browser.profile ? '-pd ' + browser.profile : null, // disabled temporarily '-nosession', '-nomail', uri ], // using the same rules as for chrome blink: [ browser.profile ? '--user-data-dir=' + browser.profile : null, '--disable-restore-session-state', '--no-default-browser-check', '--disable-popup-blocking', '--disable-translate', '--start-maximized', '--disable-default-apps', '--disable-sync', '--enable-fixed-layout', '--no-first-run', '--noerrdialogs', uri ] }, prefFile = prefs[ major( browser.version ) >= 15 ? 'blink' : 'old' ], src = path.join( __dirname, '../res/' + prefFile ), dest = path.join( browser.profile, prefFile ); options.options = options.options || []; copy( src, dest, function( err ) { if ( err ) { callback( err ); } else { callback( null, options.options.concat( engine[ major( browser.version ) >= 15 ? 'blink' : 'old' ] ).filter( Boolean ) ); } } ); }; /** * Run a browser * @param {Object} config Configuration object * @param {String} name Browser name * @param {String} version Browser version * @return {Function} */ module.exports = function runBrowser( config, name, version ) { var browser = findMatch( config, name, version ); if ( !browser ) { return; } return function( uri, options, callback ) { // run a regular browser in a "headless" mode if ( options.headless && !browser.headless ) { headless( function( err, proc, display ) { if ( err ) { return callback( err ); } run( { DISPLAY: ':' + display } ); } ); } else { run( {} ); } function run( customEnv ) { var env = {}, cwd = process.cwd(); // copy environment variables Object.keys( process.env ).forEach( function( key ) { env[ key ] = process.env[ key ]; } ); Object.keys( customEnv ).forEach( function( key ) { env[ key ] = customEnv[ key ]; } ); // setup the browser setups[ browser.type ]( browser, uri, options, function( err, args ) { if ( err ) { return callback( err ); } // pass proxy configuration to the new environment if ( options.noProxy && env.no_proxy === undefined ) { env.no_proxy = options.noProxy; } if ( options.proxy && env.http_proxy === undefined ) { env.http_proxy = options.proxy; } if ( options.proxy && env.HTTP_PROXY === undefined ) { env.HTTP_PROXY = options.proxy; } // prepare the launch command for Windows systems if ( process.platform === 'win32' ) { // ensure all the quotes are removed browser.command = browser.command.replace( /"/g, '' ); // change directory to the app's base (Chrome) cwd = require( 'path' ).dirname( browser.command ); } // prepare the launch command for OSX systems if ( process.platform === 'darwin' ) { // use the binary paths under the hood if ( browser.name !== 'firefox' && browser.name !== 'phantomjs' ) { // open --wait-apps --new --fresh -a /Path/To/Executable <url> --args <rest of app args> args.unshift( '--wait-apps', '--new', '--fresh', '-a', browser.command, args.pop(), '--args' ); browser.processName = browser.command; browser.command = 'open'; } } browser.tempDir = options.tempDir; callback( null, new Instance( _.extend( {}, browser, { args: args, detached: options.detached, env: env, cwd: cwd } ) ) ); } ); } }; }; /** * Copy a file * @param {String} src Source path * @param {String} dest Destination path * @param {Function} callback Completion callback */ function copy( src, dest, callback ) { var rs = fs.createReadStream( src ), ws = fs.createWriteStream( dest ), called = false; function done( err ) { if ( !called ) { called = true; callback( err ); } } rs.on( 'error', done ); ws.on( 'error', done ); ws.on( 'close', function() { done(); } ); rs.pipe( ws ); } /** * Get the major version * @param {String} version Version string * @return {Number} */ function major( version ) { return +version.split( '.' )[ 0 ]; } /** * In the given configuration find a browser matching specified name and version * @param {Object} config Configuration object * @param {String} name Browser name * @param {String} version Browser version * @return {Object} */ function findMatch( config, name, version ) { var matching = config.browsers.filter( function( b ) { return b.name === name && matches( b.version, version ); } ).sort( function( a, b ) { return major( b.version ) - major( a.version ); } ); if ( matching.length ) { return matching[ 0 ]; } } /** * Check if the given version matches the pattern * @param {String} version Browser version string * @param {String} [pattern] Expected version pattern * @return {Boolean} */ function matches( version, pattern ) { if ( pattern === undefined || pattern === '*' ) { return true; } var vs = version.split( '.' ), ps = pattern.split( '.' ), i; for ( i = 0; i < ps.length; i++ ) { if ( ps[ i ] === 'x' || ps[ i ] === '*' ) { continue; } if ( ps[ i ] !== vs[ i ] ) { return false; } } return true; }