UNPKG

react-h2p-puppeteer

Version:

just the puppeteer package used in react-h2p

638 lines (602 loc) 16.5 kB
/** * Copyright 2017 Google Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ const { helper, assert } = require('./helper'); const {Target} = require('./Target'); const EventEmitter = require('events'); const {TaskQueue} = require('./TaskQueue'); class Browser extends EventEmitter { /** * @param {!Puppeteer.Connection} connection * @param {!Array<string>} contextIds * @param {boolean} ignoreHTTPSErrors * @param {?Puppeteer.Viewport} defaultViewport * @param {?Puppeteer.ChildProcess} process * @param {(function():Promise)=} closeCallback */ constructor(connection, contextIds, ignoreHTTPSErrors, defaultViewport, process, closeCallback) { super(); this._ignoreHTTPSErrors = ignoreHTTPSErrors; this._defaultViewport = defaultViewport; this._process = process; this._screenshotTaskQueue = new TaskQueue(); this._connection = connection; this._closeCallback = closeCallback || new Function(); this._defaultContext = new BrowserContext(this, null); /** @type {Map<string, BrowserContext>} */ this._contexts = new Map(); for (const contextId of contextIds) this._contexts.set(contextId, new BrowserContext(this, contextId)); /** @type {Map<string, Target>} */ this._targets = new Map(); this._connection.setClosedCallback(() => { this.emit(Browser.Events.Disconnected); }); this._connection.on('Target.targetCreated', this._targetCreated.bind(this)); this._connection.on('Target.targetDestroyed', this._targetDestroyed.bind(this)); this._connection.on('Target.targetInfoChanged', this._targetInfoChanged.bind(this)); } /** * @return {?Puppeteer.ChildProcess} */ process() { return this._process; } /** * @return {!Promise<!BrowserContext>} */ /* async */ createIncognitoBrowserContext() {return (fn => { const gen = fn.call(this); return new Promise((resolve, reject) => { function step(key, arg) { let info, value; try { info = gen[key](arg); value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then( value => { step('next', value); }, err => { step('throw', err); }); } } return step('next'); }); })(function*(){ const {browserContextId} = (yield this._connection.send('Target.createBrowserContext')); const context = new BrowserContext(this, browserContextId); this._contexts.set(browserContextId, context); return context; });} /** * @return {!Array<!BrowserContext>} */ browserContexts() { return [this._defaultContext, ...Array.from(this._contexts.values())]; } /** * @param {?string} contextId */ /* async */ _disposeContext(contextId) {return (fn => { const gen = fn.call(this); return new Promise((resolve, reject) => { function step(key, arg) { let info, value; try { info = gen[key](arg); value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then( value => { step('next', value); }, err => { step('throw', err); }); } } return step('next'); }); })(function*(){ (yield this._connection.send('Target.disposeBrowserContext', {browserContextId: contextId || undefined})); this._contexts.delete(contextId); });} /** * @param {!Puppeteer.Connection} connection * @param {!Array<string>} contextIds * @param {boolean} ignoreHTTPSErrors * @param {?Puppeteer.Viewport} defaultViewport * @param {?Puppeteer.ChildProcess} process * @param {function()=} closeCallback */ static /* async */ create(connection, contextIds, ignoreHTTPSErrors, defaultViewport, process, closeCallback) {return (fn => { const gen = fn.call(this); return new Promise((resolve, reject) => { function step(key, arg) { let info, value; try { info = gen[key](arg); value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then( value => { step('next', value); }, err => { step('throw', err); }); } } return step('next'); }); })(function*(){ const browser = new Browser(connection, contextIds, ignoreHTTPSErrors, defaultViewport, process, closeCallback); (yield connection.send('Target.setDiscoverTargets', {discover: true})); return browser; });} /** * @param {!Protocol.Target.targetCreatedPayload} event */ /* async */ _targetCreated(event) {return (fn => { const gen = fn.call(this); return new Promise((resolve, reject) => { function step(key, arg) { let info, value; try { info = gen[key](arg); value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then( value => { step('next', value); }, err => { step('throw', err); }); } } return step('next'); }); })(function*(){ const targetInfo = event.targetInfo; const {browserContextId} = targetInfo; const context = (browserContextId && this._contexts.has(browserContextId)) ? this._contexts.get(browserContextId) : this._defaultContext; const target = new Target(targetInfo, context, () => this._connection.createSession(targetInfo), this._ignoreHTTPSErrors, this._defaultViewport, this._screenshotTaskQueue); assert(!this._targets.has(event.targetInfo.targetId), 'Target should not exist before targetCreated'); this._targets.set(event.targetInfo.targetId, target); if ((yield target._initializedPromise)) { this.emit(Browser.Events.TargetCreated, target); context.emit(BrowserContext.Events.TargetCreated, target); } });} /** * @param {{targetId: string}} event */ /* async */ _targetDestroyed(event) {return (fn => { const gen = fn.call(this); return new Promise((resolve, reject) => { function step(key, arg) { let info, value; try { info = gen[key](arg); value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then( value => { step('next', value); }, err => { step('throw', err); }); } } return step('next'); }); })(function*(){ const target = this._targets.get(event.targetId); target._initializedCallback(false); this._targets.delete(event.targetId); target._closedCallback(); if ((yield target._initializedPromise)) { this.emit(Browser.Events.TargetDestroyed, target); target.browserContext().emit(BrowserContext.Events.TargetDestroyed, target); } });} /** * @param {!Protocol.Target.targetInfoChangedPayload} event */ _targetInfoChanged(event) { const target = this._targets.get(event.targetInfo.targetId); assert(target, 'target should exist before targetInfoChanged'); const previousURL = target.url(); const wasInitialized = target._isInitialized; target._targetInfoChanged(event.targetInfo); if (wasInitialized && previousURL !== target.url()) { this.emit(Browser.Events.TargetChanged, target); target.browserContext().emit(BrowserContext.Events.TargetChanged, target); } } /** * @return {string} */ wsEndpoint() { return this._connection.url(); } /** * @return {!Promise<!Puppeteer.Page>} */ /* async */ newPage() {return (fn => { const gen = fn.call(this); return new Promise((resolve, reject) => { function step(key, arg) { let info, value; try { info = gen[key](arg); value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then( value => { step('next', value); }, err => { step('throw', err); }); } } return step('next'); }); })(function*(){ return this._defaultContext.newPage(); });} /** * @param {string} contextId * @return {!Promise<!Puppeteer.Page>} */ /* async */ _createPageInContext(contextId) {return (fn => { const gen = fn.call(this); return new Promise((resolve, reject) => { function step(key, arg) { let info, value; try { info = gen[key](arg); value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then( value => { step('next', value); }, err => { step('throw', err); }); } } return step('next'); }); })(function*(){ const {targetId} = (yield this._connection.send('Target.createTarget', {url: 'about:blank', browserContextId: contextId || undefined})); const target = (yield this._targets.get(targetId)); assert((yield target._initializedPromise), 'Failed to create target for page'); const page = (yield target.page()); return page; });} /** * @return {!Array<!Target>} */ targets() { return Array.from(this._targets.values()).filter(target => target._isInitialized); } /** * @return {!Promise<!Array<!Puppeteer.Page>>} */ /* async */ pages() {return (fn => { const gen = fn.call(this); return new Promise((resolve, reject) => { function step(key, arg) { let info, value; try { info = gen[key](arg); value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then( value => { step('next', value); }, err => { step('throw', err); }); } } return step('next'); }); })(function*(){ const contextPages = (yield Promise.all(this.browserContexts().map(context => context.pages()))); // Flatten array. return contextPages.reduce((acc, x) => acc.concat(x), []); });} /** * @return {!Promise<string>} */ /* async */ version() {return (fn => { const gen = fn.call(this); return new Promise((resolve, reject) => { function step(key, arg) { let info, value; try { info = gen[key](arg); value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then( value => { step('next', value); }, err => { step('throw', err); }); } } return step('next'); }); })(function*(){ const version = (yield this._getVersion()); return version.product; });} /** * @return {!Promise<string>} */ /* async */ userAgent() {return (fn => { const gen = fn.call(this); return new Promise((resolve, reject) => { function step(key, arg) { let info, value; try { info = gen[key](arg); value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then( value => { step('next', value); }, err => { step('throw', err); }); } } return step('next'); }); })(function*(){ const version = (yield this._getVersion()); return version.userAgent; });} /* async */ close() {return (fn => { const gen = fn.call(this); return new Promise((resolve, reject) => { function step(key, arg) { let info, value; try { info = gen[key](arg); value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then( value => { step('next', value); }, err => { step('throw', err); }); } } return step('next'); }); })(function*(){ (yield this._closeCallback.call(null)); this.disconnect(); });} disconnect() { this._connection.dispose(); } /** * @return {!Promise<!Object>} */ _getVersion() { return this._connection.send('Browser.getVersion'); } } /** @enum {string} */ Browser.Events = { TargetCreated: 'targetcreated', TargetDestroyed: 'targetdestroyed', TargetChanged: 'targetchanged', Disconnected: 'disconnected' }; class BrowserContext extends EventEmitter { /** * @param {!Browser} browser * @param {?string} contextId */ constructor(browser, contextId) { super(); this._browser = browser; this._id = contextId; } /** * @return {!Array<!Target>} target */ targets() { return this._browser.targets().filter(target => target.browserContext() === this); } /** * @return {!Promise<!Array<!Puppeteer.Page>>} */ /* async */ pages() {return (fn => { const gen = fn.call(this); return new Promise((resolve, reject) => { function step(key, arg) { let info, value; try { info = gen[key](arg); value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then( value => { step('next', value); }, err => { step('throw', err); }); } } return step('next'); }); })(function*(){ const pages = (yield Promise.all( this.targets() .filter(target => target.type() === 'page') .map(target => target.page()) )); return pages.filter(page => !!page); });} /** * @return {boolean} */ isIncognito() { return !!this._id; } /** * @return {!Promise<!Puppeteer.Page>} */ newPage() { return this._browser._createPageInContext(this._id); } /** * @return {!Browser} */ browser() { return this._browser; } /* async */ close() {return (fn => { const gen = fn.call(this); return new Promise((resolve, reject) => { function step(key, arg) { let info, value; try { info = gen[key](arg); value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then( value => { step('next', value); }, err => { step('throw', err); }); } } return step('next'); }); })(function*(){ assert(this._id, 'Non-incognito profiles cannot be closed!'); (yield this._browser._disposeContext(this._id)); });} } /** @enum {string} */ BrowserContext.Events = { TargetCreated: 'targetcreated', TargetDestroyed: 'targetdestroyed', TargetChanged: 'targetchanged', }; helper.tracePublicAPI(BrowserContext); helper.tracePublicAPI(Browser); module.exports = {Browser, BrowserContext};