UNPKG

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