81d2600236
This corresponds to Chromium 79.0.3942.0. This roll includes: - Support CSS media feature emulation in CDP https://chromium-review.googlesource.com/c/chromium/src/+/1821608 - Implement timezone ID verification https://chromium-review.googlesource.com/c/chromium/src/+/1822557 - Allow aria-hidden objects into the browser-side ax tree https://chromium-review.googlesource.com/c/chromium/src/+/1760862 - Remove pre-BlinkGenPropertyTrees codepaths (affects screenshot clipping) https://chromium-review.googlesource.com/c/chromium/src/+/1752244 - Terminate some asynchronous waiting commands on cross-process navigation https://chromium-review.googlesource.com/c/chromium/src/+/1766001/21#message-a6a61261b97ffca6ecb180c0a2303b538f7a6476 Per upstream Chromium changes, `page.screenshot` now clips elements to the viewport. This matches the clipping behavior of elements in inner scrollers (i.e., document and overflow scroll clipping now work the same).
199 lines
5.9 KiB
JavaScript
199 lines
5.9 KiB
JavaScript
/**
|
|
* Copyright 2019 Google Inc. All rights reserved.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
const {helper, assert} = require('./helper');
|
|
const {Events} = require('./Events');
|
|
const {TimeoutError} = require('./Errors');
|
|
|
|
class LifecycleWatcher {
|
|
/**
|
|
* @param {!Puppeteer.FrameManager} frameManager
|
|
* @param {!Puppeteer.Frame} frame
|
|
* @param {string|!Array<string>} waitUntil
|
|
* @param {number} timeout
|
|
*/
|
|
constructor(frameManager, frame, waitUntil, timeout) {
|
|
if (Array.isArray(waitUntil))
|
|
waitUntil = waitUntil.slice();
|
|
else if (typeof waitUntil === 'string')
|
|
waitUntil = [waitUntil];
|
|
this._expectedLifecycle = waitUntil.map(value => {
|
|
const protocolEvent = puppeteerToProtocolLifecycle[value];
|
|
assert(protocolEvent, 'Unknown value for options.waitUntil: ' + value);
|
|
return protocolEvent;
|
|
});
|
|
|
|
this._frameManager = frameManager;
|
|
this._frame = frame;
|
|
this._initialLoaderId = frame._loaderId;
|
|
this._timeout = timeout;
|
|
/** @type {?Puppeteer.Request} */
|
|
this._navigationRequest = null;
|
|
this._eventListeners = [
|
|
helper.addEventListener(frameManager._client, Events.CDPSession.Disconnected, () => this._terminate(new Error('Navigation failed because browser has disconnected!'))),
|
|
helper.addEventListener(this._frameManager, Events.FrameManager.LifecycleEvent, this._checkLifecycleComplete.bind(this)),
|
|
helper.addEventListener(this._frameManager, Events.FrameManager.FrameNavigatedWithinDocument, this._navigatedWithinDocument.bind(this)),
|
|
helper.addEventListener(this._frameManager, Events.FrameManager.FrameDetached, this._onFrameDetached.bind(this)),
|
|
helper.addEventListener(this._frameManager.networkManager(), Events.NetworkManager.Request, this._onRequest.bind(this)),
|
|
];
|
|
|
|
this._sameDocumentNavigationPromise = new Promise(fulfill => {
|
|
this._sameDocumentNavigationCompleteCallback = fulfill;
|
|
});
|
|
|
|
this._lifecyclePromise = new Promise(fulfill => {
|
|
this._lifecycleCallback = fulfill;
|
|
});
|
|
|
|
this._newDocumentNavigationPromise = new Promise(fulfill => {
|
|
this._newDocumentNavigationCompleteCallback = fulfill;
|
|
});
|
|
|
|
this._timeoutPromise = this._createTimeoutPromise();
|
|
this._terminationPromise = new Promise(fulfill => {
|
|
this._terminationCallback = fulfill;
|
|
});
|
|
this._checkLifecycleComplete();
|
|
}
|
|
|
|
/**
|
|
* @param {!Puppeteer.Request} request
|
|
*/
|
|
_onRequest(request) {
|
|
if (request.frame() !== this._frame || !request.isNavigationRequest())
|
|
return;
|
|
this._navigationRequest = request;
|
|
}
|
|
|
|
/**
|
|
* @param {!Puppeteer.Frame} frame
|
|
*/
|
|
_onFrameDetached(frame) {
|
|
if (this._frame === frame) {
|
|
this._terminationCallback.call(null, new Error('Navigating frame was detached'));
|
|
return;
|
|
}
|
|
this._checkLifecycleComplete();
|
|
}
|
|
|
|
/**
|
|
* @return {?Puppeteer.Response}
|
|
*/
|
|
navigationResponse() {
|
|
return this._navigationRequest ? this._navigationRequest.response() : null;
|
|
}
|
|
|
|
/**
|
|
* @param {!Error} error
|
|
*/
|
|
_terminate(error) {
|
|
this._terminationCallback.call(null, error);
|
|
}
|
|
|
|
/**
|
|
* @return {!Promise<?Error>}
|
|
*/
|
|
sameDocumentNavigationPromise() {
|
|
return this._sameDocumentNavigationPromise;
|
|
}
|
|
|
|
/**
|
|
* @return {!Promise<?Error>}
|
|
*/
|
|
newDocumentNavigationPromise() {
|
|
return this._newDocumentNavigationPromise;
|
|
}
|
|
|
|
/**
|
|
* @return {!Promise}
|
|
*/
|
|
lifecyclePromise() {
|
|
return this._lifecyclePromise;
|
|
}
|
|
|
|
/**
|
|
* @return {!Promise<?Error>}
|
|
*/
|
|
timeoutOrTerminationPromise() {
|
|
return Promise.race([this._timeoutPromise, this._terminationPromise]);
|
|
}
|
|
|
|
/**
|
|
* @return {!Promise<?Error>}
|
|
*/
|
|
_createTimeoutPromise() {
|
|
if (!this._timeout)
|
|
return new Promise(() => {});
|
|
const errorMessage = 'Navigation timeout of ' + this._timeout + ' ms exceeded';
|
|
return new Promise(fulfill => this._maximumTimer = setTimeout(fulfill, this._timeout))
|
|
.then(() => new TimeoutError(errorMessage));
|
|
}
|
|
|
|
/**
|
|
* @param {!Puppeteer.Frame} frame
|
|
*/
|
|
_navigatedWithinDocument(frame) {
|
|
if (frame !== this._frame)
|
|
return;
|
|
this._hasSameDocumentNavigation = true;
|
|
this._checkLifecycleComplete();
|
|
}
|
|
|
|
_checkLifecycleComplete() {
|
|
// We expect navigation to commit.
|
|
if (!checkLifecycle(this._frame, this._expectedLifecycle))
|
|
return;
|
|
this._lifecycleCallback();
|
|
if (this._frame._loaderId === this._initialLoaderId && !this._hasSameDocumentNavigation)
|
|
return;
|
|
if (this._hasSameDocumentNavigation)
|
|
this._sameDocumentNavigationCompleteCallback();
|
|
if (this._frame._loaderId !== this._initialLoaderId)
|
|
this._newDocumentNavigationCompleteCallback();
|
|
|
|
/**
|
|
* @param {!Puppeteer.Frame} frame
|
|
* @param {!Array<string>} expectedLifecycle
|
|
* @return {boolean}
|
|
*/
|
|
function checkLifecycle(frame, expectedLifecycle) {
|
|
for (const event of expectedLifecycle) {
|
|
if (!frame._lifecycleEvents.has(event))
|
|
return false;
|
|
}
|
|
for (const child of frame.childFrames()) {
|
|
if (!checkLifecycle(child, expectedLifecycle))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
dispose() {
|
|
helper.removeEventListeners(this._eventListeners);
|
|
clearTimeout(this._maximumTimer);
|
|
}
|
|
}
|
|
|
|
const puppeteerToProtocolLifecycle = {
|
|
'load': 'load',
|
|
'domcontentloaded': 'DOMContentLoaded',
|
|
'networkidle0': 'networkIdle',
|
|
'networkidle2': 'networkAlmostIdle',
|
|
};
|
|
|
|
module.exports = {LifecycleWatcher};
|