UNPKG

6.68 kBJavaScriptView Raw
1"use strict";
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6exports.addJob = addJob;
7exports.removeJob = removeJob;
8exports.launchBrowserIfNeeded = launchBrowserIfNeeded;
9exports.closeBrowser = closeBrowser;
10exports.restartBrowser = restartBrowser;
11exports.browserIsRunning = browserIsRunning;
12exports.getOpenBrowserPage = getOpenBrowserPage;
13exports.closeBrowserPage = closeBrowserPage;
14
15var _puppeteer = _interopRequireDefault(require("puppeteer"));
16
17var _debug = _interopRequireDefault(require("debug"));
18
19function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
20
21const debuglog = (0, _debug.default)('penthouse:browser'); // shared between penthouse calls
22
23let browser = null;
24let _browserLaunchPromise = null;
25let reusableBrowserPages = []; // keep track of when we can close the browser penthouse uses;
26// kept open by continuous use
27
28let ongoingJobs = 0;
29
30function addJob() {
31 ongoingJobs = ongoingJobs + 1;
32}
33
34function removeJob() {
35 ongoingJobs = ongoingJobs - 1;
36}
37
38const DEFAULT_PUPPETEER_LAUNCH_ARGS = ['--disable-setuid-sandbox', '--no-sandbox', '--ignore-certificate-errors' // better for Docker:
39// https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md#tips
40// (however caused memory leaks in Penthouse when testing in Ubuntu, hence disabled)
41// '--disable-dev-shm-usage'
42];
43
44async function launchBrowserIfNeeded({
45 getBrowser,
46 width,
47 height
48}) {
49 if (browser) {
50 return;
51 }
52
53 const usingCustomGetBrowser = getBrowser && typeof getBrowser === 'function';
54
55 if (usingCustomGetBrowser && !_browserLaunchPromise) {
56 debuglog('using browser provided via getBrowser option');
57 _browserLaunchPromise = Promise.resolve(getBrowser());
58 }
59
60 if (!_browserLaunchPromise) {
61 debuglog('no browser instance, launching new browser..');
62 _browserLaunchPromise = _puppeteer.default.launch({
63 args: DEFAULT_PUPPETEER_LAUNCH_ARGS,
64 ignoreHTTPSErrors: true,
65 defaultViewport: {
66 width,
67 height
68 }
69 });
70 }
71
72 _browserLaunchPromise.then(async browser => {
73 debuglog('browser ready');
74 const browserPages = await browser.pages();
75
76 if (browserPages.length > 0) {
77 debuglog('re-using the page browser launched with');
78 browserPages.forEach(Page => {
79 if (!reusableBrowserPages.includes(Page)) {
80 Page.notSetupForPenthouse = true;
81 reusableBrowserPages.push(Page);
82 } else {
83 debuglog('ignoring browser page already inside reusableBrowserPages');
84 }
85 });
86 }
87
88 return browser;
89 });
90
91 browser = await _browserLaunchPromise;
92 _browserLaunchPromise = null;
93}
94
95async function closeBrowser({
96 forceClose,
97 unstableKeepBrowserAlive
98}) {
99 if (browser && (forceClose || !unstableKeepBrowserAlive)) {
100 if (ongoingJobs > 0) {
101 debuglog('keeping browser open as ongoingJobs: ' + ongoingJobs);
102 } else if (browser && browser.close) {
103 browser.close();
104 browser = null;
105 _browserLaunchPromise = null;
106 debuglog('closed browser');
107 }
108 }
109}
110
111async function restartBrowser({
112 getBrowser,
113 width,
114 height
115}) {
116 let browserPages;
117
118 if (browser) {
119 browserPages = await browser.pages();
120 }
121
122 debuglog('restartBrowser called' + browser && browserPages && '\n_browserPagesOpen: ' + browserPages.length); // for some reason Chromium is no longer opened;
123 // perhaps it crashed
124
125 if (_browserLaunchPromise) {
126 // in this case the browser is already restarting
127 await _browserLaunchPromise; // if getBrowser is specified the user is managing the puppeteer browser themselves,
128 // so we do nothing.
129 } else if (!getBrowser) {
130 console.log('now restarting chrome after crash');
131 browser = null;
132 await launchBrowserIfNeeded({
133 width,
134 height
135 });
136 }
137}
138
139async function browserIsRunning() {
140 try {
141 // will throw 'Not opened' error if browser is not running
142 await browser.version();
143 return true;
144 } catch (e) {
145 return false;
146 }
147}
148
149async function getOpenBrowserPage() {
150 const browserPages = await browser.pages(); // if any re-usable pages to use, avoid unnecessary page open/close calls
151
152 if (reusableBrowserPages.length > 0) {
153 debuglog('re-using browser page for generateCriticalCss, remaining at: ' + browserPages.length);
154 const reusedPage = reusableBrowserPages.pop();
155 let reused = true; // if we haven't yet run any penthouse jobs with this page,
156 // don't consider it reused - i.e. it will need to be configured.
157
158 if (reusedPage.notSetupForPenthouse) {
159 reused = false; // but only once
160
161 delete reusedPage.notSetupForPenthouse;
162 }
163
164 return Promise.resolve({
165 page: reusedPage,
166 reused
167 });
168 }
169
170 debuglog('adding browser page for generateCriticalCss, before adding was: ' + browserPages.length);
171 return browser.newPage().then(page => {
172 return {
173 page
174 };
175 });
176}
177
178async function closeBrowserPage({
179 page,
180 error,
181 unstableKeepBrowserAlive,
182 unstableKeepOpenPages
183}) {
184 if (!browser || !page) {
185 return;
186 }
187
188 const browserPages = await browser.pages();
189 debuglog('remove (maybe) browser page for generateCriticalCss, before removing was: ' + browserPages.length);
190 const badErrors = ['Target closed', 'Page crashed'];
191
192 if (page && !(error && badErrors.some(badError => error.toString().indexOf(badError) > -1))) {
193 // Without try/catch if error penthouse will crash if error here,
194 // and wont restart properly
195 try {
196 // must await here, otherwise will receive errors if closing
197 // browser before page is properly closed,
198 // however in unstableKeepBrowserAlive browser is never closed by penthouse.
199 if (unstableKeepBrowserAlive) {
200 if (unstableKeepOpenPages !== 'all' && browserPages.length > unstableKeepOpenPages) {
201 page.close();
202 } else {
203 debuglog('saving page for re-use, instead of closing');
204
205 if (error) {
206 // When a penthouse job execution errors,
207 // in some conditions when later re-use the page
208 // certain methods don't work,
209 // such as Page.setUserAgent never resolving.
210 // "resetting" the page by navigation to about:blank first fixes this.
211 debuglog('Reset page first..');
212 await page.goto('about:blank').then(() => {
213 debuglog('... page reset DONE');
214 });
215 }
216
217 reusableBrowserPages.push(page);
218 }
219 } else {
220 debuglog('now try to close browser page');
221 await page.close();
222 }
223 } catch (err) {
224 debuglog('failed to close browser page (ignoring)');
225 }
226 }
227}
\No newline at end of file