Skip to content

Commit

Permalink
feat(firefox): implement missing launcher options (puppeteer#4036)
Browse files Browse the repository at this point in the history
This patch implements `puppeteer.defaultArgs()` and also missing
options, such as `ignoreDefaultArgs`.
  • Loading branch information
aslushnikov authored and kiku-jw committed Apr 6, 2019
1 parent 9a4e20b commit 4cbf200
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 130 deletions.
91 changes: 72 additions & 19 deletions experimental/puppeteer-firefox/lib/Launcher.js
Expand Up @@ -32,6 +32,11 @@ const removeFolderAsync = util.promisify(removeFolder);

const FIREFOX_PROFILE_PATH = path.join(os.tmpdir(), 'puppeteer_firefox_profile-');

const DEFAULT_ARGS = [
'-no-remote',
'-foreground',
];

/**
* @internal
*/
Expand All @@ -41,41 +46,71 @@ class Launcher {
this._preferredRevision = preferredRevision;
}

defaultArgs(options = {}) {
const {
headless = true,
args = [],
userDataDir = null,
} = options;
const firefoxArguments = [...DEFAULT_ARGS];
if (userDataDir)
firefoxArguments.push('-profile', userDataDir);
if (headless)
firefoxArguments.push('-headless');
firefoxArguments.push(...args);
if (args.every(arg => arg.startsWith('-')))
firefoxArguments.push('about:blank');
return firefoxArguments;
}

/**
* @param {Object} options
* @return {!Promise<!Browser>}
*/
async launch(options = {}) {
const {
ignoreDefaultArgs = false,
args = [],
dumpio = false,
executablePath = this.executablePath(),
executablePath = null,
env = process.env,
handleSIGHUP = true,
handleSIGINT = true,
handleSIGTERM = true,
ignoreHTTPSErrors = false,
headless = true,
defaultViewport = {width: 800, height: 600},
slowMo = 0,
timeout = 30000,
} = options;

const firefoxArguments = args.slice();
firefoxArguments.push('-no-remote');
firefoxArguments.push('-juggler', '0');
firefoxArguments.push('-foreground');
if (headless)
firefoxArguments.push('-headless');
const firefoxArguments = [];
if (!ignoreDefaultArgs)
firefoxArguments.push(...this.defaultArgs(options));
else if (Array.isArray(ignoreDefaultArgs))
firefoxArguments.push(...this.defaultArgs(options).filter(arg => !ignoreDefaultArgs.includes(arg)));
else
firefoxArguments.push(...args);

if (!firefoxArguments.includes('-juggler'))
firefoxArguments.push('-juggler', '0');

let temporaryProfileDir = null;
if (!firefoxArguments.some(arg => arg.startsWith('-profile') || arg.startsWith('--profile'))) {
if (!firefoxArguments.includes('-profile') && !firefoxArguments.includes('--profile')) {
temporaryProfileDir = await mkdtempAsync(FIREFOX_PROFILE_PATH);
firefoxArguments.push(`-profile`, temporaryProfileDir);
}
if (firefoxArguments.every(arg => arg.startsWith('--') || arg.startsWith('-')))
firefoxArguments.push('about:blank');

let firefoxExecutable = executablePath;
if (!firefoxExecutable) {
const {missingText, executablePath} = this._resolveExecutablePath();
if (missingText)
throw new Error(missingText);
firefoxExecutable = executablePath;
}
const stdio = ['pipe', 'pipe', 'pipe'];
const firefoxProcess = childProcess.spawn(
executablePath,
firefoxExecutable,
firefoxArguments,
{
// On non-windows platforms, `detached: false` makes child process a leader of a new
Expand All @@ -85,9 +120,9 @@ class Launcher {
stdio,
// On linux Juggler ships the libstdc++ it was linked against.
env: os.platform() === 'linux' ? {
...process.env,
LD_LIBRARY_PATH: `${path.dirname(executablePath)}:${process.env.LD_LIBRARY_PATH}`,
} : process.env,
...env,
LD_LIBRARY_PATH: `${path.dirname(firefoxExecutable)}:${process.env.LD_LIBRARY_PATH}`,
} : env,
}
);

Expand Down Expand Up @@ -115,16 +150,16 @@ class Launcher {
if (handleSIGINT)
listeners.push(helper.addEventListener(process, 'SIGINT', () => { killFirefox(); process.exit(130); }));
if (handleSIGTERM)
listeners.push(helper.addEventListener(process, 'SIGTERM', killFirefox));
listeners.push(helper.addEventListener(process, 'SIGTERM', gracefullyCloseFirefox));
if (handleSIGHUP)
listeners.push(helper.addEventListener(process, 'SIGHUP', killFirefox));
listeners.push(helper.addEventListener(process, 'SIGHUP', gracefullyCloseFirefox));
/** @type {?Connection} */
let connection = null;
try {
const url = await waitForWSEndpoint(firefoxProcess, 30000);
const url = await waitForWSEndpoint(firefoxProcess, timeout);
const transport = await WebSocketTransport.create(url);
connection = new Connection(url, transport, slowMo);
const browser = await Browser.create(connection, defaultViewport, firefoxProcess, killFirefox);
const browser = await Browser.create(connection, defaultViewport, firefoxProcess, gracefullyCloseFirefox);
if (ignoreHTTPSErrors)
await connection.send('Browser.setIgnoreHTTPSErrors', {enabled: true});
await browser.waitForTarget(t => t.type() === 'page');
Expand All @@ -134,6 +169,19 @@ class Launcher {
throw e;
}

function gracefullyCloseFirefox() {
helper.removeEventListeners(listeners);
if (temporaryProfileDir) {
killFirefox();
} else if (connection) {
connection.send('Browser.close').catch(error => {
debugError(error);
killFirefox();
});
}
return waitForFirefoxToClose;
}

// This method has to be sync to be used as 'exit' event handler.
function killFirefox() {
helper.removeEventListeners(listeners);
Expand Down Expand Up @@ -179,9 +227,14 @@ class Launcher {
* @return {string}
*/
executablePath() {
return this._resolveExecutablePath().executablePath;
}

_resolveExecutablePath() {
const browserFetcher = new BrowserFetcher(this._projectRoot, { product: 'firefox' });
const revisionInfo = browserFetcher.revisionInfo(this._preferredRevision);
return revisionInfo.executablePath;
const missingText = !revisionInfo.local ? `Firefox revision is not downloaded. Run "npm install" or "yarn install"` : null;
return {executablePath: revisionInfo.executablePath, missingText};
}
}

Expand Down
1 change: 1 addition & 0 deletions experimental/puppeteer-firefox/lib/NavigationWatchdog.js
Expand Up @@ -62,6 +62,7 @@ class NavigationWatchdog {

const check = this._checkNavigationComplete.bind(this);
this._eventListeners = [
helper.addEventListener(session, Events.JugglerSession.Disconnected, () => this._resolveCallback(new Error('Navigation failed because browser has disconnected!'))),
helper.addEventListener(session, 'Page.eventFired', check),
helper.addEventListener(session, 'Page.frameAttached', check),
helper.addEventListener(session, 'Page.frameDetached', check),
Expand Down
4 changes: 4 additions & 0 deletions experimental/puppeteer-firefox/lib/Puppeteer.js
Expand Up @@ -26,6 +26,10 @@ class Puppeteer {
executablePath() {
return this._launcher.executablePath();
}

defaultArgs(options) {
return this._launcher.defaultArgs(options);
}
}

module.exports = {Puppeteer};
4 changes: 2 additions & 2 deletions experimental/puppeteer-firefox/misc/puppeteer.cfg
Expand Up @@ -57,8 +57,8 @@ pref("browser.startup.homepage_override.mstone", "ignore");
// Disable browser animations (tabs, fullscreen, sliding alerts)
pref("toolkit.cosmeticAnimations.enabled", false);
// Do not close the window when the last tab gets closed
pref("browser.tabs.closeWindowWithLastTab", false);
// Close the window when the last tab gets closed
pref("browser.tabs.closeWindowWithLastTab", true);
// Do not allow background tabs to be zombified on Android, otherwise for
// tests that open additional tabs, the test harness tab itself might get
Expand Down
21 changes: 1 addition & 20 deletions lib/Launcher.js
Expand Up @@ -174,32 +174,13 @@ class Launcher {
connection = new Connection('', transport, slowMo);
}
const browser = await Browser.create(connection, [], ignoreHTTPSErrors, defaultViewport, chromeProcess, gracefullyCloseChrome);
await ensureInitialPage(browser);
await browser.waitForTarget(t => t.type() === 'page');
return browser;
} catch (e) {
killChrome();
throw e;
}

/**
* @param {!Browser} browser
*/
async function ensureInitialPage(browser) {
// Wait for initial page target to be created.
if (browser.targets().find(target => target.type() === 'page'))
return;

let initialPageCallback;
const initialPagePromise = new Promise(resolve => initialPageCallback = resolve);
const listeners = [helper.addEventListener(browser, 'targetcreated', target => {
if (target.type() === 'page')
initialPageCallback();
})];

await initialPagePromise;
helper.removeEventListeners(listeners);
}

/**
* @return {Promise}
*/
Expand Down

0 comments on commit 4cbf200

Please sign in to comment.