core(fr): colocate PerformanceObserver installation with wait logic (#12365)

This commit is contained in:
Patrick Hulce 2021-04-14 16:00:30 -05:00 коммит произвёл GitHub
Родитель a69595ddfa
Коммит 21abce81e7
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
6 изменённых файлов: 70 добавлений и 71 удалений

Просмотреть файл

@ -899,15 +899,6 @@ class Driver {
`);
}
/**
* Install a performance observer that watches longtask timestamps for waitForCPUIdle.
* @return {Promise<void>}
*/
async registerPerformanceObserver() {
const scriptStr = `(${pageFunctions.registerPerformanceObserverInPageString})()`;
await this.evaluateScriptOnNewDocument(scriptStr);
}
/**
* Use a RequestIdleCallback shim for tests run with simulated throttling, so that the deadline can be used without
* a penalty

Просмотреть файл

@ -5,10 +5,11 @@
*/
'use strict';
/* global window */
const log = require('lighthouse-logger');
const LHError = require('../../lib/lh-error.js');
const ExecutionContext = require('./execution-context.js');
const pageFunctions = require('../../lib/page-functions.js');
/** @typedef {import('./network-monitor.js')} NetworkMonitor */
/** @typedef {import('./network-monitor.js').NetworkMonitorEvent} NetworkMonitorEvent */
@ -221,7 +222,7 @@ function waitForCPUIdle(session, waitForCPUQuiet) {
async function checkForQuiet(executionContext, resolve) {
if (canceled) return;
const timeSinceLongTask =
await executionContext.evaluate(pageFunctions.checkTimeSinceLastLongTask, {args: []});
await executionContext.evaluate(checkTimeSinceLastLongTaskInPage, {args: []});
if (canceled) return;
if (typeof timeSinceLongTask === 'number') {
@ -241,9 +242,12 @@ function waitForCPUIdle(session, waitForCPUQuiet) {
throw new Error('waitForCPUIdle.cancel() called before it was defined');
};
const executionContext = new ExecutionContext(session);
/** @type {Promise<void>} */
const promise = new Promise((resolve, reject) => {
checkForQuiet(new ExecutionContext(session), resolve).catch(reject);
executionContext.evaluate(registerPerformanceObserverInPage, {args: []})
.then(() => checkForQuiet(executionContext, resolve))
.catch(reject);
cancel = () => {
if (canceled) return;
canceled = true;
@ -258,6 +262,69 @@ function waitForCPUIdle(session, waitForCPUQuiet) {
};
}
/* c8 ignore start */
/**
* This function is executed in the page itself when the document is first loaded.
*
* Used by _waitForCPUIdle and executed in the context of the page, updates the ____lastLongTask
* property on window to the end time of the last long task.
*/
function registerPerformanceObserverInPage() {
// Do not re-register if we've already run this script.
if (window.____lastLongTask !== undefined) return;
window.____lastLongTask = window.__perfNow();
const observer = new window.PerformanceObserver(entryList => {
const entries = entryList.getEntries();
for (const entry of entries) {
if (entry.entryType === 'longtask') {
const taskEnd = entry.startTime + entry.duration;
window.____lastLongTask = Math.max(window.____lastLongTask || 0, taskEnd);
}
}
});
observer.observe({entryTypes: ['longtask'], buffered: true});
}
/**
* This function is executed in the page itself.
*
* Used by _waitForCPUIdle and executed in the context of the page, returns time since last long task.
* @return {Promise<number>}
*/
function checkTimeSinceLastLongTaskInPage() {
// This function attempts to return the time since the last long task occurred.
// `PerformanceObserver`s don't always immediately fire though, so we check twice with some time in
// between to make sure nothing has happened very recently.
// Chrome 88 introduced heavy throttling of timers which means our `setTimeout` will be executed
// at some point farish (several hundred ms) into the future and the time at which it executes isn't
// a reliable indicator of long task existence, instead we check if any information has changed.
// See https://developer.chrome.com/blog/timer-throttling-in-chrome-88/
return new window.__nativePromise(resolve => {
const firstAttemptTs = window.__perfNow();
const firstAttemptLastLongTaskTs = window.____lastLongTask || 0;
setTimeout(() => {
// We can't be sure a long task hasn't occurred since our first attempt, but if the `____lastLongTask`
// value is the same (i.e. the perf observer didn't have any new information), we can be pretty
// confident that the long task info was accurate *at the time of our first attempt*.
const secondAttemptLastLongTaskTs = window.____lastLongTask || 0;
const timeSinceLongTask =
firstAttemptLastLongTaskTs === secondAttemptLastLongTaskTs
? // The time of the last long task hasn't changed, the information from our first attempt is accurate.
firstAttemptTs - firstAttemptLastLongTaskTs
: // The time of the last long task *did* change, we can't really trust the information we have.
0;
resolve(timeSinceLongTask);
}, 150);
});
}
/* c8 ignore stop */
/**
* Return a promise that resolves `pauseAfterLoadMs` after the load event
* fires and a method to cancel internal listeners and timeout.
@ -294,7 +361,6 @@ function waitForLoadEvent(session, pauseAfterLoadMs) {
};
}
/**
* Returns whether the page appears to be hung.
* @param {LH.Gatherer.FRProtocolSession} session

Просмотреть файл

@ -133,7 +133,6 @@ class GatherRunner {
await driver.enableRuntimeEvents();
await driver.enableAsyncStacks();
await driver.cacheNatives();
await driver.registerPerformanceObserver();
await driver.dismissJavaScriptDialogs();
await driver.registerRequestIdleCallbackWrap(options.settings);
if (resetStorage) {

Просмотреть файл

@ -47,57 +47,6 @@ function wrapRuntimeEvalErrorInBrowser(err) {
};
}
/**
* Used by _waitForCPUIdle and executed in the context of the page, updates the ____lastLongTask
* property on window to the end time of the last long task.
*/
function registerPerformanceObserverInPage() {
window.____lastLongTask = window.__perfNow();
const observer = new window.PerformanceObserver(entryList => {
const entries = entryList.getEntries();
for (const entry of entries) {
if (entry.entryType === 'longtask') {
const taskEnd = entry.startTime + entry.duration;
window.____lastLongTask = Math.max(window.____lastLongTask || 0, taskEnd);
}
}
});
observer.observe({entryTypes: ['longtask']});
}
/**
* Used by _waitForCPUIdle and executed in the context of the page, returns time since last long task.
* @return {Promise<number>}
*/
function checkTimeSinceLastLongTask() {
// This function attempts to return the time since the last long task occurred.
// `PerformanceObserver`s don't always immediately fire though, so we check twice with some time in
// between to make sure nothing has happened very recently.
// Chrome 88 introduced heavy throttling of timers which means our `setTimeout` will be executed
// at some point farish (several hundred ms) into the future and the time at which it executes isn't
// a reliable indicator of long task existence, instead we check if any information has changed.
// See https://developer.chrome.com/blog/timer-throttling-in-chrome-88/
return new window.__nativePromise(resolve => {
const firstAttemptTs = window.__perfNow();
const firstAttemptLastLongTaskTs = window.____lastLongTask || 0;
setTimeout(() => {
// We can't be sure a long task hasn't occurred since our first attempt, but if the `____lastLongTask`
// value is the same (i.e. the perf observer didn't have any new information), we can be pretty
// confident that the long task info was accurate *at the time of our first attempt*.
const secondAttemptLastLongTaskTs = window.____lastLongTask || 0;
const timeSinceLongTask = firstAttemptLastLongTaskTs === secondAttemptLastLongTaskTs ?
// The time of the last long task hasn't changed, the information from our first attempt is accurate.
firstAttemptTs - firstAttemptLastLongTaskTs :
// The time of the last long task *did* change, we can't really trust the information we have.
0;
resolve(timeSinceLongTask);
}, 150);
});
}
/**
* @template {string} T
* @param {T} selector Optional simple CSS selector to filter nodes on.
@ -577,8 +526,6 @@ const getNodeDetailsString = `function getNodeDetails(element) {
module.exports = {
wrapRuntimeEvalErrorInBrowserString: wrapRuntimeEvalErrorInBrowser.toString(),
wrapRuntimeEvalErrorInBrowser,
registerPerformanceObserverInPageString: registerPerformanceObserverInPage.toString(),
checkTimeSinceLastLongTask,
getElementsInDocument,
getElementsInDocumentString: getElementsInDocument.toString(),
getOuterHTMLSnippetString: getOuterHTMLSnippet.toString(),

Просмотреть файл

@ -84,9 +84,6 @@ function makeFakeDriver({protocolGetVersionResponse}) {
getScrollPosition() {
return Promise.resolve(scrollPosition);
},
registerPerformanceObserver() {
return Promise.resolve();
},
beginTrace() {
return Promise.resolve();
},

Просмотреть файл

@ -341,7 +341,6 @@ describe('GatherRunner', function() {
enableAsyncStacks: asyncFunc,
cacheNatives: asyncFunc,
gotoURL: asyncFunc,
registerPerformanceObserver: asyncFunc,
registerRequestIdleCallbackWrap: asyncFunc,
cleanBrowserCaches: createCheck('calledCleanBrowserCaches'),
clearDataForOrigin: createCheck('calledClearStorage'),