Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(firefox): implement missing launcher options #4036

Merged
merged 4 commits into from Feb 20, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
89 changes: 71 additions & 18 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,
...env,
LD_LIBRARY_PATH: `${path.dirname(executablePath)}:${process.env.LD_LIBRARY_PATH}`,
} : process.env,
} : 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};
2 changes: 1 addition & 1 deletion experimental/puppeteer-firefox/misc/puppeteer.cfg
Expand Up @@ -58,7 +58,7 @@ pref("browser.startup.homepage_override.mstone", "ignore");
pref("toolkit.cosmeticAnimations.enabled", false);

// Do not close the window when the last tab gets closed
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this comment looks wrong now.

pref("browser.tabs.closeWindowWithLastTab", false);
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