From bc0655b5872b7be9ee3dcb0d7541791da9ee415f Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Tue, 20 Jun 2017 16:41:27 -0700 Subject: [PATCH] Extract basic in-flight counting navigator --- lib/Navigator.js | 98 ++++++++++++++++++++++++++++++++++++++++++++++++ lib/Page.js | 16 +++----- 2 files changed, 103 insertions(+), 11 deletions(-) create mode 100644 lib/Navigator.js diff --git a/lib/Navigator.js b/lib/Navigator.js new file mode 100644 index 00000000..aafa6b42 --- /dev/null +++ b/lib/Navigator.js @@ -0,0 +1,98 @@ +/** + * Copyright 2017 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. + */ + +class Navigator { + /** + * @param {!Connection} client + * @param {!Object=} options + */ + constructor(client, options) { + this._client = client; + client.on('Network.requestWillBeSent', event => this._onRequestWillBeSent(event)); + client.on('Network.loadingFinished', event => this._onLoadingFinished(event)); + client.on('Network.loadingFailed', event => this._onLoadingFailed(event)); + this._minTime = options && options['minTime'] ? options['minTime'] : 0; + this._maxTime = options && options['maxTime'] ? options['maxTime'] : 30000; + this._inflightRequests = 0; + } + + /** + * @param {string} url + * @param {string=} referrer + */ + async navigate(url, referrer) { + this._navigationStartTime = Date.now(); + this._watchdogTimer = setTimeout(this._completeNavigation.bind(this, true), this._maxTime); + this._minimumTimer = setTimeout(this._completeNavigation.bind(this, false), this._minTime); + let onload = new Promise(fulfill => this._client.once('Page.loadEventFired', fulfill)); + let networkIdle = new Promise(fulfill => this._navigationLoadCallback = fulfill); + var interstitialPromise = new Promise(fulfill => this._client.once('Security.certificateError', fulfill)).then(() => false); + + this._inflightRequests = 0; + // Await for the command to throw exception in case of illegal arguments. + try { + await this._client.send('Page.navigate', {url, referrer}); + } catch (e) { + return false; + } + return await Promise.race([Promise.all([onload, networkIdle]).then(() => true), interstitialPromise]); + } + + /** + * @param {!Object} event + */ + _onRequestWillBeSent(event) { + if (!event.redirectResponse) + ++this._inflightRequests; + } + + /** + * @param {!Object} event + */ + _onLoadingFinished(event) { + this._onLoadingCompleted(event); + } + + /** + * @param {!Object} event + */ + _onLoadingFailed(event) { + this._onLoadingCompleted(event); + } + + _onLoadingCompleted(event) { + --this._inflightRequests; + if (Date.now() - this._navigationStartTime < this._minTime) + return; + this._completeNavigation(false); + } + + /** + * @param {boolean} force + */ + _completeNavigation(force) { + if (!this._navigationLoadCallback) + return; + if (this._inflightRequests < 2 || force) { + clearTimeout(this._minimumTimer); + clearTimeout(this._watchdogTimer); + this._navigationLoadCallback(); + this._navigationLoadCallback = null; + } + } +} + +module.exports = Navigator; diff --git a/lib/Page.js b/lib/Page.js index c21cab76..d220bdad 100644 --- a/lib/Page.js +++ b/lib/Page.js @@ -18,6 +18,7 @@ var fs = require('fs'); var EventEmitter = require('events'); var mime = require('mime'); var Request = require('./Request'); +var Navigator = require('./Navigator'); var Dialog = require('./Dialog'); var FrameManager = require('./FrameManager'); @@ -66,6 +67,7 @@ class Page extends EventEmitter { client.on('Network.responseReceived', event => this.emit(Page.Events.ResponseReceived, event.response)); client.on('Network.loadingFailed', event => this.emit(Page.Events.ResourceLoadingFailed, event)); + client.on('Network.requestIntercepted', event => this._onRequestIntercepted(event)); client.on('Runtime.consoleAPICalled', event => this._onConsoleAPI(event)); client.on('Page.javascriptDialogOpening', event => this._onDialog(event)); @@ -260,19 +262,11 @@ class Page extends EventEmitter { /** * @param {string} html + * @param {!Object=} options * @return {!Promise} */ - async navigate(url) { - var loadPromise = new Promise(fulfill => this._client.once('Page.loadEventFired', fulfill)).then(() => true); - var interstitialPromise = new Promise(fulfill => this._client.once('Security.certificateError', fulfill)).then(() => false); - var referrer = this._extraHeaders.referer; - // Await for the command to throw exception in case of illegal arguments. - try { - await this._client.send('Page.navigate', {url, referrer}); - } catch (e) { - return false; - } - return await Promise.race([loadPromise, interstitialPromise]); + navigate(url, options) { + return new Navigator(this._client, options).navigate(url, this._extraHeaders.referer); } /**