UNPKG

5.23 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 {FrameManager} = require('./FrameManager');
19const {TimeoutError} = require('./Errors');
20
21class NavigatorWatcher {
22 /**
23 * @param {!FrameManager} frameManager
24 * @param {!Puppeteer.Frame} frame
25 * @param {number} timeout
26 * @param {!Object=} options
27 */
28 constructor(frameManager, frame, timeout, options = {}) {
29 assert(options.networkIdleTimeout === undefined, 'ERROR: networkIdleTimeout option is no longer supported.');
30 assert(options.networkIdleInflight === undefined, 'ERROR: networkIdleInflight option is no longer supported.');
31 assert(options.waitUntil !== 'networkidle', 'ERROR: "networkidle" option is no longer supported. Use "networkidle2" instead');
32 let waitUntil = ['load'];
33 if (Array.isArray(options.waitUntil))
34 waitUntil = options.waitUntil.slice();
35 else if (typeof options.waitUntil === 'string')
36 waitUntil = [options.waitUntil];
37 this._expectedLifecycle = waitUntil.map(value => {
38 const protocolEvent = puppeteerToProtocolLifecycle[value];
39 assert(protocolEvent, 'Unknown value for options.waitUntil: ' + value);
40 return protocolEvent;
41 });
42
43 this._frameManager = frameManager;
44 this._frame = frame;
45 this._initialLoaderId = frame._loaderId;
46 this._timeout = timeout;
47 this._hasSameDocumentNavigation = false;
48 this._eventListeners = [
49 helper.addEventListener(this._frameManager, FrameManager.Events.LifecycleEvent, this._checkLifecycleComplete.bind(this)),
50 helper.addEventListener(this._frameManager, FrameManager.Events.FrameNavigatedWithinDocument, this._navigatedWithinDocument.bind(this)),
51 helper.addEventListener(this._frameManager, FrameManager.Events.FrameDetached, this._checkLifecycleComplete.bind(this))
52 ];
53
54 const lifecycleCompletePromise = new Promise(fulfill => {
55 this._lifecycleCompleteCallback = fulfill;
56 });
57 this._navigationPromise = Promise.race([
58 this._createTimeoutPromise(),
59 lifecycleCompletePromise
60 ]).then(error => {
61 this._cleanup();
62 return error;
63 });
64 }
65
66 /**
67 * @return {!Promise<?Error>}
68 */
69 _createTimeoutPromise() {
70 if (!this._timeout)
71 return new Promise(() => {});
72 const errorMessage = 'Navigation Timeout Exceeded: ' + this._timeout + 'ms exceeded';
73 return new Promise(fulfill => this._maximumTimer = setTimeout(fulfill, this._timeout))
74 .then(() => new TimeoutError(errorMessage));
75 }
76
77 /**
78 * @return {!Promise<?Error>}
79 */
80 /* async */ navigationPromise() {return (fn => {
81 const gen = fn.call(this);
82 return new Promise((resolve, reject) => {
83 function step(key, arg) {
84 let info, value;
85 try {
86 info = gen[key](arg);
87 value = info.value;
88 } catch (error) {
89 reject(error);
90 return;
91 }
92 if (info.done) {
93 resolve(value);
94 } else {
95 return Promise.resolve(value).then(
96 value => {
97 step('next', value);
98 },
99 err => {
100 step('throw', err);
101 });
102 }
103 }
104 return step('next');
105 });
106})(function*(){
107 return this._navigationPromise;
108 });}
109
110 /**
111 * @param {!Puppeteer.Frame} frame
112 */
113 _navigatedWithinDocument(frame) {
114 if (frame !== this._frame)
115 return;
116 this._hasSameDocumentNavigation = true;
117 this._checkLifecycleComplete();
118 }
119
120 _checkLifecycleComplete() {
121 // We expect navigation to commit.
122 if (this._frame._loaderId === this._initialLoaderId && !this._hasSameDocumentNavigation)
123 return;
124 if (!checkLifecycle(this._frame, this._expectedLifecycle))
125 return;
126 this._lifecycleCompleteCallback();
127
128 /**
129 * @param {!Puppeteer.Frame} frame
130 * @param {!Array<string>} expectedLifecycle
131 * @return {boolean}
132 */
133 function checkLifecycle(frame, expectedLifecycle) {
134 for (const event of expectedLifecycle) {
135 if (!frame._lifecycleEvents.has(event))
136 return false;
137 }
138 for (const child of frame.childFrames()) {
139 if (!checkLifecycle(child, expectedLifecycle))
140 return false;
141 }
142 return true;
143 }
144 }
145
146 cancel() {
147 this._cleanup();
148 }
149
150 _cleanup() {
151 helper.removeEventListeners(this._eventListeners);
152 this._lifecycleCompleteCallback(new Error('Navigation failed'));
153 clearTimeout(this._maximumTimer);
154 }
155}
156
157const puppeteerToProtocolLifecycle = {
158 'load': 'load',
159 'domcontentloaded': 'DOMContentLoaded',
160 'networkidle0': 'networkIdle',
161 'networkidle2': 'networkAlmostIdle',
162};
163
164module.exports = {NavigatorWatcher};