UNPKG

8.4 kBJavaScriptView Raw
1/**
2 * Copyright 2017 Google Inc. All rights reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17const { helper, assert } = require('./helper');
18const Target = require('./Target');
19const EventEmitter = require('events');
20const TaskQueue = require('./TaskQueue');
21
22class Browser extends EventEmitter {
23 /**
24 * @param {!Puppeteer.Connection} connection
25 * @param {!Array<string>} contextIds
26 * @param {boolean} ignoreHTTPSErrors
27 * @param {boolean} setDefaultViewport
28 * @param {?Puppeteer.ChildProcess} process
29 * @param {(function():Promise)=} closeCallback
30 */
31 constructor(connection, contextIds, ignoreHTTPSErrors, setDefaultViewport, process, closeCallback) {
32 super();
33 this._ignoreHTTPSErrors = ignoreHTTPSErrors;
34 this._setDefaultViewport = setDefaultViewport;
35 this._process = process;
36 this._screenshotTaskQueue = new TaskQueue();
37 this._connection = connection;
38 this._closeCallback = closeCallback || new Function();
39
40 this._defaultContext = new BrowserContext(this, null);
41 /** @type {Map<string, BrowserContext>} */
42 this._contexts = new Map();
43 for (const contextId of contextIds)
44 this._contexts.set(contextId, new BrowserContext(this, contextId));
45
46 /** @type {Map<string, Target>} */
47 this._targets = new Map();
48 this._connection.setClosedCallback(() => {
49 this.emit(Browser.Events.Disconnected);
50 });
51 this._connection.on('Target.targetCreated', this._targetCreated.bind(this));
52 this._connection.on('Target.targetDestroyed', this._targetDestroyed.bind(this));
53 this._connection.on('Target.targetInfoChanged', this._targetInfoChanged.bind(this));
54 }
55
56 /**
57 * @return {?Puppeteer.ChildProcess}
58 */
59 process() {
60 return this._process;
61 }
62
63 /**
64 * @return {!Promise<!BrowserContext>}
65 */
66 async createIncognitoBrowserContext() {
67 const {browserContextId} = await this._connection.send('Target.createBrowserContext');
68 const context = new BrowserContext(this, browserContextId);
69 this._contexts.set(browserContextId, context);
70 return context;
71 }
72
73 /**
74 * @return {!Array<!BrowserContext>}
75 */
76 browserContexts() {
77 return [this._defaultContext, ...Array.from(this._contexts.values())];
78 }
79
80 /**
81 * @param {?string} contextId
82 */
83 async _disposeContext(contextId) {
84 await this._connection.send('Target.disposeBrowserContext', {browserContextId: contextId || undefined});
85 this._contexts.delete(contextId);
86 }
87
88 /**
89 * @param {!Puppeteer.Connection} connection
90 * @param {!Array<string>} contextIds
91 * @param {boolean} ignoreHTTPSErrors
92 * @param {boolean} appMode
93 * @param {?Puppeteer.ChildProcess} process
94 * @param {function()=} closeCallback
95 */
96 static async create(connection, contextIds, ignoreHTTPSErrors, appMode, process, closeCallback) {
97 const browser = new Browser(connection, contextIds, ignoreHTTPSErrors, appMode, process, closeCallback);
98 await connection.send('Target.setDiscoverTargets', {discover: true});
99 return browser;
100 }
101
102 /**
103 * @param {!Protocol.Target.targetCreatedPayload} event
104 */
105 async _targetCreated(event) {
106 const targetInfo = event.targetInfo;
107 const {browserContextId} = targetInfo;
108 const context = (browserContextId && this._contexts.has(browserContextId)) ? this._contexts.get(browserContextId) : this._defaultContext;
109
110 const target = new Target(targetInfo, context, () => this._connection.createSession(targetInfo), this._ignoreHTTPSErrors, this._setDefaultViewport, this._screenshotTaskQueue);
111 assert(!this._targets.has(event.targetInfo.targetId), 'Target should not exist before targetCreated');
112 this._targets.set(event.targetInfo.targetId, target);
113
114 if (await target._initializedPromise) {
115 this.emit(Browser.Events.TargetCreated, target);
116 context.emit(BrowserContext.Events.TargetCreated, target);
117 }
118 }
119
120 /**
121 * @param {{targetId: string}} event
122 */
123 async _targetDestroyed(event) {
124 const target = this._targets.get(event.targetId);
125 target._initializedCallback(false);
126 this._targets.delete(event.targetId);
127 target._closedCallback();
128 if (await target._initializedPromise) {
129 this.emit(Browser.Events.TargetDestroyed, target);
130 target.browserContext().emit(BrowserContext.Events.TargetDestroyed, target);
131 }
132 }
133
134 /**
135 * @param {!Protocol.Target.targetInfoChangedPayload} event
136 */
137 _targetInfoChanged(event) {
138 const target = this._targets.get(event.targetInfo.targetId);
139 assert(target, 'target should exist before targetInfoChanged');
140 const previousURL = target.url();
141 const wasInitialized = target._isInitialized;
142 target._targetInfoChanged(event.targetInfo);
143 if (wasInitialized && previousURL !== target.url()) {
144 this.emit(Browser.Events.TargetChanged, target);
145 target.browserContext().emit(BrowserContext.Events.TargetChanged, target);
146 }
147 }
148
149 /**
150 * @return {string}
151 */
152 wsEndpoint() {
153 return this._connection.url();
154 }
155
156 /**
157 * @return {!Promise<!Puppeteer.Page>}
158 */
159 async newPage() {
160 return this._defaultContext.newPage();
161 }
162
163 /**
164 * @param {string} contextId
165 * @return {!Promise<!Puppeteer.Page>}
166 */
167 async _createPageInContext(contextId) {
168 const {targetId} = await this._connection.send('Target.createTarget', {url: 'about:blank', browserContextId: contextId || undefined});
169 const target = await this._targets.get(targetId);
170 assert(await target._initializedPromise, 'Failed to create target for page');
171 const page = await target.page();
172 return page;
173 }
174
175 /**
176 * @return {!Array<!Target>}
177 */
178 targets() {
179 return Array.from(this._targets.values()).filter(target => target._isInitialized);
180 }
181
182 /**
183 * @return {!Promise<!Array<!Puppeteer.Page>>}
184 */
185 async pages() {
186 const pages = await Promise.all(this.targets().map(target => target.page()));
187 return pages.filter(page => !!page);
188 }
189
190 /**
191 * @return {!Promise<string>}
192 */
193 async version() {
194 const version = await this._getVersion();
195 return version.product;
196 }
197
198 /**
199 * @return {!Promise<string>}
200 */
201 async userAgent() {
202 const version = await this._getVersion();
203 return version.userAgent;
204 }
205
206 async close() {
207 await this._closeCallback.call(null);
208 this.disconnect();
209 }
210
211 disconnect() {
212 this._connection.dispose();
213 }
214
215 /**
216 * @return {!Promise<!Object>}
217 */
218 _getVersion() {
219 return this._connection.send('Browser.getVersion');
220 }
221}
222
223/** @enum {string} */
224Browser.Events = {
225 TargetCreated: 'targetcreated',
226 TargetDestroyed: 'targetdestroyed',
227 TargetChanged: 'targetchanged',
228 Disconnected: 'disconnected'
229};
230
231class BrowserContext extends EventEmitter {
232 /**
233 * @param {!Browser} browser
234 * @param {?string} contextId
235 */
236 constructor(browser, contextId) {
237 super();
238 this._browser = browser;
239 this._id = contextId;
240 }
241
242 /**
243 * @return {!Array<!Target>} target
244 */
245 targets() {
246 return this._browser.targets().filter(target => target.browserContext() === this);
247 }
248
249 /**
250 * @return {boolean}
251 */
252 isIncognito() {
253 return !!this._id;
254 }
255
256 /**
257 * @return {!Promise<!Puppeteer.Page>}
258 */
259 newPage() {
260 return this._browser._createPageInContext(this._id);
261 }
262
263 /**
264 * @return {!Browser}
265 */
266 browser() {
267 return this._browser;
268 }
269
270 async close() {
271 assert(this._id, 'Non-incognito profiles cannot be closed!');
272 await this._browser._disposeContext(this._id);
273 }
274}
275
276/** @enum {string} */
277BrowserContext.Events = {
278 TargetCreated: 'targetcreated',
279 TargetDestroyed: 'targetdestroyed',
280 TargetChanged: 'targetchanged',
281};
282
283helper.tracePublicAPI(BrowserContext);
284helper.tracePublicAPI(Browser);
285
286module.exports = {Browser, BrowserContext};
287
288/**
289 * @typedef {Object} BrowserOptions
290 * @property {boolean=} appMode
291 * @property {boolean=} ignoreHTTPSErrors
292 */