UNPKG

4.63 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() {
81 return this._navigationPromise;
82 }
83
84 /**
85 * @param {!Puppeteer.Frame} frame
86 */
87 _navigatedWithinDocument(frame) {
88 if (frame !== this._frame)
89 return;
90 this._hasSameDocumentNavigation = true;
91 this._checkLifecycleComplete();
92 }
93
94 _checkLifecycleComplete() {
95 // We expect navigation to commit.
96 if (this._frame._loaderId === this._initialLoaderId && !this._hasSameDocumentNavigation)
97 return;
98 if (!checkLifecycle(this._frame, this._expectedLifecycle))
99 return;
100 this._lifecycleCompleteCallback();
101
102 /**
103 * @param {!Puppeteer.Frame} frame
104 * @param {!Array<string>} expectedLifecycle
105 * @return {boolean}
106 */
107 function checkLifecycle(frame, expectedLifecycle) {
108 for (const event of expectedLifecycle) {
109 if (!frame._lifecycleEvents.has(event))
110 return false;
111 }
112 for (const child of frame.childFrames()) {
113 if (!checkLifecycle(child, expectedLifecycle))
114 return false;
115 }
116 return true;
117 }
118 }
119
120 cancel() {
121 this._cleanup();
122 }
123
124 _cleanup() {
125 helper.removeEventListeners(this._eventListeners);
126 this._lifecycleCompleteCallback(new Error('Navigation failed'));
127 clearTimeout(this._maximumTimer);
128 }
129}
130
131const puppeteerToProtocolLifecycle = {
132 'load': 'load',
133 'domcontentloaded': 'DOMContentLoaded',
134 'networkidle0': 'networkIdle',
135 'networkidle2': 'networkAlmostIdle',
136};
137
138module.exports = {NavigatorWatcher};