UNPKG

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