UNPKG

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