[api] Launcher: Close gracefully when a userDataDir is specified (#700)

This patch:
- makes `browser.close()` return a promise that resolves when browser gets closed
- starts closing chrome gracefully if a custom `userDataDir` is supplied

Fixes #527
This commit is contained in:
JoelEinbinder 2017-09-13 21:27:14 -07:00 committed by Andrey Lushnikov
parent d7e673645a
commit f398e69dbb
5 changed files with 41 additions and 16 deletions

View File

@ -211,6 +211,7 @@ puppeteer.launch().then(async browser => {
``` ```
#### browser.close() #### browser.close()
- returns: <[Promise]>
Closes browser with all the pages (if any were opened). The browser object itself is considered to be disposed and could not be used anymore. Closes browser with all the pages (if any were opened). The browser object itself is considered to be disposed and could not be used anymore.

View File

@ -16,14 +16,16 @@
const {helper} = require('./helper'); const {helper} = require('./helper');
const Page = require('./Page'); const Page = require('./Page');
const EventEmitter = require('events');
class Browser { class Browser extends EventEmitter {
/** /**
* @param {!Connection} connection * @param {!Connection} connection
* @param {boolean} ignoreHTTPSErrors * @param {boolean} ignoreHTTPSErrors
* @param {function()=} closeCallback * @param {function():Promise=} closeCallback
*/ */
constructor(connection, ignoreHTTPSErrors, closeCallback) { constructor(connection, ignoreHTTPSErrors, closeCallback) {
super();
this._ignoreHTTPSErrors = ignoreHTTPSErrors; this._ignoreHTTPSErrors = ignoreHTTPSErrors;
this._screenshotTaskQueue = new TaskQueue(); this._screenshotTaskQueue = new TaskQueue();
this._connection = connection; this._connection = connection;
@ -54,9 +56,9 @@ class Browser {
return version.product; return version.product;
} }
close() { async close() {
this._connection.dispose(); this._connection.dispose();
this._closeCallback.call(null); await this._closeCallback.call(null);
} }
} }

View File

@ -84,9 +84,14 @@ class Launcher {
chromeProcess.stderr.pipe(process.stderr); chromeProcess.stderr.pipe(process.stderr);
} }
const waitForChromeToClose = new Promise(fulfill => {
chromeProcess.once('close', () => {
// Cleanup as processes exit. // Cleanup as processes exit.
if (temporaryUserDataDir) if (temporaryUserDataDir)
chromeProcess.once('close', () => removeSync(temporaryUserDataDir)); removeSync(temporaryUserDataDir);
fulfill();
});
});
const listeners = [ helper.addEventListener(process, 'exit', killChrome) ]; const listeners = [ helper.addEventListener(process, 'exit', killChrome) ];
if (options.handleSIGINT !== false) if (options.handleSIGINT !== false)
@ -102,12 +107,29 @@ class Launcher {
throw e; throw e;
} }
/**
* @return {Promise}
*/
function killChrome() { function killChrome() {
helper.removeEventListeners(listeners); helper.removeEventListeners(listeners);
if (temporaryUserDataDir) {
// Force kill chrome.
if (process.platform === 'win32') if (process.platform === 'win32')
childProcess.execSync(`taskkill /pid ${chromeProcess.pid} /T /F`); childProcess.execSync(`taskkill /pid ${chromeProcess.pid} /T /F`);
else else
process.kill(-chromeProcess.pid); process.kill(-chromeProcess.pid, 'SIGKILL');
// Attempt to remove temporary profile directory to avoid littering.
try {
removeSync(temporaryUserDataDir);
} catch (e) { }
} else {
// Terminate chrome gracefully.
if (process.platform === 'win32')
chromeProcess.kill();
else
process.kill(-chromeProcess.pid, 'SIGTERM');
}
return waitForChromeToClose;
} }
} }

View File

@ -107,7 +107,7 @@ describe('Puppeteer', function() {
const options = Object.assign({userDataDir}, defaultBrowserOptions); const options = Object.assign({userDataDir}, defaultBrowserOptions);
const browser = await puppeteer.launch(options); const browser = await puppeteer.launch(options);
expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0); expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0);
browser.close(); await browser.close();
expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0); expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0);
rm(userDataDir); rm(userDataDir);
})); }));
@ -117,7 +117,7 @@ describe('Puppeteer', function() {
options.args = [`--user-data-dir=${userDataDir}`].concat(options.args); options.args = [`--user-data-dir=${userDataDir}`].concat(options.args);
const browser = await puppeteer.launch(options); const browser = await puppeteer.launch(options);
expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0); expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0);
browser.close(); await browser.close();
expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0); expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0);
rm(userDataDir); rm(userDataDir);
})); }));

View File

@ -165,9 +165,9 @@ function checkDuplicates(doc) {
classes.add(cls.name); classes.add(cls.name);
const members = new Set(); const members = new Set();
for (const member of cls.membersArray) { for (const member of cls.membersArray) {
if (members.has(member.name)) if (members.has(member.type + ' ' + member.name))
errors.push(`Duplicate declaration of method ${cls.name}.${member.name}()`); errors.push(`Duplicate declaration of ${member.type} ${cls.name}.${member.name}()`);
members.add(member.name); members.add(member.type + ' ' + member.name);
const args = new Set(); const args = new Set();
for (const arg of member.argsArray) { for (const arg of member.argsArray) {
if (args.has(arg.name)) if (args.has(arg.name))