UNPKG

64.8 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 fs = require('fs');
18const EventEmitter = require('events');
19const mime = require('mime');
20const {NetworkManager} = require('./NetworkManager');
21const {NavigatorWatcher} = require('./NavigatorWatcher');
22const {Dialog} = require('./Dialog');
23const {EmulationManager} = require('./EmulationManager');
24const {FrameManager} = require('./FrameManager');
25const {Keyboard, Mouse, Touchscreen} = require('./Input');
26const Tracing = require('./Tracing');
27const {helper, debugError, assert} = require('./helper');
28const {Coverage} = require('./Coverage');
29const {Worker} = require('./Worker');
30
31const writeFileAsync = helper.promisify(fs.writeFile);
32
33class Page extends EventEmitter {
34 /**
35 * @param {!Puppeteer.CDPSession} client
36 * @param {!Puppeteer.Target} target
37 * @param {boolean} ignoreHTTPSErrors
38 * @param {?Puppeteer.Viewport} defaultViewport
39 * @param {!Puppeteer.TaskQueue} screenshotTaskQueue
40 * @return {!Promise<!Page>}
41 */
42 static /* async */ create(client, target, ignoreHTTPSErrors, defaultViewport, screenshotTaskQueue) {return (fn => {
43 const gen = fn.call(this);
44 return new Promise((resolve, reject) => {
45 function step(key, arg) {
46 let info, value;
47 try {
48 info = gen[key](arg);
49 value = info.value;
50 } catch (error) {
51 reject(error);
52 return;
53 }
54 if (info.done) {
55 resolve(value);
56 } else {
57 return Promise.resolve(value).then(
58 value => {
59 step('next', value);
60 },
61 err => {
62 step('throw', err);
63 });
64 }
65 }
66 return step('next');
67 });
68})(function*(){
69
70 (yield client.send('Page.enable'));
71 const {frameTree} = (yield client.send('Page.getFrameTree'));
72 const page = new Page(client, target, frameTree, ignoreHTTPSErrors, screenshotTaskQueue);
73
74 (yield Promise.all([
75 client.send('Target.setAutoAttach', {autoAttach: true, waitForDebuggerOnStart: false}),
76 client.send('Page.setLifecycleEventsEnabled', { enabled: true }),
77 client.send('Network.enable', {}),
78 client.send('Runtime.enable', {}),
79 client.send('Security.enable', {}),
80 client.send('Performance.enable', {}),
81 client.send('Log.enable', {}),
82 ]));
83 if (ignoreHTTPSErrors)
84 (yield client.send('Security.setOverrideCertificateErrors', {override: true}));
85 // Initialize default page size.
86 if (defaultViewport)
87 (yield page.setViewport(defaultViewport));
88
89 return page;
90 });}
91
92 /**
93 * @param {!Puppeteer.CDPSession} client
94 * @param {!Puppeteer.Target} target
95 * @param {!Protocol.Page.FrameTree} frameTree
96 * @param {boolean} ignoreHTTPSErrors
97 * @param {!Puppeteer.TaskQueue} screenshotTaskQueue
98 */
99 constructor(client, target, frameTree, ignoreHTTPSErrors, screenshotTaskQueue) {
100 super();
101 this._closed = false;
102 this._client = client;
103 this._target = target;
104 this._keyboard = new Keyboard(client);
105 this._mouse = new Mouse(client, this._keyboard);
106 this._touchscreen = new Touchscreen(client, this._keyboard);
107 /** @type {!FrameManager} */
108 this._frameManager = new FrameManager(client, frameTree, this);
109 this._networkManager = new NetworkManager(client, this._frameManager);
110 this._emulationManager = new EmulationManager(client);
111 this._tracing = new Tracing(client);
112 /** @type {!Map<string, Function>} */
113 this._pageBindings = new Map();
114 this._ignoreHTTPSErrors = ignoreHTTPSErrors;
115 this._coverage = new Coverage(client);
116 this._defaultNavigationTimeout = 30000;
117 this._javascriptEnabled = true;
118 /** @type {?Puppeteer.Viewport} */
119 this._viewport = null;
120
121 this._screenshotTaskQueue = screenshotTaskQueue;
122
123 /** @type {!Map<string, Worker>} */
124 this._workers = new Map();
125 client.on('Target.attachedToTarget', event => {
126 if (event.targetInfo.type !== 'worker') {
127 // If we don't detach from service workers, they will never die.
128 client.send('Target.detachFromTarget', {
129 sessionId: event.sessionId
130 }).catch(debugError);
131 return;
132 }
133 const session = client._createSession(event.targetInfo.type, event.sessionId);
134 const worker = new Worker(session, event.targetInfo.url, this._addConsoleMessage.bind(this), this._handleException.bind(this));
135 this._workers.set(event.sessionId, worker);
136 this.emit(Page.Events.WorkerCreated, worker);
137
138 });
139 client.on('Target.detachedFromTarget', event => {
140 const worker = this._workers.get(event.sessionId);
141 if (!worker)
142 return;
143 this.emit(Page.Events.WorkerDestroyed, worker);
144 this._workers.delete(event.sessionId);
145 });
146
147 this._frameManager.on(FrameManager.Events.FrameAttached, event => this.emit(Page.Events.FrameAttached, event));
148 this._frameManager.on(FrameManager.Events.FrameDetached, event => this.emit(Page.Events.FrameDetached, event));
149 this._frameManager.on(FrameManager.Events.FrameNavigated, event => this.emit(Page.Events.FrameNavigated, event));
150
151 this._networkManager.on(NetworkManager.Events.Request, event => this.emit(Page.Events.Request, event));
152 this._networkManager.on(NetworkManager.Events.Response, event => this.emit(Page.Events.Response, event));
153 this._networkManager.on(NetworkManager.Events.RequestFailed, event => this.emit(Page.Events.RequestFailed, event));
154 this._networkManager.on(NetworkManager.Events.RequestFinished, event => this.emit(Page.Events.RequestFinished, event));
155
156 client.on('Page.domContentEventFired', event => this.emit(Page.Events.DOMContentLoaded));
157 client.on('Page.loadEventFired', event => this.emit(Page.Events.Load));
158 client.on('Runtime.consoleAPICalled', event => this._onConsoleAPI(event));
159 client.on('Runtime.bindingCalled', event => this._onBindingCalled(event));
160 client.on('Page.javascriptDialogOpening', event => this._onDialog(event));
161 client.on('Runtime.exceptionThrown', exception => this._handleException(exception.exceptionDetails));
162 client.on('Security.certificateError', event => this._onCertificateError(event));
163 client.on('Inspector.targetCrashed', event => this._onTargetCrashed());
164 client.on('Performance.metrics', event => this._emitMetrics(event));
165 client.on('Log.entryAdded', event => this._onLogEntryAdded(event));
166 this._target._isClosedPromise.then(() => {
167 this.emit(Page.Events.Close);
168 this._closed = true;
169 });
170 }
171
172 /**
173 * @return {!Puppeteer.Target}
174 */
175 target() {
176 return this._target;
177 }
178
179 /**
180 * @return {!Puppeteer.Browser}
181 */
182 browser() {
183 return this._target.browser();
184 }
185
186 _onTargetCrashed() {
187 this.emit('error', new Error('Page crashed!'));
188 }
189
190 /**
191 * @param {!Protocol.Log.entryAddedPayload} event
192 */
193 _onLogEntryAdded(event) {
194 const {level, text, args, source} = event.entry;
195 if (args)
196 args.map(arg => helper.releaseObject(this._client, arg));
197 if (source !== 'worker')
198 this.emit(Page.Events.Console, new ConsoleMessage(level, text));
199 }
200
201 /**
202 * @return {!Puppeteer.Frame}
203 */
204 mainFrame() {
205 return this._frameManager.mainFrame();
206 }
207
208 /**
209 * @return {!Keyboard}
210 */
211 get keyboard() {
212 return this._keyboard;
213 }
214
215 /**
216 * @return {!Touchscreen}
217 */
218 get touchscreen() {
219 return this._touchscreen;
220 }
221
222 /**
223 * @return {!Coverage}
224 */
225 get coverage() {
226 return this._coverage;
227 }
228
229 /**
230 * @return {!Tracing}
231 */
232 get tracing() {
233 return this._tracing;
234 }
235
236 /**
237 * @return {!Array<Puppeteer.Frame>}
238 */
239 frames() {
240 return this._frameManager.frames();
241 }
242
243 /**
244 * @return {!Array<!Worker>}
245 */
246 workers() {
247 return Array.from(this._workers.values());
248 }
249
250 /**
251 * @param {boolean} value
252 */
253 /* async */ setRequestInterception(value) {return (fn => {
254 const gen = fn.call(this);
255 return new Promise((resolve, reject) => {
256 function step(key, arg) {
257 let info, value;
258 try {
259 info = gen[key](arg);
260 value = info.value;
261 } catch (error) {
262 reject(error);
263 return;
264 }
265 if (info.done) {
266 resolve(value);
267 } else {
268 return Promise.resolve(value).then(
269 value => {
270 step('next', value);
271 },
272 err => {
273 step('throw', err);
274 });
275 }
276 }
277 return step('next');
278 });
279})(function*(){
280 return this._networkManager.setRequestInterception(value);
281 });}
282
283 /**
284 * @param {boolean} enabled
285 */
286 setOfflineMode(enabled) {
287 return this._networkManager.setOfflineMode(enabled);
288 }
289
290 /**
291 * @param {number} timeout
292 */
293 setDefaultNavigationTimeout(timeout) {
294 this._defaultNavigationTimeout = timeout;
295 }
296
297 /**
298 * @param {!Protocol.Security.certificateErrorPayload} event
299 */
300 _onCertificateError(event) {
301 if (!this._ignoreHTTPSErrors)
302 return;
303 this._client.send('Security.handleCertificateError', {
304 eventId: event.eventId,
305 action: 'continue'
306 }).catch(debugError);
307 }
308
309 /**
310 * @param {string} selector
311 * @return {!Promise<?Puppeteer.ElementHandle>}
312 */
313 /* async */ $(selector) {return (fn => {
314 const gen = fn.call(this);
315 return new Promise((resolve, reject) => {
316 function step(key, arg) {
317 let info, value;
318 try {
319 info = gen[key](arg);
320 value = info.value;
321 } catch (error) {
322 reject(error);
323 return;
324 }
325 if (info.done) {
326 resolve(value);
327 } else {
328 return Promise.resolve(value).then(
329 value => {
330 step('next', value);
331 },
332 err => {
333 step('throw', err);
334 });
335 }
336 }
337 return step('next');
338 });
339})(function*(){
340 return this.mainFrame().$(selector);
341 });}
342
343 /**
344 * @param {function()|string} pageFunction
345 * @param {!Array<*>} args
346 * @return {!Promise<!Puppeteer.JSHandle>}
347 */
348 /* async */ evaluateHandle(pageFunction, ...args) {return (fn => {
349 const gen = fn.call(this);
350 return new Promise((resolve, reject) => {
351 function step(key, arg) {
352 let info, value;
353 try {
354 info = gen[key](arg);
355 value = info.value;
356 } catch (error) {
357 reject(error);
358 return;
359 }
360 if (info.done) {
361 resolve(value);
362 } else {
363 return Promise.resolve(value).then(
364 value => {
365 step('next', value);
366 },
367 err => {
368 step('throw', err);
369 });
370 }
371 }
372 return step('next');
373 });
374})(function*(){
375 const context = (yield this.mainFrame().executionContext());
376 return context.evaluateHandle(pageFunction, ...args);
377 });}
378
379 /**
380 * @param {!Puppeteer.JSHandle} prototypeHandle
381 * @return {!Promise<!Puppeteer.JSHandle>}
382 */
383 /* async */ queryObjects(prototypeHandle) {return (fn => {
384 const gen = fn.call(this);
385 return new Promise((resolve, reject) => {
386 function step(key, arg) {
387 let info, value;
388 try {
389 info = gen[key](arg);
390 value = info.value;
391 } catch (error) {
392 reject(error);
393 return;
394 }
395 if (info.done) {
396 resolve(value);
397 } else {
398 return Promise.resolve(value).then(
399 value => {
400 step('next', value);
401 },
402 err => {
403 step('throw', err);
404 });
405 }
406 }
407 return step('next');
408 });
409})(function*(){
410 const context = (yield this.mainFrame().executionContext());
411 return context.queryObjects(prototypeHandle);
412 });}
413
414 /**
415 * @param {string} selector
416 * @param {function()|string} pageFunction
417 * @param {!Array<*>} args
418 * @return {!Promise<(!Object|undefined)>}
419 */
420 /* async */ $eval(selector, pageFunction, ...args) {return (fn => {
421 const gen = fn.call(this);
422 return new Promise((resolve, reject) => {
423 function step(key, arg) {
424 let info, value;
425 try {
426 info = gen[key](arg);
427 value = info.value;
428 } catch (error) {
429 reject(error);
430 return;
431 }
432 if (info.done) {
433 resolve(value);
434 } else {
435 return Promise.resolve(value).then(
436 value => {
437 step('next', value);
438 },
439 err => {
440 step('throw', err);
441 });
442 }
443 }
444 return step('next');
445 });
446})(function*(){
447 return this.mainFrame().$eval(selector, pageFunction, ...args);
448 });}
449
450 /**
451 * @param {string} selector
452 * @param {Function|string} pageFunction
453 * @param {!Array<*>} args
454 * @return {!Promise<(!Object|undefined)>}
455 */
456 /* async */ $$eval(selector, pageFunction, ...args) {return (fn => {
457 const gen = fn.call(this);
458 return new Promise((resolve, reject) => {
459 function step(key, arg) {
460 let info, value;
461 try {
462 info = gen[key](arg);
463 value = info.value;
464 } catch (error) {
465 reject(error);
466 return;
467 }
468 if (info.done) {
469 resolve(value);
470 } else {
471 return Promise.resolve(value).then(
472 value => {
473 step('next', value);
474 },
475 err => {
476 step('throw', err);
477 });
478 }
479 }
480 return step('next');
481 });
482})(function*(){
483 return this.mainFrame().$$eval(selector, pageFunction, ...args);
484 });}
485
486 /**
487 * @param {string} selector
488 * @return {!Promise<!Array<!Puppeteer.ElementHandle>>}
489 */
490 /* async */ $$(selector) {return (fn => {
491 const gen = fn.call(this);
492 return new Promise((resolve, reject) => {
493 function step(key, arg) {
494 let info, value;
495 try {
496 info = gen[key](arg);
497 value = info.value;
498 } catch (error) {
499 reject(error);
500 return;
501 }
502 if (info.done) {
503 resolve(value);
504 } else {
505 return Promise.resolve(value).then(
506 value => {
507 step('next', value);
508 },
509 err => {
510 step('throw', err);
511 });
512 }
513 }
514 return step('next');
515 });
516})(function*(){
517 return this.mainFrame().$$(selector);
518 });}
519
520 /**
521 * @param {string} expression
522 * @return {!Promise<!Array<!Puppeteer.ElementHandle>>}
523 */
524 /* async */ $x(expression) {return (fn => {
525 const gen = fn.call(this);
526 return new Promise((resolve, reject) => {
527 function step(key, arg) {
528 let info, value;
529 try {
530 info = gen[key](arg);
531 value = info.value;
532 } catch (error) {
533 reject(error);
534 return;
535 }
536 if (info.done) {
537 resolve(value);
538 } else {
539 return Promise.resolve(value).then(
540 value => {
541 step('next', value);
542 },
543 err => {
544 step('throw', err);
545 });
546 }
547 }
548 return step('next');
549 });
550})(function*(){
551 return this.mainFrame().$x(expression);
552 });}
553
554 /**
555 * @param {!Array<string>} urls
556 * @return {!Promise<!Array<Network.Cookie>>}
557 */
558 /* async */ cookies(...urls) {return (fn => {
559 const gen = fn.call(this);
560 return new Promise((resolve, reject) => {
561 function step(key, arg) {
562 let info, value;
563 try {
564 info = gen[key](arg);
565 value = info.value;
566 } catch (error) {
567 reject(error);
568 return;
569 }
570 if (info.done) {
571 resolve(value);
572 } else {
573 return Promise.resolve(value).then(
574 value => {
575 step('next', value);
576 },
577 err => {
578 step('throw', err);
579 });
580 }
581 }
582 return step('next');
583 });
584})(function*(){
585 return ((yield this._client.send('Network.getCookies', {
586 urls: urls.length ? urls : [this.url()]
587 }))).cookies;
588 });}
589
590 /**
591 * @param {Array<Network.CookieParam>} cookies
592 */
593 /* async */ deleteCookie(...cookies) {return (fn => {
594 const gen = fn.call(this);
595 return new Promise((resolve, reject) => {
596 function step(key, arg) {
597 let info, value;
598 try {
599 info = gen[key](arg);
600 value = info.value;
601 } catch (error) {
602 reject(error);
603 return;
604 }
605 if (info.done) {
606 resolve(value);
607 } else {
608 return Promise.resolve(value).then(
609 value => {
610 step('next', value);
611 },
612 err => {
613 step('throw', err);
614 });
615 }
616 }
617 return step('next');
618 });
619})(function*(){
620 const pageURL = this.url();
621 for (const cookie of cookies) {
622 const item = Object.assign({}, cookie);
623 if (!cookie.url && pageURL.startsWith('http'))
624 item.url = pageURL;
625 (yield this._client.send('Network.deleteCookies', item));
626 }
627 });}
628
629 /**
630 * @param {Array<Network.CookieParam>} cookies
631 */
632 /* async */ setCookie(...cookies) {return (fn => {
633 const gen = fn.call(this);
634 return new Promise((resolve, reject) => {
635 function step(key, arg) {
636 let info, value;
637 try {
638 info = gen[key](arg);
639 value = info.value;
640 } catch (error) {
641 reject(error);
642 return;
643 }
644 if (info.done) {
645 resolve(value);
646 } else {
647 return Promise.resolve(value).then(
648 value => {
649 step('next', value);
650 },
651 err => {
652 step('throw', err);
653 });
654 }
655 }
656 return step('next');
657 });
658})(function*(){
659 const pageURL = this.url();
660 const startsWithHTTP = pageURL.startsWith('http');
661 const items = cookies.map(cookie => {
662 const item = Object.assign({}, cookie);
663 if (!item.url && startsWithHTTP)
664 item.url = pageURL;
665 assert(
666 item.url !== 'about:blank',
667 `Blank page can not have cookie "${item.name}"`
668 );
669 assert(
670 !String.prototype.startsWith.call(item.url || '', 'data:'),
671 `Data URL page can not have cookie "${item.name}"`
672 );
673 return item;
674 });
675 (yield this.deleteCookie(...items));
676 if (items.length)
677 (yield this._client.send('Network.setCookies', { cookies: items }));
678 });}
679
680 /**
681 * @param {Object} options
682 * @return {!Promise<!Puppeteer.ElementHandle>}
683 */
684 /* async */ addScriptTag(options) {return (fn => {
685 const gen = fn.call(this);
686 return new Promise((resolve, reject) => {
687 function step(key, arg) {
688 let info, value;
689 try {
690 info = gen[key](arg);
691 value = info.value;
692 } catch (error) {
693 reject(error);
694 return;
695 }
696 if (info.done) {
697 resolve(value);
698 } else {
699 return Promise.resolve(value).then(
700 value => {
701 step('next', value);
702 },
703 err => {
704 step('throw', err);
705 });
706 }
707 }
708 return step('next');
709 });
710})(function*(){
711 return this.mainFrame().addScriptTag(options);
712 });}
713
714 /**
715 * @param {Object} options
716 * @return {!Promise<!Puppeteer.ElementHandle>}
717 */
718 /* async */ addStyleTag(options) {return (fn => {
719 const gen = fn.call(this);
720 return new Promise((resolve, reject) => {
721 function step(key, arg) {
722 let info, value;
723 try {
724 info = gen[key](arg);
725 value = info.value;
726 } catch (error) {
727 reject(error);
728 return;
729 }
730 if (info.done) {
731 resolve(value);
732 } else {
733 return Promise.resolve(value).then(
734 value => {
735 step('next', value);
736 },
737 err => {
738 step('throw', err);
739 });
740 }
741 }
742 return step('next');
743 });
744})(function*(){
745 return this.mainFrame().addStyleTag(options);
746 });}
747
748 /**
749 * @param {string} name
750 * @param {function(?)} puppeteerFunction
751 */
752 /* async */ exposeFunction(name, puppeteerFunction) {return (fn => {
753 const gen = fn.call(this);
754 return new Promise((resolve, reject) => {
755 function step(key, arg) {
756 let info, value;
757 try {
758 info = gen[key](arg);
759 value = info.value;
760 } catch (error) {
761 reject(error);
762 return;
763 }
764 if (info.done) {
765 resolve(value);
766 } else {
767 return Promise.resolve(value).then(
768 value => {
769 step('next', value);
770 },
771 err => {
772 step('throw', err);
773 });
774 }
775 }
776 return step('next');
777 });
778})(function*(){
779 if (this._pageBindings.has(name))
780 throw new Error(`Failed to add page binding with name ${name}: window['${name}'] already exists!`);
781 this._pageBindings.set(name, puppeteerFunction);
782
783 const expression = helper.evaluationString(addPageBinding, name);
784 (yield this._client.send('Runtime.addBinding', {name: name}));
785 (yield this._client.send('Page.addScriptToEvaluateOnNewDocument', {source: expression}));
786 (yield Promise.all(this.frames().map(frame => frame.evaluate(expression).catch(debugError))));
787
788 function addPageBinding(bindingName) {
789 const binding = window[bindingName];
790 window[bindingName] = /* async */(...args) => {return (fn => {
791 const gen = fn.call(this);
792 return new Promise((resolve, reject) => {
793 function step(key, arg) {
794 let info, value;
795 try {
796 info = gen[key](arg);
797 value = info.value;
798 } catch (error) {
799 reject(error);
800 return;
801 }
802 if (info.done) {
803 resolve(value);
804 } else {
805 return Promise.resolve(value).then(
806 value => {
807 step('next', value);
808 },
809 err => {
810 step('throw', err);
811 });
812 }
813 }
814 return step('next');
815 });
816})(function*(){
817 const me = window[bindingName];
818 let callbacks = me['callbacks'];
819 if (!callbacks) {
820 callbacks = new Map();
821 me['callbacks'] = callbacks;
822 }
823 const seq = (me['lastSeq'] || 0) + 1;
824 me['lastSeq'] = seq;
825 const promise = new Promise(fulfill => callbacks.set(seq, fulfill));
826 binding(JSON.stringify({name: bindingName, seq, args}));
827 return promise;
828 });};
829 }
830 });}
831
832 /**
833 * @param {?{username: string, password: string}} credentials
834 */
835 /* async */ authenticate(credentials) {return (fn => {
836 const gen = fn.call(this);
837 return new Promise((resolve, reject) => {
838 function step(key, arg) {
839 let info, value;
840 try {
841 info = gen[key](arg);
842 value = info.value;
843 } catch (error) {
844 reject(error);
845 return;
846 }
847 if (info.done) {
848 resolve(value);
849 } else {
850 return Promise.resolve(value).then(
851 value => {
852 step('next', value);
853 },
854 err => {
855 step('throw', err);
856 });
857 }
858 }
859 return step('next');
860 });
861})(function*(){
862 return this._networkManager.authenticate(credentials);
863 });}
864
865 /**
866 * @param {!Object<string, string>} headers
867 */
868 /* async */ setExtraHTTPHeaders(headers) {return (fn => {
869 const gen = fn.call(this);
870 return new Promise((resolve, reject) => {
871 function step(key, arg) {
872 let info, value;
873 try {
874 info = gen[key](arg);
875 value = info.value;
876 } catch (error) {
877 reject(error);
878 return;
879 }
880 if (info.done) {
881 resolve(value);
882 } else {
883 return Promise.resolve(value).then(
884 value => {
885 step('next', value);
886 },
887 err => {
888 step('throw', err);
889 });
890 }
891 }
892 return step('next');
893 });
894})(function*(){
895 return this._networkManager.setExtraHTTPHeaders(headers);
896 });}
897
898 /**
899 * @param {string} userAgent
900 */
901 /* async */ setUserAgent(userAgent) {return (fn => {
902 const gen = fn.call(this);
903 return new Promise((resolve, reject) => {
904 function step(key, arg) {
905 let info, value;
906 try {
907 info = gen[key](arg);
908 value = info.value;
909 } catch (error) {
910 reject(error);
911 return;
912 }
913 if (info.done) {
914 resolve(value);
915 } else {
916 return Promise.resolve(value).then(
917 value => {
918 step('next', value);
919 },
920 err => {
921 step('throw', err);
922 });
923 }
924 }
925 return step('next');
926 });
927})(function*(){
928 return this._networkManager.setUserAgent(userAgent);
929 });}
930
931 /**
932 * @return {!Promise<!Object>}
933 */
934 /* async */ metrics() {return (fn => {
935 const gen = fn.call(this);
936 return new Promise((resolve, reject) => {
937 function step(key, arg) {
938 let info, value;
939 try {
940 info = gen[key](arg);
941 value = info.value;
942 } catch (error) {
943 reject(error);
944 return;
945 }
946 if (info.done) {
947 resolve(value);
948 } else {
949 return Promise.resolve(value).then(
950 value => {
951 step('next', value);
952 },
953 err => {
954 step('throw', err);
955 });
956 }
957 }
958 return step('next');
959 });
960})(function*(){
961 const response = (yield this._client.send('Performance.getMetrics'));
962 return this._buildMetricsObject(response.metrics);
963 });}
964
965 /**
966 * @param {*} event
967 */
968 _emitMetrics(event) {
969 this.emit(Page.Events.Metrics, {
970 title: event.title,
971 metrics: this._buildMetricsObject(event.metrics)
972 });
973 }
974
975 /**
976 * @param {?Array<!Protocol.Performance.Metric>} metrics
977 * @return {!Object}
978 */
979 _buildMetricsObject(metrics) {
980 const result = {};
981 for (const metric of metrics || []) {
982 if (supportedMetrics.has(metric.name))
983 result[metric.name] = metric.value;
984 }
985 return result;
986 }
987
988 /**
989 * @param {!Protocol.Runtime.ExceptionDetails} exceptionDetails
990 */
991 _handleException(exceptionDetails) {
992 const message = helper.getExceptionMessage(exceptionDetails);
993 const err = new Error(message);
994 err.stack = ''; // Don't report clientside error with a node stack attached
995 this.emit(Page.Events.PageError, err);
996 }
997
998 /**
999 * @param {!Protocol.Runtime.consoleAPICalledPayload} event
1000 */
1001 /* async */ _onConsoleAPI(event) {return (fn => {
1002 const gen = fn.call(this);
1003 return new Promise((resolve, reject) => {
1004 function step(key, arg) {
1005 let info, value;
1006 try {
1007 info = gen[key](arg);
1008 value = info.value;
1009 } catch (error) {
1010 reject(error);
1011 return;
1012 }
1013 if (info.done) {
1014 resolve(value);
1015 } else {
1016 return Promise.resolve(value).then(
1017 value => {
1018 step('next', value);
1019 },
1020 err => {
1021 step('throw', err);
1022 });
1023 }
1024 }
1025 return step('next');
1026 });
1027})(function*(){
1028 const context = this._frameManager.executionContextById(event.executionContextId);
1029 const values = event.args.map(arg => this._frameManager.createJSHandle(context, arg));
1030 this._addConsoleMessage(event.type, values);
1031 });}
1032
1033 /**
1034 * @param {!Protocol.Runtime.bindingCalledPayload} event
1035 */
1036 /* async */ _onBindingCalled(event) {return (fn => {
1037 const gen = fn.call(this);
1038 return new Promise((resolve, reject) => {
1039 function step(key, arg) {
1040 let info, value;
1041 try {
1042 info = gen[key](arg);
1043 value = info.value;
1044 } catch (error) {
1045 reject(error);
1046 return;
1047 }
1048 if (info.done) {
1049 resolve(value);
1050 } else {
1051 return Promise.resolve(value).then(
1052 value => {
1053 step('next', value);
1054 },
1055 err => {
1056 step('throw', err);
1057 });
1058 }
1059 }
1060 return step('next');
1061 });
1062})(function*(){
1063 const {name, seq, args} = JSON.parse(event.payload);
1064 const result = (yield this._pageBindings.get(name)(...args));
1065 const expression = helper.evaluationString(deliverResult, name, seq, result);
1066 this._client.send('Runtime.evaluate', { expression, contextId: event.executionContextId }).catch(debugError);
1067
1068 function deliverResult(name, seq, result) {
1069 window[name]['callbacks'].get(seq)(result);
1070 window[name]['callbacks'].delete(seq);
1071 }
1072 });}
1073
1074 /**
1075 * @param {string} type
1076 * @param {!Array<!Puppeteer.JSHandle>} args
1077 */
1078 _addConsoleMessage(type, args) {
1079 if (!this.listenerCount(Page.Events.Console)) {
1080 args.forEach(arg => arg.dispose());
1081 return;
1082 }
1083 const textTokens = [];
1084 for (const arg of args) {
1085 const remoteObject = arg._remoteObject;
1086 if (remoteObject.objectId)
1087 textTokens.push(arg.toString());
1088 else
1089 textTokens.push(helper.valueFromRemoteObject(remoteObject));
1090 }
1091 const message = new ConsoleMessage(type, textTokens.join(' '), args);
1092 this.emit(Page.Events.Console, message);
1093 }
1094
1095 _onDialog(event) {
1096 let dialogType = null;
1097 if (event.type === 'alert')
1098 dialogType = Dialog.Type.Alert;
1099 else if (event.type === 'confirm')
1100 dialogType = Dialog.Type.Confirm;
1101 else if (event.type === 'prompt')
1102 dialogType = Dialog.Type.Prompt;
1103 else if (event.type === 'beforeunload')
1104 dialogType = Dialog.Type.BeforeUnload;
1105 assert(dialogType, 'Unknown javascript dialog type: ' + event.type);
1106 const dialog = new Dialog(this._client, dialogType, event.message, event.defaultPrompt);
1107 this.emit(Page.Events.Dialog, dialog);
1108 }
1109
1110 /**
1111 * @return {!string}
1112 */
1113 url() {
1114 return this.mainFrame().url();
1115 }
1116
1117 /**
1118 * @return {!Promise<String>}
1119 */
1120 /* async */ content() {return (fn => {
1121 const gen = fn.call(this);
1122 return new Promise((resolve, reject) => {
1123 function step(key, arg) {
1124 let info, value;
1125 try {
1126 info = gen[key](arg);
1127 value = info.value;
1128 } catch (error) {
1129 reject(error);
1130 return;
1131 }
1132 if (info.done) {
1133 resolve(value);
1134 } else {
1135 return Promise.resolve(value).then(
1136 value => {
1137 step('next', value);
1138 },
1139 err => {
1140 step('throw', err);
1141 });
1142 }
1143 }
1144 return step('next');
1145 });
1146})(function*(){
1147 return (yield this._frameManager.mainFrame().content());
1148 });}
1149
1150 /**
1151 * @param {string} html
1152 */
1153 /* async */ setContent(html) {return (fn => {
1154 const gen = fn.call(this);
1155 return new Promise((resolve, reject) => {
1156 function step(key, arg) {
1157 let info, value;
1158 try {
1159 info = gen[key](arg);
1160 value = info.value;
1161 } catch (error) {
1162 reject(error);
1163 return;
1164 }
1165 if (info.done) {
1166 resolve(value);
1167 } else {
1168 return Promise.resolve(value).then(
1169 value => {
1170 step('next', value);
1171 },
1172 err => {
1173 step('throw', err);
1174 });
1175 }
1176 }
1177 return step('next');
1178 });
1179})(function*(){
1180 (yield this._frameManager.mainFrame().setContent(html));
1181 });}
1182
1183 /**
1184 * @param {string} url
1185 * @param {!Object=} options
1186 * @return {!Promise<?Puppeteer.Response>}
1187 */
1188 /* async */ goto(url, options = {}) {return (fn => {
1189 const gen = fn.call(this);
1190 return new Promise((resolve, reject) => {
1191 function step(key, arg) {
1192 let info, value;
1193 try {
1194 info = gen[key](arg);
1195 value = info.value;
1196 } catch (error) {
1197 reject(error);
1198 return;
1199 }
1200 if (info.done) {
1201 resolve(value);
1202 } else {
1203 return Promise.resolve(value).then(
1204 value => {
1205 step('next', value);
1206 },
1207 err => {
1208 step('throw', err);
1209 });
1210 }
1211 }
1212 return step('next');
1213 });
1214})(function*(){
1215 const referrer = this._networkManager.extraHTTPHeaders()['referer'];
1216
1217 /** @type {Map<string, !Puppeteer.Request>} */
1218 const requests = new Map();
1219 const eventListeners = [
1220 helper.addEventListener(this._networkManager, NetworkManager.Events.Request, request => {
1221 if (!requests.get(request.url()))
1222 requests.set(request.url(), request);
1223 })
1224 ];
1225
1226 const mainFrame = this._frameManager.mainFrame();
1227 const timeout = typeof options.timeout === 'number' ? options.timeout : this._defaultNavigationTimeout;
1228 const watcher = new NavigatorWatcher(this._frameManager, mainFrame, timeout, options);
1229 const navigationPromise = watcher.navigationPromise();
1230 let error = (yield Promise.race([
1231 navigate(this._client, url, referrer),
1232 navigationPromise,
1233 ]));
1234 if (!error)
1235 error = (yield navigationPromise);
1236 watcher.cancel();
1237 helper.removeEventListeners(eventListeners);
1238 if (error)
1239 throw error;
1240 const request = requests.get(mainFrame._navigationURL);
1241 return request ? request.response() : null;
1242
1243 /**
1244 * @param {!Puppeteer.CDPSession} client
1245 * @param {string} url
1246 * @param {string} referrer
1247 * @return {!Promise<?Error>}
1248 */
1249 /* async */ function navigate(client, url, referrer) {return (fn => {
1250 const gen = fn.call(this);
1251 return new Promise((resolve, reject) => {
1252 function step(key, arg) {
1253 let info, value;
1254 try {
1255 info = gen[key](arg);
1256 value = info.value;
1257 } catch (error) {
1258 reject(error);
1259 return;
1260 }
1261 if (info.done) {
1262 resolve(value);
1263 } else {
1264 return Promise.resolve(value).then(
1265 value => {
1266 step('next', value);
1267 },
1268 err => {
1269 step('throw', err);
1270 });
1271 }
1272 }
1273 return step('next');
1274 });
1275})(function*(){
1276 try {
1277 const response = (yield client.send('Page.navigate', {url, referrer}));
1278 return response.errorText ? new Error(`${response.errorText} at ${url}`) : null;
1279 } catch (error) {
1280 return error;
1281 }
1282 });}
1283 });}
1284
1285 /**
1286 * @param {!Object=} options
1287 * @return {!Promise<?Puppeteer.Response>}
1288 */
1289 /* async */ reload(options) {return (fn => {
1290 const gen = fn.call(this);
1291 return new Promise((resolve, reject) => {
1292 function step(key, arg) {
1293 let info, value;
1294 try {
1295 info = gen[key](arg);
1296 value = info.value;
1297 } catch (error) {
1298 reject(error);
1299 return;
1300 }
1301 if (info.done) {
1302 resolve(value);
1303 } else {
1304 return Promise.resolve(value).then(
1305 value => {
1306 step('next', value);
1307 },
1308 err => {
1309 step('throw', err);
1310 });
1311 }
1312 }
1313 return step('next');
1314 });
1315})(function*(){
1316 const [response] = (yield Promise.all([
1317 this.waitForNavigation(options),
1318 this._client.send('Page.reload')
1319 ]));
1320 return response;
1321 });}
1322
1323 /**
1324 * @param {!Object=} options
1325 * @return {!Promise<?Puppeteer.Response>}
1326 */
1327 /* async */ waitForNavigation(options = {}) {return (fn => {
1328 const gen = fn.call(this);
1329 return new Promise((resolve, reject) => {
1330 function step(key, arg) {
1331 let info, value;
1332 try {
1333 info = gen[key](arg);
1334 value = info.value;
1335 } catch (error) {
1336 reject(error);
1337 return;
1338 }
1339 if (info.done) {
1340 resolve(value);
1341 } else {
1342 return Promise.resolve(value).then(
1343 value => {
1344 step('next', value);
1345 },
1346 err => {
1347 step('throw', err);
1348 });
1349 }
1350 }
1351 return step('next');
1352 });
1353})(function*(){
1354 const mainFrame = this._frameManager.mainFrame();
1355 const timeout = typeof options.timeout === 'number' ? options.timeout : this._defaultNavigationTimeout;
1356 const watcher = new NavigatorWatcher(this._frameManager, mainFrame, timeout, options);
1357
1358 const responses = new Map();
1359 const listener = helper.addEventListener(this._networkManager, NetworkManager.Events.Response, response => responses.set(response.url(), response));
1360 const error = (yield watcher.navigationPromise());
1361 helper.removeEventListeners([listener]);
1362 if (error)
1363 throw error;
1364 return responses.get(this.mainFrame().url()) || null;
1365 });}
1366
1367 /**
1368 * @param {(string|Function)} urlOrPredicate
1369 * @param {!Object=} options
1370 * @return {!Promise<!Puppeteer.Request>}
1371 */
1372 /* async */ waitForRequest(urlOrPredicate, options = {}) {return (fn => {
1373 const gen = fn.call(this);
1374 return new Promise((resolve, reject) => {
1375 function step(key, arg) {
1376 let info, value;
1377 try {
1378 info = gen[key](arg);
1379 value = info.value;
1380 } catch (error) {
1381 reject(error);
1382 return;
1383 }
1384 if (info.done) {
1385 resolve(value);
1386 } else {
1387 return Promise.resolve(value).then(
1388 value => {
1389 step('next', value);
1390 },
1391 err => {
1392 step('throw', err);
1393 });
1394 }
1395 }
1396 return step('next');
1397 });
1398})(function*(){
1399 const timeout = typeof options.timeout === 'number' ? options.timeout : 30000;
1400 return helper.waitForEvent(this._networkManager, NetworkManager.Events.Request, request => {
1401 if (helper.isString(urlOrPredicate))
1402 return (urlOrPredicate === request.url());
1403 if (typeof urlOrPredicate === 'function')
1404 return !!(urlOrPredicate(request));
1405 return false;
1406 }, timeout);
1407 });}
1408
1409 /**
1410 * @param {(string|Function)} urlOrPredicate
1411 * @param {!Object=} options
1412 * @return {!Promise<!Puppeteer.Response>}
1413 */
1414 /* async */ waitForResponse(urlOrPredicate, options = {}) {return (fn => {
1415 const gen = fn.call(this);
1416 return new Promise((resolve, reject) => {
1417 function step(key, arg) {
1418 let info, value;
1419 try {
1420 info = gen[key](arg);
1421 value = info.value;
1422 } catch (error) {
1423 reject(error);
1424 return;
1425 }
1426 if (info.done) {
1427 resolve(value);
1428 } else {
1429 return Promise.resolve(value).then(
1430 value => {
1431 step('next', value);
1432 },
1433 err => {
1434 step('throw', err);
1435 });
1436 }
1437 }
1438 return step('next');
1439 });
1440})(function*(){
1441 const timeout = typeof options.timeout === 'number' ? options.timeout : 30000;
1442 return helper.waitForEvent(this._networkManager, NetworkManager.Events.Response, response => {
1443 if (helper.isString(urlOrPredicate))
1444 return (urlOrPredicate === response.url());
1445 if (typeof urlOrPredicate === 'function')
1446 return !!(urlOrPredicate(response));
1447 return false;
1448 }, timeout);
1449 });}
1450
1451 /**
1452 * @param {!Object=} options
1453 * @return {!Promise<?Puppeteer.Response>}
1454 */
1455 /* async */ goBack(options) {return (fn => {
1456 const gen = fn.call(this);
1457 return new Promise((resolve, reject) => {
1458 function step(key, arg) {
1459 let info, value;
1460 try {
1461 info = gen[key](arg);
1462 value = info.value;
1463 } catch (error) {
1464 reject(error);
1465 return;
1466 }
1467 if (info.done) {
1468 resolve(value);
1469 } else {
1470 return Promise.resolve(value).then(
1471 value => {
1472 step('next', value);
1473 },
1474 err => {
1475 step('throw', err);
1476 });
1477 }
1478 }
1479 return step('next');
1480 });
1481})(function*(){
1482 return this._go(-1, options);
1483 });}
1484
1485 /**
1486 * @param {!Object=} options
1487 * @return {!Promise<?Puppeteer.Response>}
1488 */
1489 /* async */ goForward(options) {return (fn => {
1490 const gen = fn.call(this);
1491 return new Promise((resolve, reject) => {
1492 function step(key, arg) {
1493 let info, value;
1494 try {
1495 info = gen[key](arg);
1496 value = info.value;
1497 } catch (error) {
1498 reject(error);
1499 return;
1500 }
1501 if (info.done) {
1502 resolve(value);
1503 } else {
1504 return Promise.resolve(value).then(
1505 value => {
1506 step('next', value);
1507 },
1508 err => {
1509 step('throw', err);
1510 });
1511 }
1512 }
1513 return step('next');
1514 });
1515})(function*(){
1516 return this._go(+1, options);
1517 });}
1518
1519 /**
1520 * @param {!Object=} options
1521 * @return {!Promise<?Puppeteer.Response>}
1522 */
1523 /* async */ _go(delta, options) {return (fn => {
1524 const gen = fn.call(this);
1525 return new Promise((resolve, reject) => {
1526 function step(key, arg) {
1527 let info, value;
1528 try {
1529 info = gen[key](arg);
1530 value = info.value;
1531 } catch (error) {
1532 reject(error);
1533 return;
1534 }
1535 if (info.done) {
1536 resolve(value);
1537 } else {
1538 return Promise.resolve(value).then(
1539 value => {
1540 step('next', value);
1541 },
1542 err => {
1543 step('throw', err);
1544 });
1545 }
1546 }
1547 return step('next');
1548 });
1549})(function*(){
1550 const history = (yield this._client.send('Page.getNavigationHistory'));
1551 const entry = history.entries[history.currentIndex + delta];
1552 if (!entry)
1553 return null;
1554 const [response] = (yield Promise.all([
1555 this.waitForNavigation(options),
1556 this._client.send('Page.navigateToHistoryEntry', {entryId: entry.id}),
1557 ]));
1558 return response;
1559 });}
1560
1561 /* async */ bringToFront() {return (fn => {
1562 const gen = fn.call(this);
1563 return new Promise((resolve, reject) => {
1564 function step(key, arg) {
1565 let info, value;
1566 try {
1567 info = gen[key](arg);
1568 value = info.value;
1569 } catch (error) {
1570 reject(error);
1571 return;
1572 }
1573 if (info.done) {
1574 resolve(value);
1575 } else {
1576 return Promise.resolve(value).then(
1577 value => {
1578 step('next', value);
1579 },
1580 err => {
1581 step('throw', err);
1582 });
1583 }
1584 }
1585 return step('next');
1586 });
1587})(function*(){
1588 (yield this._client.send('Page.bringToFront'));
1589 });}
1590
1591 /**
1592 * @param {!Object} options
1593 */
1594 /* async */ emulate(options) {return (fn => {
1595 const gen = fn.call(this);
1596 return new Promise((resolve, reject) => {
1597 function step(key, arg) {
1598 let info, value;
1599 try {
1600 info = gen[key](arg);
1601 value = info.value;
1602 } catch (error) {
1603 reject(error);
1604 return;
1605 }
1606 if (info.done) {
1607 resolve(value);
1608 } else {
1609 return Promise.resolve(value).then(
1610 value => {
1611 step('next', value);
1612 },
1613 err => {
1614 step('throw', err);
1615 });
1616 }
1617 }
1618 return step('next');
1619 });
1620})(function*(){
1621 return Promise.all([
1622 this.setViewport(options.viewport),
1623 this.setUserAgent(options.userAgent)
1624 ]);
1625 });}
1626
1627 /**
1628 * @param {boolean} enabled
1629 */
1630 /* async */ setJavaScriptEnabled(enabled) {return (fn => {
1631 const gen = fn.call(this);
1632 return new Promise((resolve, reject) => {
1633 function step(key, arg) {
1634 let info, value;
1635 try {
1636 info = gen[key](arg);
1637 value = info.value;
1638 } catch (error) {
1639 reject(error);
1640 return;
1641 }
1642 if (info.done) {
1643 resolve(value);
1644 } else {
1645 return Promise.resolve(value).then(
1646 value => {
1647 step('next', value);
1648 },
1649 err => {
1650 step('throw', err);
1651 });
1652 }
1653 }
1654 return step('next');
1655 });
1656})(function*(){
1657 if (this._javascriptEnabled === enabled)
1658 return;
1659 this._javascriptEnabled = enabled;
1660 (yield this._client.send('Emulation.setScriptExecutionDisabled', { value: !enabled }));
1661 });}
1662
1663 /**
1664 * @param {boolean} enabled
1665 */
1666 /* async */ setBypassCSP(enabled) {return (fn => {
1667 const gen = fn.call(this);
1668 return new Promise((resolve, reject) => {
1669 function step(key, arg) {
1670 let info, value;
1671 try {
1672 info = gen[key](arg);
1673 value = info.value;
1674 } catch (error) {
1675 reject(error);
1676 return;
1677 }
1678 if (info.done) {
1679 resolve(value);
1680 } else {
1681 return Promise.resolve(value).then(
1682 value => {
1683 step('next', value);
1684 },
1685 err => {
1686 step('throw', err);
1687 });
1688 }
1689 }
1690 return step('next');
1691 });
1692})(function*(){
1693 (yield this._client.send('Page.setBypassCSP', { enabled }));
1694 });}
1695
1696 /**
1697 * @param {?string} mediaType
1698 */
1699 /* async */ emulateMedia(mediaType) {return (fn => {
1700 const gen = fn.call(this);
1701 return new Promise((resolve, reject) => {
1702 function step(key, arg) {
1703 let info, value;
1704 try {
1705 info = gen[key](arg);
1706 value = info.value;
1707 } catch (error) {
1708 reject(error);
1709 return;
1710 }
1711 if (info.done) {
1712 resolve(value);
1713 } else {
1714 return Promise.resolve(value).then(
1715 value => {
1716 step('next', value);
1717 },
1718 err => {
1719 step('throw', err);
1720 });
1721 }
1722 }
1723 return step('next');
1724 });
1725})(function*(){
1726 assert(mediaType === 'screen' || mediaType === 'print' || mediaType === null, 'Unsupported media type: ' + mediaType);
1727 (yield this._client.send('Emulation.setEmulatedMedia', {media: mediaType || ''}));
1728 });}
1729
1730 /**
1731 * @param {!Puppeteer.Viewport} viewport
1732 */
1733 /* async */ setViewport(viewport) {return (fn => {
1734 const gen = fn.call(this);
1735 return new Promise((resolve, reject) => {
1736 function step(key, arg) {
1737 let info, value;
1738 try {
1739 info = gen[key](arg);
1740 value = info.value;
1741 } catch (error) {
1742 reject(error);
1743 return;
1744 }
1745 if (info.done) {
1746 resolve(value);
1747 } else {
1748 return Promise.resolve(value).then(
1749 value => {
1750 step('next', value);
1751 },
1752 err => {
1753 step('throw', err);
1754 });
1755 }
1756 }
1757 return step('next');
1758 });
1759})(function*(){
1760 const needsReload = (yield this._emulationManager.emulateViewport(viewport));
1761 this._viewport = viewport;
1762 if (needsReload)
1763 (yield this.reload());
1764 });}
1765
1766 /**
1767 * @return {?Puppeteer.Viewport}
1768 */
1769 viewport() {
1770 return this._viewport;
1771 }
1772
1773 /**
1774 * @param {function()|string} pageFunction
1775 * @param {!Array<*>} args
1776 * @return {!Promise<*>}
1777 */
1778 /* async */ evaluate(pageFunction, ...args) {return (fn => {
1779 const gen = fn.call(this);
1780 return new Promise((resolve, reject) => {
1781 function step(key, arg) {
1782 let info, value;
1783 try {
1784 info = gen[key](arg);
1785 value = info.value;
1786 } catch (error) {
1787 reject(error);
1788 return;
1789 }
1790 if (info.done) {
1791 resolve(value);
1792 } else {
1793 return Promise.resolve(value).then(
1794 value => {
1795 step('next', value);
1796 },
1797 err => {
1798 step('throw', err);
1799 });
1800 }
1801 }
1802 return step('next');
1803 });
1804})(function*(){
1805 return this._frameManager.mainFrame().evaluate(pageFunction, ...args);
1806 });}
1807
1808 /**
1809 * @param {function()|string} pageFunction
1810 * @param {!Array<*>} args
1811 */
1812 /* async */ evaluateOnNewDocument(pageFunction, ...args) {return (fn => {
1813 const gen = fn.call(this);
1814 return new Promise((resolve, reject) => {
1815 function step(key, arg) {
1816 let info, value;
1817 try {
1818 info = gen[key](arg);
1819 value = info.value;
1820 } catch (error) {
1821 reject(error);
1822 return;
1823 }
1824 if (info.done) {
1825 resolve(value);
1826 } else {
1827 return Promise.resolve(value).then(
1828 value => {
1829 step('next', value);
1830 },
1831 err => {
1832 step('throw', err);
1833 });
1834 }
1835 }
1836 return step('next');
1837 });
1838})(function*(){
1839 const source = helper.evaluationString(pageFunction, ...args);
1840 (yield this._client.send('Page.addScriptToEvaluateOnNewDocument', { source }));
1841 });}
1842
1843 /**
1844 * @param {Boolean} enabled
1845 * @returns {!Promise}
1846 */
1847 /* async */ setCacheEnabled(enabled = true) {return (fn => {
1848 const gen = fn.call(this);
1849 return new Promise((resolve, reject) => {
1850 function step(key, arg) {
1851 let info, value;
1852 try {
1853 info = gen[key](arg);
1854 value = info.value;
1855 } catch (error) {
1856 reject(error);
1857 return;
1858 }
1859 if (info.done) {
1860 resolve(value);
1861 } else {
1862 return Promise.resolve(value).then(
1863 value => {
1864 step('next', value);
1865 },
1866 err => {
1867 step('throw', err);
1868 });
1869 }
1870 }
1871 return step('next');
1872 });
1873})(function*(){
1874 (yield this._client.send('Network.setCacheDisabled', {cacheDisabled: !enabled}));
1875 });}
1876
1877 /**
1878 * @param {!Object=} options
1879 * @return {!Promise<!Buffer|!String>}
1880 */
1881 /* async */ screenshot(options = {}) {return (fn => {
1882 const gen = fn.call(this);
1883 return new Promise((resolve, reject) => {
1884 function step(key, arg) {
1885 let info, value;
1886 try {
1887 info = gen[key](arg);
1888 value = info.value;
1889 } catch (error) {
1890 reject(error);
1891 return;
1892 }
1893 if (info.done) {
1894 resolve(value);
1895 } else {
1896 return Promise.resolve(value).then(
1897 value => {
1898 step('next', value);
1899 },
1900 err => {
1901 step('throw', err);
1902 });
1903 }
1904 }
1905 return step('next');
1906 });
1907})(function*(){
1908 let screenshotType = null;
1909 // options.type takes precedence over inferring the type from options.path
1910 // because it may be a 0-length file with no extension created beforehand (i.e. as a temp file).
1911 if (options.type) {
1912 assert(options.type === 'png' || options.type === 'jpeg', 'Unknown options.type value: ' + options.type);
1913 screenshotType = options.type;
1914 } else if (options.path) {
1915 const mimeType = mime.getType(options.path);
1916 if (mimeType === 'image/png')
1917 screenshotType = 'png';
1918 else if (mimeType === 'image/jpeg')
1919 screenshotType = 'jpeg';
1920 assert(screenshotType, 'Unsupported screenshot mime type: ' + mimeType);
1921 }
1922
1923 if (!screenshotType)
1924 screenshotType = 'png';
1925
1926 if (options.quality) {
1927 assert(screenshotType === 'jpeg', 'options.quality is unsupported for the ' + screenshotType + ' screenshots');
1928 assert(typeof options.quality === 'number', 'Expected options.quality to be a number but found ' + (typeof options.quality));
1929 assert(Number.isInteger(options.quality), 'Expected options.quality to be an integer');
1930 assert(options.quality >= 0 && options.quality <= 100, 'Expected options.quality to be between 0 and 100 (inclusive), got ' + options.quality);
1931 }
1932 assert(!options.clip || !options.fullPage, 'options.clip and options.fullPage are exclusive');
1933 if (options.clip) {
1934 assert(typeof options.clip.x === 'number', 'Expected options.clip.x to be a number but found ' + (typeof options.clip.x));
1935 assert(typeof options.clip.y === 'number', 'Expected options.clip.y to be a number but found ' + (typeof options.clip.y));
1936 assert(typeof options.clip.width === 'number', 'Expected options.clip.width to be a number but found ' + (typeof options.clip.width));
1937 assert(typeof options.clip.height === 'number', 'Expected options.clip.height to be a number but found ' + (typeof options.clip.height));
1938 }
1939 return this._screenshotTaskQueue.postTask(this._screenshotTask.bind(this, screenshotType, options));
1940 });}
1941
1942 /**
1943 * @param {"png"|"jpeg"} format
1944 * @param {!Object=} options
1945 * @return {!Promise<!Buffer|!String>}
1946 */
1947 /* async */ _screenshotTask(format, options) {return (fn => {
1948 const gen = fn.call(this);
1949 return new Promise((resolve, reject) => {
1950 function step(key, arg) {
1951 let info, value;
1952 try {
1953 info = gen[key](arg);
1954 value = info.value;
1955 } catch (error) {
1956 reject(error);
1957 return;
1958 }
1959 if (info.done) {
1960 resolve(value);
1961 } else {
1962 return Promise.resolve(value).then(
1963 value => {
1964 step('next', value);
1965 },
1966 err => {
1967 step('throw', err);
1968 });
1969 }
1970 }
1971 return step('next');
1972 });
1973})(function*(){
1974 (yield this._client.send('Target.activateTarget', {targetId: this._target._targetId}));
1975 let clip = options.clip ? Object.assign({}, options['clip']) : undefined;
1976 if (clip)
1977 clip.scale = 1;
1978
1979 if (options.fullPage) {
1980 const metrics = (yield this._client.send('Page.getLayoutMetrics'));
1981 const width = Math.ceil(metrics.contentSize.width);
1982 const height = Math.ceil(metrics.contentSize.height);
1983
1984 // Overwrite clip for full page at all times.
1985 clip = { x: 0, y: 0, width, height, scale: 1 };
1986 const mobile = this._viewport.isMobile || false;
1987 const deviceScaleFactor = this._viewport.deviceScaleFactor || 1;
1988 const landscape = this._viewport.isLandscape || false;
1989 /** @type {!Protocol.Emulation.ScreenOrientation} */
1990 const screenOrientation = landscape ? { angle: 90, type: 'landscapePrimary' } : { angle: 0, type: 'portraitPrimary' };
1991 (yield this._client.send('Emulation.setDeviceMetricsOverride', { mobile, width, height, deviceScaleFactor, screenOrientation }));
1992 }
1993
1994 if (options.omitBackground)
1995 (yield this._client.send('Emulation.setDefaultBackgroundColorOverride', { color: { r: 0, g: 0, b: 0, a: 0 } }));
1996 const result = (yield this._client.send('Page.captureScreenshot', { format, quality: options.quality, clip }));
1997 if (options.omitBackground)
1998 (yield this._client.send('Emulation.setDefaultBackgroundColorOverride'));
1999
2000 if (options.fullPage)
2001 (yield this.setViewport(this._viewport));
2002
2003 const buffer = options.encoding === 'base64' ? result.data : Buffer.from(result.data, 'base64');
2004 if (options.path)
2005 (yield writeFileAsync(options.path, buffer));
2006 return buffer;
2007 });}
2008
2009 /**
2010 * @param {!Object=} options
2011 * @return {!Promise<!Buffer>}
2012 */
2013 /* async */ pdf(options = {}) {return (fn => {
2014 const gen = fn.call(this);
2015 return new Promise((resolve, reject) => {
2016 function step(key, arg) {
2017 let info, value;
2018 try {
2019 info = gen[key](arg);
2020 value = info.value;
2021 } catch (error) {
2022 reject(error);
2023 return;
2024 }
2025 if (info.done) {
2026 resolve(value);
2027 } else {
2028 return Promise.resolve(value).then(
2029 value => {
2030 step('next', value);
2031 },
2032 err => {
2033 step('throw', err);
2034 });
2035 }
2036 }
2037 return step('next');
2038 });
2039})(function*(){
2040 const scale = options.scale || 1;
2041 const displayHeaderFooter = !!options.displayHeaderFooter;
2042 const headerTemplate = options.headerTemplate || '';
2043 const footerTemplate = options.footerTemplate || '';
2044 const printBackground = !!options.printBackground;
2045 const landscape = !!options.landscape;
2046 const pageRanges = options.pageRanges || '';
2047
2048 let paperWidth = 8.5;
2049 let paperHeight = 11;
2050 if (options.format) {
2051 const format = Page.PaperFormats[options.format.toLowerCase()];
2052 assert(format, 'Unknown paper format: ' + options.format);
2053 paperWidth = format.width;
2054 paperHeight = format.height;
2055 } else {
2056 paperWidth = convertPrintParameterToInches(options.width) || paperWidth;
2057 paperHeight = convertPrintParameterToInches(options.height) || paperHeight;
2058 }
2059
2060 const marginOptions = options.margin || {};
2061 const marginTop = convertPrintParameterToInches(marginOptions.top) || 0;
2062 const marginLeft = convertPrintParameterToInches(marginOptions.left) || 0;
2063 const marginBottom = convertPrintParameterToInches(marginOptions.bottom) || 0;
2064 const marginRight = convertPrintParameterToInches(marginOptions.right) || 0;
2065 const preferCSSPageSize = options.preferCSSPageSize || false;
2066
2067 const result = (yield this._client.send('Page.printToPDF', {
2068 landscape: landscape,
2069 displayHeaderFooter: displayHeaderFooter,
2070 headerTemplate: headerTemplate,
2071 footerTemplate: footerTemplate,
2072 printBackground: printBackground,
2073 scale: scale,
2074 paperWidth: paperWidth,
2075 paperHeight: paperHeight,
2076 marginTop: marginTop,
2077 marginBottom: marginBottom,
2078 marginLeft: marginLeft,
2079 marginRight: marginRight,
2080 pageRanges: pageRanges,
2081 preferCSSPageSize: preferCSSPageSize
2082 }));
2083 const buffer = Buffer.from(result.data, 'base64');
2084 if (options.path)
2085 (yield writeFileAsync(options.path, buffer));
2086 return buffer;
2087 });}
2088
2089 /**
2090 * @return {!Promise<string>}
2091 */
2092 /* async */ title() {return (fn => {
2093 const gen = fn.call(this);
2094 return new Promise((resolve, reject) => {
2095 function step(key, arg) {
2096 let info, value;
2097 try {
2098 info = gen[key](arg);
2099 value = info.value;
2100 } catch (error) {
2101 reject(error);
2102 return;
2103 }
2104 if (info.done) {
2105 resolve(value);
2106 } else {
2107 return Promise.resolve(value).then(
2108 value => {
2109 step('next', value);
2110 },
2111 err => {
2112 step('throw', err);
2113 });
2114 }
2115 }
2116 return step('next');
2117 });
2118})(function*(){
2119 return this.mainFrame().title();
2120 });}
2121
2122 /**
2123 * @param {!{runBeforeUnload: (boolean|undefined)}=} options
2124 */
2125 /* async */ close(options = {runBeforeUnload: undefined}) {return (fn => {
2126 const gen = fn.call(this);
2127 return new Promise((resolve, reject) => {
2128 function step(key, arg) {
2129 let info, value;
2130 try {
2131 info = gen[key](arg);
2132 value = info.value;
2133 } catch (error) {
2134 reject(error);
2135 return;
2136 }
2137 if (info.done) {
2138 resolve(value);
2139 } else {
2140 return Promise.resolve(value).then(
2141 value => {
2142 step('next', value);
2143 },
2144 err => {
2145 step('throw', err);
2146 });
2147 }
2148 }
2149 return step('next');
2150 });
2151})(function*(){
2152 assert(!!this._client._connection, 'Protocol error: Connection closed. Most likely the page has been closed.');
2153 const runBeforeUnload = !!options.runBeforeUnload;
2154 if (runBeforeUnload) {
2155 (yield this._client.send('Page.close'));
2156 } else {
2157 (yield this._client._connection.send('Target.closeTarget', { targetId: this._target._targetId }));
2158 (yield this._target._isClosedPromise);
2159 }
2160 });}
2161
2162 /**
2163 * @return {boolean}
2164 */
2165 isClosed() {
2166 return this._closed;
2167 }
2168
2169 /**
2170 * @return {!Mouse}
2171 */
2172 get mouse() {
2173 return this._mouse;
2174 }
2175
2176 /**
2177 * @param {string} selector
2178 * @param {!Object=} options
2179 */
2180 click(selector, options = {}) {
2181 return this.mainFrame().click(selector, options);
2182 }
2183
2184 /**
2185 * @param {string} selector
2186 */
2187 focus(selector) {
2188 return this.mainFrame().focus(selector);
2189 }
2190
2191 /**
2192 * @param {string} selector
2193 */
2194 hover(selector) {
2195 return this.mainFrame().hover(selector);
2196 }
2197
2198 /**
2199 * @param {string} selector
2200 * @param {!Array<string>} values
2201 * @return {!Promise<!Array<string>>}
2202 */
2203 select(selector, ...values) {
2204 return this.mainFrame().select(selector, ...values);
2205 }
2206
2207 /**
2208 * @param {string} selector
2209 */
2210 tap(selector) {
2211 return this.mainFrame().tap(selector);
2212 }
2213
2214 /**
2215 * @param {string} selector
2216 * @param {string} text
2217 * @param {{delay: (number|undefined)}=} options
2218 */
2219 type(selector, text, options) {
2220 return this.mainFrame().type(selector, text, options);
2221 }
2222
2223 /**
2224 * @param {(string|number|Function)} selectorOrFunctionOrTimeout
2225 * @param {!Object=} options
2226 * @param {!Array<*>} args
2227 * @return {!Promise}
2228 */
2229 waitFor(selectorOrFunctionOrTimeout, options = {}, ...args) {
2230 return this.mainFrame().waitFor(selectorOrFunctionOrTimeout, options, ...args);
2231 }
2232
2233 /**
2234 * @param {string} selector
2235 * @param {!Object=} options
2236 * @return {!Promise}
2237 */
2238 waitForSelector(selector, options = {}) {
2239 return this.mainFrame().waitForSelector(selector, options);
2240 }
2241
2242 /**
2243 * @param {string} xpath
2244 * @param {!Object=} options
2245 * @return {!Promise}
2246 */
2247 waitForXPath(xpath, options = {}) {
2248 return this.mainFrame().waitForXPath(xpath, options);
2249 }
2250
2251 /**
2252 * @param {function()} pageFunction
2253 * @param {!Object=} options
2254 * @param {!Array<*>} args
2255 * @return {!Promise}
2256 */
2257 waitForFunction(pageFunction, options = {}, ...args) {
2258 return this.mainFrame().waitForFunction(pageFunction, options, ...args);
2259 }
2260}
2261
2262/** @type {!Set<string>} */
2263const supportedMetrics = new Set([
2264 'Timestamp',
2265 'Documents',
2266 'Frames',
2267 'JSEventListeners',
2268 'Nodes',
2269 'LayoutCount',
2270 'RecalcStyleCount',
2271 'LayoutDuration',
2272 'RecalcStyleDuration',
2273 'ScriptDuration',
2274 'TaskDuration',
2275 'JSHeapUsedSize',
2276 'JSHeapTotalSize',
2277]);
2278
2279/** @enum {string} */
2280Page.PaperFormats = {
2281 letter: {width: 8.5, height: 11},
2282 legal: {width: 8.5, height: 14},
2283 tabloid: {width: 11, height: 17},
2284 ledger: {width: 17, height: 11},
2285 a0: {width: 33.1, height: 46.8 },
2286 a1: {width: 23.4, height: 33.1 },
2287 a2: {width: 16.5, height: 23.4 },
2288 a3: {width: 11.7, height: 16.5 },
2289 a4: {width: 8.27, height: 11.7 },
2290 a5: {width: 5.83, height: 8.27 },
2291 a6: {width: 4.13, height: 5.83 },
2292};
2293
2294const unitToPixels = {
2295 'px': 1,
2296 'in': 96,
2297 'cm': 37.8,
2298 'mm': 3.78
2299};
2300
2301/**
2302 * @param {(string|number|undefined)} parameter
2303 * @return {(number|undefined)}
2304 */
2305function convertPrintParameterToInches(parameter) {
2306 if (typeof parameter === 'undefined')
2307 return undefined;
2308 let pixels;
2309 if (helper.isNumber(parameter)) {
2310 // Treat numbers as pixel values to be aligned with phantom's paperSize.
2311 pixels = /** @type {number} */ (parameter);
2312 } else if (helper.isString(parameter)) {
2313 const text = /** @type {string} */ (parameter);
2314 let unit = text.substring(text.length - 2).toLowerCase();
2315 let valueText = '';
2316 if (unitToPixels.hasOwnProperty(unit)) {
2317 valueText = text.substring(0, text.length - 2);
2318 } else {
2319 // In case of unknown unit try to parse the whole parameter as number of pixels.
2320 // This is consistent with phantom's paperSize behavior.
2321 unit = 'px';
2322 valueText = text;
2323 }
2324 const value = Number(valueText);
2325 assert(!isNaN(value), 'Failed to parse parameter value: ' + text);
2326 pixels = value * unitToPixels[unit];
2327 } else {
2328 throw new Error('page.pdf() Cannot handle parameter type: ' + (typeof parameter));
2329 }
2330 return pixels / 96;
2331}
2332
2333Page.Events = {
2334 Close: 'close',
2335 Console: 'console',
2336 Dialog: 'dialog',
2337 DOMContentLoaded: 'domcontentloaded',
2338 Error: 'error',
2339 // Can't use just 'error' due to node.js special treatment of error events.
2340 // @see https://nodejs.org/api/events.html#events_error_events
2341 PageError: 'pageerror',
2342 Request: 'request',
2343 Response: 'response',
2344 RequestFailed: 'requestfailed',
2345 RequestFinished: 'requestfinished',
2346 FrameAttached: 'frameattached',
2347 FrameDetached: 'framedetached',
2348 FrameNavigated: 'framenavigated',
2349 Load: 'load',
2350 Metrics: 'metrics',
2351 WorkerCreated: 'workercreated',
2352 WorkerDestroyed: 'workerdestroyed',
2353};
2354
2355
2356/**
2357 * @typedef {Object} Network.Cookie
2358 * @property {string} name
2359 * @property {string} value
2360 * @property {string} domain
2361 * @property {string} path
2362 * @property {number} expires
2363 * @property {number} size
2364 * @property {boolean} httpOnly
2365 * @property {boolean} secure
2366 * @property {boolean} session
2367 * @property {("Strict"|"Lax")=} sameSite
2368 */
2369
2370
2371/**
2372 * @typedef {Object} Network.CookieParam
2373 * @property {string} name
2374 * @property {string} value
2375 * @property {string=} url
2376 * @property {string=} domain
2377 * @property {string=} path
2378 * @property {number=} expires
2379 * @property {boolean=} httpOnly
2380 * @property {boolean=} secure
2381 * @property {("Strict"|"Lax")=} sameSite
2382 */
2383
2384class ConsoleMessage {
2385 /**
2386 * @param {string} type
2387 * @param {string} text
2388 * @param {!Array<!Puppeteer.JSHandle>} args
2389 */
2390 constructor(type, text, args = []) {
2391 this._type = type;
2392 this._text = text;
2393 this._args = args;
2394 }
2395
2396 /**
2397 * @return {string}
2398 */
2399 type() {
2400 return this._type;
2401 }
2402
2403 /**
2404 * @return {string}
2405 */
2406 text() {
2407 return this._text;
2408 }
2409
2410 /**
2411 * @return {!Array<!Puppeteer.JSHandle>}
2412 */
2413 args() {
2414 return this._args;
2415 }
2416}
2417
2418
2419module.exports = {Page};
2420helper.tracePublicAPI(Page);