UNPKG

10.5 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');
21const {Connection} = require('./Connection');
22
23class Browser extends EventEmitter {
24 /**
25 * @param {!Puppeteer.Connection} connection
26 * @param {!Array<string>} contextIds
27 * @param {boolean} ignoreHTTPSErrors
28 * @param {?Puppeteer.Viewport} defaultViewport
29 * @param {?Puppeteer.ChildProcess} process
30 * @param {(function():Promise)=} closeCallback
31 */
32 constructor(connection, contextIds, ignoreHTTPSErrors, defaultViewport, process, closeCallback) {
33 super();
34 this._ignoreHTTPSErrors = ignoreHTTPSErrors;
35 this._defaultViewport = defaultViewport;
36 this._process = process;
37 this._screenshotTaskQueue = new TaskQueue();
38 this._connection = connection;
39 this._closeCallback = closeCallback || new Function();
40
41 this._defaultContext = new BrowserContext(this._connection, this, null);
42 /** @type {Map<string, BrowserContext>} */
43 this._contexts = new Map();
44 for (const contextId of contextIds)
45 this._contexts.set(contextId, new BrowserContext(this._connection, this, contextId));
46
47 /** @type {Map<string, Target>} */
48 this._targets = new Map();
49 this._connection.on(Connection.Events.Disconnected, () => this.emit(Browser.Events.Disconnected));
50 this._connection.on('Target.targetCreated', this._targetCreated.bind(this));
51 this._connection.on('Target.targetDestroyed', this._targetDestroyed.bind(this));
52 this._connection.on('Target.targetInfoChanged', this._targetInfoChanged.bind(this));
53 }
54
55 /**
56 * @return {?Puppeteer.ChildProcess}
57 */
58 process() {
59 return this._process;
60 }
61
62 /**
63 * @return {!Promise<!BrowserContext>}
64 */
65 async createIncognitoBrowserContext() {
66 const {browserContextId} = await this._connection.send('Target.createBrowserContext');
67 const context = new BrowserContext(this._connection, this, browserContextId);
68 this._contexts.set(browserContextId, context);
69 return context;
70 }
71
72 /**
73 * @return {!Array<!BrowserContext>}
74 */
75 browserContexts() {
76 return [this._defaultContext, ...Array.from(this._contexts.values())];
77 }
78
79 /**
80 * @return {!BrowserContext}
81 */
82 defaultBrowserContext() {
83 return this._defaultContext;
84 }
85
86 /**
87 * @param {?string} contextId
88 */
89 async _disposeContext(contextId) {
90 await this._connection.send('Target.disposeBrowserContext', {browserContextId: contextId || undefined});
91 this._contexts.delete(contextId);
92 }
93
94 /**
95 * @param {!Puppeteer.Connection} connection
96 * @param {!Array<string>} contextIds
97 * @param {boolean} ignoreHTTPSErrors
98 * @param {?Puppeteer.Viewport} defaultViewport
99 * @param {?Puppeteer.ChildProcess} process
100 * @param {function()=} closeCallback
101 */
102 static async create(connection, contextIds, ignoreHTTPSErrors, defaultViewport, process, closeCallback) {
103 const browser = new Browser(connection, contextIds, ignoreHTTPSErrors, defaultViewport, process, closeCallback);
104 await connection.send('Target.setDiscoverTargets', {discover: true});
105 return browser;
106 }
107
108 /**
109 * @param {!Protocol.Target.targetCreatedPayload} event
110 */
111 async _targetCreated(event) {
112 const targetInfo = event.targetInfo;
113 const {browserContextId} = targetInfo;
114 const context = (browserContextId && this._contexts.has(browserContextId)) ? this._contexts.get(browserContextId) : this._defaultContext;
115
116 const target = new Target(targetInfo, context, () => this._connection.createSession(targetInfo), this._ignoreHTTPSErrors, this._defaultViewport, this._screenshotTaskQueue);
117 assert(!this._targets.has(event.targetInfo.targetId), 'Target should not exist before targetCreated');
118 this._targets.set(event.targetInfo.targetId, target);
119
120 if (await target._initializedPromise) {
121 this.emit(Browser.Events.TargetCreated, target);
122 context.emit(BrowserContext.Events.TargetCreated, target);
123 }
124 }
125
126 /**
127 * @param {{targetId: string}} event
128 */
129 async _targetDestroyed(event) {
130 const target = this._targets.get(event.targetId);
131 target._initializedCallback(false);
132 this._targets.delete(event.targetId);
133 target._closedCallback();
134 if (await target._initializedPromise) {
135 this.emit(Browser.Events.TargetDestroyed, target);
136 target.browserContext().emit(BrowserContext.Events.TargetDestroyed, target);
137 }
138 }
139
140 /**
141 * @param {!Protocol.Target.targetInfoChangedPayload} event
142 */
143 _targetInfoChanged(event) {
144 const target = this._targets.get(event.targetInfo.targetId);
145 assert(target, 'target should exist before targetInfoChanged');
146 const previousURL = target.url();
147 const wasInitialized = target._isInitialized;
148 target._targetInfoChanged(event.targetInfo);
149 if (wasInitialized && previousURL !== target.url()) {
150 this.emit(Browser.Events.TargetChanged, target);
151 target.browserContext().emit(BrowserContext.Events.TargetChanged, target);
152 }
153 }
154
155 /**
156 * @return {string}
157 */
158 wsEndpoint() {
159 return this._connection.url();
160 }
161
162 /**
163 * @return {!Promise<!Puppeteer.Page>}
164 */
165 async newPage() {
166 return this._defaultContext.newPage();
167 }
168
169 /**
170 * @param {?string} contextId
171 * @return {!Promise<!Puppeteer.Page>}
172 */
173 async _createPageInContext(contextId) {
174 const {targetId} = await this._connection.send('Target.createTarget', {url: 'about:blank', browserContextId: contextId || undefined});
175 const target = await this._targets.get(targetId);
176 assert(await target._initializedPromise, 'Failed to create target for page');
177 const page = await target.page();
178 return page;
179 }
180
181 /**
182 * @return {!Array<!Target>}
183 */
184 targets() {
185 return Array.from(this._targets.values()).filter(target => target._isInitialized);
186 }
187
188 /**
189 * @return {!Target}
190 */
191 target() {
192 return this.targets().find(target => target.type() === 'browser');
193 }
194
195 /**
196 * @return {!Promise<!Array<!Puppeteer.Page>>}
197 */
198 async pages() {
199 const contextPages = await Promise.all(this.browserContexts().map(context => context.pages()));
200 // Flatten array.
201 return contextPages.reduce((acc, x) => acc.concat(x), []);
202 }
203
204 /**
205 * @return {!Promise<string>}
206 */
207 async version() {
208 const version = await this._getVersion();
209 return version.product;
210 }
211
212 /**
213 * @return {!Promise<string>}
214 */
215 async userAgent() {
216 const version = await this._getVersion();
217 return version.userAgent;
218 }
219
220 async close() {
221 await this._closeCallback.call(null);
222 this.disconnect();
223 }
224
225 disconnect() {
226 this._connection.dispose();
227 }
228
229 /**
230 * @return {!Promise<!Object>}
231 */
232 _getVersion() {
233 return this._connection.send('Browser.getVersion');
234 }
235}
236
237/** @enum {string} */
238Browser.Events = {
239 TargetCreated: 'targetcreated',
240 TargetDestroyed: 'targetdestroyed',
241 TargetChanged: 'targetchanged',
242 Disconnected: 'disconnected'
243};
244
245class BrowserContext extends EventEmitter {
246 /**
247 * @param {!Puppeteer.Connection} connection
248 * @param {!Browser} browser
249 * @param {?string} contextId
250 */
251 constructor(connection, browser, contextId) {
252 super();
253 this._connection = connection;
254 this._browser = browser;
255 this._id = contextId;
256 }
257
258 /**
259 * @return {!Array<!Target>} target
260 */
261 targets() {
262 return this._browser.targets().filter(target => target.browserContext() === this);
263 }
264
265 /**
266 * @return {!Promise<!Array<!Puppeteer.Page>>}
267 */
268 async pages() {
269 const pages = await Promise.all(
270 this.targets()
271 .filter(target => target.type() === 'page')
272 .map(target => target.page())
273 );
274 return pages.filter(page => !!page);
275 }
276
277 /**
278 * @return {boolean}
279 */
280 isIncognito() {
281 return !!this._id;
282 }
283
284 /**
285 * @param {string} origin
286 * @param {!Array<string>} permissions
287 */
288 async overridePermissions(origin, permissions) {
289 const webPermissionToProtocol = new Map([
290 ['geolocation', 'geolocation'],
291 ['midi', 'midi'],
292 ['notifications', 'notifications'],
293 ['push', 'push'],
294 ['camera', 'videoCapture'],
295 ['microphone', 'audioCapture'],
296 ['background-sync', 'backgroundSync'],
297 ['ambient-light-sensor', 'sensors'],
298 ['accelerometer', 'sensors'],
299 ['gyroscope', 'sensors'],
300 ['magnetometer', 'sensors'],
301 ['accessibility-events', 'accessibilityEvents'],
302 ['clipboard-read', 'clipboardRead'],
303 ['clipboard-write', 'clipboardWrite'],
304 ['payment-handler', 'paymentHandler'],
305 // chrome-specific permissions we have.
306 ['midi-sysex', 'midiSysex'],
307 ]);
308 permissions = permissions.map(permission => {
309 const protocolPermission = webPermissionToProtocol.get(permission);
310 if (!protocolPermission)
311 throw new Error('Unknown permission: ' + permission);
312 return protocolPermission;
313 });
314 await this._connection.send('Browser.grantPermissions', {origin, browserContextId: this._id || undefined, permissions});
315 }
316
317 async clearPermissionOverrides() {
318 await this._connection.send('Browser.resetPermissions', {browserContextId: this._id || undefined});
319 }
320
321 /**
322 * @return {!Promise<!Puppeteer.Page>}
323 */
324 newPage() {
325 return this._browser._createPageInContext(this._id);
326 }
327
328 /**
329 * @return {!Browser}
330 */
331 browser() {
332 return this._browser;
333 }
334
335 async close() {
336 assert(this._id, 'Non-incognito profiles cannot be closed!');
337 await this._browser._disposeContext(this._id);
338 }
339}
340
341/** @enum {string} */
342BrowserContext.Events = {
343 TargetCreated: 'targetcreated',
344 TargetDestroyed: 'targetdestroyed',
345 TargetChanged: 'targetchanged',
346};
347
348helper.tracePublicAPI(BrowserContext);
349helper.tracePublicAPI(Browser);
350
351module.exports = {Browser, BrowserContext};