UNPKG

8.67 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 {?Puppeteer.Viewport} defaultViewport
28 * @param {?Puppeteer.ChildProcess} process
29 * @param {(function():Promise)=} closeCallback
30 */
31 constructor(connection, contextIds, ignoreHTTPSErrors, defaultViewport, process, closeCallback) {
32 super();
33 this._ignoreHTTPSErrors = ignoreHTTPSErrors;
34 this._defaultViewport = defaultViewport;
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 {?Puppeteer.Viewport} defaultViewport
93 * @param {?Puppeteer.ChildProcess} process
94 * @param {function()=} closeCallback
95 */
96 static async create(connection, contextIds, ignoreHTTPSErrors, defaultViewport, process, closeCallback) {
97 const browser = new Browser(connection, contextIds, ignoreHTTPSErrors, defaultViewport, 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._defaultViewport, 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 contextPages = await Promise.all(this.browserContexts().map(context => context.pages()));
187 // Flatten array.
188 return contextPages.reduce((acc, x) => acc.concat(x), []);
189 }
190
191 /**
192 * @return {!Promise<string>}
193 */
194 async version() {
195 const version = await this._getVersion();
196 return version.product;
197 }
198
199 /**
200 * @return {!Promise<string>}
201 */
202 async userAgent() {
203 const version = await this._getVersion();
204 return version.userAgent;
205 }
206
207 async close() {
208 await this._closeCallback.call(null);
209 this.disconnect();
210 }
211
212 disconnect() {
213 this._connection.dispose();
214 }
215
216 /**
217 * @return {!Promise<!Object>}
218 */
219 _getVersion() {
220 return this._connection.send('Browser.getVersion');
221 }
222}
223
224/** @enum {string} */
225Browser.Events = {
226 TargetCreated: 'targetcreated',
227 TargetDestroyed: 'targetdestroyed',
228 TargetChanged: 'targetchanged',
229 Disconnected: 'disconnected'
230};
231
232class BrowserContext extends EventEmitter {
233 /**
234 * @param {!Browser} browser
235 * @param {?string} contextId
236 */
237 constructor(browser, contextId) {
238 super();
239 this._browser = browser;
240 this._id = contextId;
241 }
242
243 /**
244 * @return {!Array<!Target>} target
245 */
246 targets() {
247 return this._browser.targets().filter(target => target.browserContext() === this);
248 }
249
250 /**
251 * @return {!Promise<!Array<!Puppeteer.Page>>}
252 */
253 async pages() {
254 const pages = await Promise.all(
255 this.targets()
256 .filter(target => target.type() === 'page')
257 .map(target => target.page())
258 );
259 return pages.filter(page => !!page);
260 }
261
262 /**
263 * @return {boolean}
264 */
265 isIncognito() {
266 return !!this._id;
267 }
268
269 /**
270 * @return {!Promise<!Puppeteer.Page>}
271 */
272 newPage() {
273 return this._browser._createPageInContext(this._id);
274 }
275
276 /**
277 * @return {!Browser}
278 */
279 browser() {
280 return this._browser;
281 }
282
283 async close() {
284 assert(this._id, 'Non-incognito profiles cannot be closed!');
285 await this._browser._disposeContext(this._id);
286 }
287}
288
289/** @enum {string} */
290BrowserContext.Events = {
291 TargetCreated: 'targetcreated',
292 TargetDestroyed: 'targetdestroyed',
293 TargetChanged: 'targetchanged',
294};
295
296helper.tracePublicAPI(BrowserContext);
297helper.tracePublicAPI(Browser);
298
299module.exports = {Browser, BrowserContext};