From af4be6682f32f4f555eab117d0e8d7d487235de0 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Tue, 30 May 2023 14:59:37 -0700 Subject: [PATCH 1/6] wip --- build/build-cdt-lib.js | 187 +++++++--- core/computed/js-bundles.js | 2 +- .../metrics/cumulative-layout-shift.js | 36 +- core/gather/gatherers/source-maps.js | 5 +- core/lib/cdt/Platform.js | 114 ++++++ core/lib/cdt/SDK.js | 6 +- core/lib/cdt/generated/SourceMap.js | 152 +++----- .../cdt/generated/models/trace/Processor.js | 215 +++++++++++ .../trace/handlers/LayoutShiftsHandler.js | 344 ++++++++++++++++++ .../models/trace/handlers/MetaHandler.js | 277 ++++++++++++++ .../models/trace/handlers/handlers.js | 9 + .../generated/models/trace/handlers/types.js | 194 ++++++++++ .../generated/models/trace/helpers/Timing.js | 110 ++++++ .../generated/models/trace/helpers/Trace.js | 91 +++++ .../generated/models/trace/helpers/helpers.js | 12 + .../generated/models/trace/types/Timing.js | 42 +++ .../models/trace/types/TraceEvents.js | 326 +++++++++++++++++ .../cdt/generated/models/trace/types/types.js | 12 + 18 files changed, 1965 insertions(+), 169 deletions(-) create mode 100644 core/lib/cdt/generated/models/trace/Processor.js create mode 100644 core/lib/cdt/generated/models/trace/handlers/LayoutShiftsHandler.js create mode 100644 core/lib/cdt/generated/models/trace/handlers/MetaHandler.js create mode 100644 core/lib/cdt/generated/models/trace/handlers/handlers.js create mode 100644 core/lib/cdt/generated/models/trace/handlers/types.js create mode 100644 core/lib/cdt/generated/models/trace/helpers/Timing.js create mode 100644 core/lib/cdt/generated/models/trace/helpers/Trace.js create mode 100644 core/lib/cdt/generated/models/trace/helpers/helpers.js create mode 100644 core/lib/cdt/generated/models/trace/types/Timing.js create mode 100644 core/lib/cdt/generated/models/trace/types/TraceEvents.js create mode 100644 core/lib/cdt/generated/models/trace/types/types.js diff --git a/build/build-cdt-lib.js b/build/build-cdt-lib.js index cac2eaf2fdc3..95575dfa8ef3 100644 --- a/build/build-cdt-lib.js +++ b/build/build-cdt-lib.js @@ -28,50 +28,22 @@ const outDir = `${LH_ROOT}/core/lib/cdt/generated`; /** @type {Modification[]} */ const modifications = [ { - input: 'node_modules/chrome-devtools-frontend/front_end/core/sdk/SourceMap.ts', + input: '/Users/cjamcl/src/devtools/devtools-frontend/front_end/core/sdk/SourceMap.ts', output: `${outDir}/SourceMap.js`, template: [ 'const Common = require(\'../Common.js\');', 'const Platform = require(\'../Platform.js\');', '%sourceFilePrinted%', - 'module.exports = TextSourceMap;', + 'module.exports = SourceMap;', + 'SourceMap.parseSourceMap = parseSourceMap;', ].join('\n'), rawCodeToReplace: { - /* Original: - - let url = Common.ParsedURL.ParsedURL.completeURL(this.#baseURL, href) || href; - const source = sourceMap.sourcesContent && sourceMap.sourcesContent[i]; - if (url === this.#compiledURLInternal && source) { - url = Common.ParsedURL.ParsedURL.concatenate(url, '? [sm]'); - } - if (this.#sourceInfos.has(url)) { - continue; - } - this.#sourceInfos.set(url, new TextSourceMap.SourceInfo(source || null, null)); - sourcesList.push(url); - ---- - If a source file is the same as the compiled url and there is a sourcesContent, - then `entry.sourceURL` (what is returned from .mappings) will have `? [sm]` appended. - This is useful in DevTools - to show that a sources panel tab not a real network resource - - but for us it is not wanted. The sizing function uses `entry.sourceURL` to index the byte - counts, and is further used in the details to specify a file within a source map. - */ - [`url = Common.ParsedURL.ParsedURL.concatenate(url, '? [sm]');`]: '', // Use normal console.warn so we don't need to import CDT's logger. 'Common.Console.Console.instance().warn': 'console.warn', - // Similar to the reason for removing `url += Common.UIString('? [sm]')`. // The entries in `.mappings` should not have their url property modified. + // The sizing function uses `entry.sourceURL` to index the byte + // counts, and is further used in the details to specify a file within a source map. 'Common.ParsedURL.ParsedURL.completeURL(this.#baseURL, href)': `''`, - // Replace i18n function with a very simple templating function. - 'i18n.i18n.getLocalizedString.bind(undefined, str_)': ( - /** @param {string} template @param {object} vars */ - function(template, vars) { - let result = template; - for (const [key, value] of Object.entries(vars)) { - result = result.replace(new RegExp('{' + key + '}'), value); - } - return result; - }).toString(), // Add some types. // eslint-disable-next-line max-len 'mappings(): SourceMapEntry[] {': '/** @return {Array<{lineNumber: number, columnNumber: number, sourceURL?: string, sourceLineNumber: number, sourceColumnNumber: number, name?: string, lastColumnNumber?: number}>} */\nmappings(): SourceMapEntry[] {', @@ -79,24 +51,18 @@ const modifications = [ classesToRemove: [], methodsToRemove: [ // Not needed. + 'compatibleForURL', 'load', - // Not needed. - 'sourceContentProvider', + 'reverseMapTextRanges', ], variablesToRemove: [ 'Common', - 'CompilerSourceMappingContentProvider_js_1', - 'i18n', - 'i18nString', - 'PageResourceLoader_js_1', 'Platform', - 'str_', 'TextUtils', - 'UIStrings', ], }, { - input: 'node_modules/chrome-devtools-frontend/front_end/core/common/ParsedURL.ts', + input: '/Users/cjamcl/src/devtools/devtools-frontend/front_end/core/common/ParsedURL.ts', output: `${outDir}/ParsedURL.js`, template: '%sourceFilePrinted%', rawCodeToReplace: {}, @@ -147,6 +113,124 @@ const modifications = [ 'Platform', ], }, + { + input: '/Users/cjamcl/src/devtools/devtools-frontend/front_end/models/trace/types/types.ts', + output: `${outDir}/models/trace/types/types.js`, + template: '%sourceFilePrinted%', + rawCodeToReplace: {}, + classesToRemove: [], + methodsToRemove: [], + variablesToRemove: [], + }, + { + input: '/Users/cjamcl/src/devtools/devtools-frontend/front_end/models/trace/types/TraceEvents.ts', + output: `${outDir}/models/trace/types/TraceEvents.js`, + template: '%sourceFilePrinted%', + rawCodeToReplace: {}, + classesToRemove: [], + methodsToRemove: [], + variablesToRemove: [], + }, + { + input: '/Users/cjamcl/src/devtools/devtools-frontend/front_end/models/trace/types/Timing.ts', + output: `${outDir}/models/trace/types/Timing.js`, + template: '%sourceFilePrinted%', + rawCodeToReplace: {}, + classesToRemove: [], + methodsToRemove: [], + variablesToRemove: [], + }, + { + input: '/Users/cjamcl/src/devtools/devtools-frontend/front_end/models/trace/helpers/helpers.ts', + output: `${outDir}/models/trace/helpers/helpers.js`, + template: '%sourceFilePrinted%', + rawCodeToReplace: {}, + classesToRemove: [], + methodsToRemove: [], + variablesToRemove: [], + }, + { + input: '/Users/cjamcl/src/devtools/devtools-frontend/front_end/models/trace/helpers/Timing.ts', + output: `${outDir}/models/trace/helpers/Timing.js`, + template: [ + 'const Platform = require(\'../../../../Platform.js\');', + '%sourceFilePrinted%', + ].join('\n'), + rawCodeToReplace: { + 'navigator.language': `'en'`, + }, + classesToRemove: [], + methodsToRemove: [], + variablesToRemove: [ + 'Platform', + ], + }, + { + input: '/Users/cjamcl/src/devtools/devtools-frontend/front_end/models/trace/helpers/Trace.ts', + output: `${outDir}/models/trace/helpers/Trace.js`, + template: [ + 'const Platform = require(\'../../../../Platform.js\');', + 'const Common = require(\'../../../../Common.js\');', + '%sourceFilePrinted%', + ].join('\n'), + rawCodeToReplace: {}, + classesToRemove: [], + methodsToRemove: [], + variablesToRemove: [ + 'Platform', + 'Common', + ], + }, + { + input: '/Users/cjamcl/src/devtools/devtools-frontend/front_end/models/trace/handlers/types.ts', + output: `${outDir}/models/trace/handlers/types.js`, + template: '%sourceFilePrinted%', + rawCodeToReplace: {}, + classesToRemove: [], + methodsToRemove: [], + variablesToRemove: [], + }, + { + input: '/Users/cjamcl/src/devtools/devtools-frontend/front_end/models/trace/handlers/MetaHandler.ts', + output: `${outDir}/models/trace/handlers/MetaHandler.js`, + template: [ + 'const Platform = require(\'../../../../Platform.js\');', + '%sourceFilePrinted%', + ].join('\n'), + rawCodeToReplace: { + 'new DOMRect(viewportX, viewportY, viewportWidth, viewportHeight)': 'null', + }, + classesToRemove: [], + methodsToRemove: [ + // 'findNextScreenshotSource', + ], + variablesToRemove: [ + 'Platform', + ], + }, + { + input: '/Users/cjamcl/src/devtools/devtools-frontend/front_end/models/trace/handlers/LayoutShiftsHandler.ts', + output: `${outDir}/models/trace/handlers/LayoutShiftsHandler.js`, + template: [ + 'const Platform = require(\'../../../../Platform.js\');', + '%sourceFilePrinted%', + ].join('\n'), + rawCodeToReplace: { + 'findNextScreenshotSource(event.ts)': 'null', + [`['Screenshots', 'Meta']`]: `['Meta']`, + '!event.args.data?.had_recent_input': 'true', + }, + classesToRemove: [], + methodsToRemove: [ + 'findNextScreenshotSource', + 'stateForLayoutShiftScore', + ], + variablesToRemove: [ + 'Platform', + 'PageLoadMetricsHandler_js_1', + 'ScreenshotsHandler_js_1', + ], + }, ]; /** @@ -233,15 +317,30 @@ function doModification(modification) { sourceFilePrinted += printer.printNode(ts.EmitHint.Unspecified, node, sourceFile) + '\n'; }); + const content = modification.template.replace('%sourceFilePrinted%', () => sourceFilePrinted); + writeGeneratedFile(modification.output, content); +} + +/** + * @param {string} outputPath + * @param {string} contents + */ +function writeGeneratedFile(outputPath, contents) { const modifiedFile = [ '// @ts-nocheck\n', '// generated by yarn build-cdt-lib\n', '/* eslint-disable */\n', '"use strict";\n', - modification.template.replace('%sourceFilePrinted%', () => sourceFilePrinted), + contents, ].join(''); - fs.writeFileSync(modification.output, modifiedFile); + fs.writeFileSync(outputPath, modifiedFile); } modifications.forEach(doModification); +writeGeneratedFile(`${outDir}/models/trace/handlers/handlers.js`, ` + module.exports.ModelHandlers = { + Meta: require('./MetaHandler.js'), + LayoutShiftsHandler: require('./LayoutShiftsHandler.js'), + }; +`); diff --git a/core/computed/js-bundles.js b/core/computed/js-bundles.js index e8cac67c03e9..9ce5be9c49e7 100644 --- a/core/computed/js-bundles.js +++ b/core/computed/js-bundles.js @@ -99,7 +99,7 @@ class JSBundles { const compiledUrl = SourceMap.scriptUrl || 'compiled.js'; const mapUrl = SourceMap.sourceMapUrl || 'compiled.js.map'; - const map = new SDK.TextSourceMap(compiledUrl, mapUrl, rawMap); + const map = new SDK.SourceMap(compiledUrl, mapUrl, rawMap); const sizes = computeGeneratedFileSizes(map, script.length || 0, script.content || ''); diff --git a/core/computed/metrics/cumulative-layout-shift.js b/core/computed/metrics/cumulative-layout-shift.js index 122417a39e61..01b89e4b8e0c 100644 --- a/core/computed/metrics/cumulative-layout-shift.js +++ b/core/computed/metrics/cumulative-layout-shift.js @@ -4,6 +4,9 @@ * 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. */ +import log from 'lighthouse-logger'; + +import SDK from '../../lib/cdt/SDK.js'; import {makeComputedArtifact} from '../computed-artifact.js'; import {ProcessedTrace} from '../processed-trace.js'; @@ -101,35 +104,30 @@ class CumulativeLayoutShift { return maxScore; } - /** - * Sum all layout shift events from the entire trace. - * @param {Array} layoutShiftEvents - * @return {number} - */ - static calculateTotalCumulativeLayoutShift(layoutShiftEvents) { - return layoutShiftEvents.reduce((sum, e) => sum += e.weightedScore, 0); - } - /** * @param {LH.Trace} trace * @param {LH.Artifacts.ComputedContext} context - * @return {Promise<{cumulativeLayoutShift: number, cumulativeLayoutShiftMainFrame: number, totalCumulativeLayoutShift: number}>} + * @return {Promise<{cumulativeLayoutShift: number}>} */ static async compute_(trace, context) { - const processedTrace = await ProcessedTrace.request(trace, context); + try { + const processor = new SDK.TraceProcessor.TraceProcessor({ + LayoutShifts: SDK.TraceHandlers.LayoutShiftsHandler, + }); + await processor.parse(trace.traceEvents); + const data = processor.data; + return { + cumulativeLayoutShift: data.LayoutShifts.sessionMaxScore, + }; + } catch (e) { + log.error('Error running SDK.TraceProcessor', e); + } + const processedTrace = await ProcessedTrace.request(trace, context); const allFrameShiftEvents = CumulativeLayoutShift.getLayoutShiftEvents(processedTrace); - const mainFrameShiftEvents = allFrameShiftEvents.filter(e => e.isMainFrame); - - // The original Cumulative Layout Shift metric, the sum of all main-frame shift events. - const totalCumulativeLayoutShift = - CumulativeLayoutShift.calculateTotalCumulativeLayoutShift(mainFrameShiftEvents); - return { cumulativeLayoutShift: CumulativeLayoutShift.calculate(allFrameShiftEvents), - cumulativeLayoutShiftMainFrame: CumulativeLayoutShift.calculate(mainFrameShiftEvents), - totalCumulativeLayoutShift, }; } } diff --git a/core/gather/gatherers/source-maps.js b/core/gather/gatherers/source-maps.js index 8da6065c3864..2f7880f8c3e9 100644 --- a/core/gather/gatherers/source-maps.js +++ b/core/gather/gatherers/source-maps.js @@ -4,6 +4,7 @@ * 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. */ +import SDK from '../../lib/cdt/SDK.js'; import FRGatherer from '../base-gatherer.js'; /** @@ -32,7 +33,7 @@ class SourceMaps extends FRGatherer { if (response.content === null) { throw new Error(`Failed fetching source map (${response.status})`); } - return JSON.parse(response.content); + return SDK.SourceMap.parseSourceMap(response.content); } /** @@ -41,7 +42,7 @@ class SourceMaps extends FRGatherer { */ parseSourceMapFromDataUrl(sourceMapURL) { const buffer = Buffer.from(sourceMapURL.split(',')[1], 'base64'); - return JSON.parse(buffer.toString()); + return SDK.SourceMap.parseSourceMap(buffer.toString()); } /** diff --git a/core/lib/cdt/Platform.js b/core/lib/cdt/Platform.js index 4ab2306a5e32..7d139f696e83 100644 --- a/core/lib/cdt/Platform.js +++ b/core/lib/cdt/Platform.js @@ -46,10 +46,124 @@ function upperBound(array, needle, comparator, left, right) { return r; } +const NearestSearchStart = { + BEGINNING: 'BEGINNING', + END: 'END', +}; + +/** + * Obtains the first or last item in the array that satisfies the predicate function. + * So, for example, if the array were arr = [2, 4, 6, 8, 10], and you are looking for + * the last item arr[i] such that arr[i] < 5 you would be returned 1, because + * array[1] is 4, the last item in the array that satisfies the + * predicate function. + * + * If instead you were looking for the first item in the same array that satisfies + * arr[i] > 5 you would be returned 2 because array[2] = 6. + * + * Please note: this presupposes that the array is already ordered. + * + * @template {T} + * @param {T[]} arr + * @param {(arrayItem: T) => boolean} + * @param {string} searchStart + * @return {number|null} + */ +function nearestIndex(arr, predicate, searchStart) { + const searchFromEnd = searchStart === NearestSearchStart.END; + if (arr.length === 0) { + return null; + } + + let left = 0; + let right = arr.length - 1; + let pivot = 0; + let matchesPredicate = false; + let moveToTheRight = false; + let middle = 0; + do { + middle = left + (right - left) / 2; + pivot = searchFromEnd ? Math.ceil(middle) : Math.floor(middle); + matchesPredicate = predicate(arr[pivot]); + moveToTheRight = matchesPredicate === searchFromEnd; + if (moveToTheRight) { + left = Math.min(right, pivot + (left === pivot ? 1 : 0)); + } else { + right = Math.max(left, pivot + (right === pivot ? -1 : 0)); + } + } while (right !== left); + + // Special-case: the indexed item doesn't pass the predicate. This + // occurs when none of the items in the array are a match for the + // predicate. + if (!predicate(arr[left])) { + return null; + } + return left; +} + +/** + * Obtains the first item in the array that satisfies the predicate function. + * So, for example, if the array was arr = [2, 4, 6, 8, 10], and you are looking for + * the first item arr[i] such that arr[i] > 5 you would be returned 2, because + * array[2] is 6, the first item in the array that satisfies the + * predicate function. + * + * Please note: this presupposes that the array is already ordered. + * @template {T} + * @param {T[]} arr + * @param {(arrayItem: T) => boolean} + * @return {number|null} + */ +function nearestIndexFromBeginning(arr, predicate) { + return nearestIndex(arr, predicate, NearestSearchStart.BEGINNING); +} + +/** + * Obtains the last item in the array that satisfies the predicate function. + * So, for example, if the array was arr = [2, 4, 6, 8, 10], and you are looking for + * the last item arr[i] such that arr[i] < 5 you would be returned 1, because + * arr[1] is 4, the last item in the array that satisfies the + * predicate function. + * + * Please note: this presupposes that the array is already ordered. + * @template {T} + * @param {T[]} arr + * @param {(arrayItem: T) => boolean} + * @return {number|null} + */ +function nearestIndexFromEnd(arr, predicate) { + return nearestIndex(arr, predicate, NearestSearchStart.END); +} + +/** + * Gets value for key, assigning a default if value is falsy. + * @template {K} + * @template {V} + * @param {Map} map + * @param {K} key + * @param {(key?: K) => V} defaultValueFactory + * @return {V} + */ +function getWithDefault(map, key, defaultValueFactory) { + let value = map.get(key); + if (!value) { + value = defaultValueFactory(key); + map.set(key, value); + } + + return value; +} + module.exports = { ArrayUtilities: { lowerBound, upperBound, + nearestIndexFromBeginning, + nearestIndexFromEnd, + }, + MapUtilities: { + getWithDefault, }, DevToolsPath: { EmptyUrlString: '', diff --git a/core/lib/cdt/SDK.js b/core/lib/cdt/SDK.js index a613d485dcf0..4a277c364d04 100644 --- a/core/lib/cdt/SDK.js +++ b/core/lib/cdt/SDK.js @@ -5,12 +5,14 @@ */ const SDK = { - TextSourceMap: require('./generated/SourceMap.js'), + SourceMap: require('./generated/SourceMap.js'), + TraceProcessor: require('./generated/models/trace/Processor.js'), + TraceHandlers: require('./generated/models/trace/handlers/handlers.js').ModelHandlers, }; // Add `lastColumnNumber` to mappings. This will eventually be added to CDT. // @ts-expect-error -SDK.TextSourceMap.prototype.computeLastGeneratedColumns = function() { +SDK.SourceMap.prototype.computeLastGeneratedColumns = function() { const mappings = this.mappings(); if (mappings.length && mappings[0].lastColumnNumber !== undefined) return; diff --git a/core/lib/cdt/generated/SourceMap.js b/core/lib/cdt/generated/SourceMap.js index 29df45d97b07..ef966850ef99 100644 --- a/core/lib/cdt/generated/SourceMap.js +++ b/core/lib/cdt/generated/SourceMap.js @@ -8,7 +8,7 @@ const Platform = require('../Platform.js'); // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. Object.defineProperty(exports, "__esModule", { value: true }); -exports.TextSourceMap = exports.SourceMapEntry = exports.Offset = exports.Section = exports.SourceMapV3 = void 0; +exports.SourceMap = exports.SourceMapEntry = exports.parseSourceMap = void 0; /* * Copyright (C) 2012 Google Inc. All rights reserved. * @@ -41,42 +41,24 @@ exports.TextSourceMap = exports.SourceMapEntry = exports.Offset = exports.Sectio ; ; ; -; -; -; -; -; -; -class SourceMapV3 { - version; - file; - sources; - sections; - mappings; - sourceRoot; - names; - sourcesContent; - // eslint-disable-next-line @typescript-eslint/naming-convention - x_google_ignoreList; - constructor() { +/** + * Parses the {@link content} as JSON, ignoring BOM markers in the beginning, and + * also handling the CORB bypass prefix correctly. + * + * @param content the string representation of a sourcemap. + * @returns the {@link SourceMapV3} representation of the {@link content}. + */ +function parseSourceMap(content) { + if (content.startsWith(')]}')) { + content = content.substring(content.indexOf('\n')); } -} -exports.SourceMapV3 = SourceMapV3; -class Section { - map; - offset; - url; - constructor() { + if (content.charCodeAt(0) === 0xFEFF) { + // Strip BOM at the beginning before parsing the JSON. + content = content.slice(1); } + return JSON.parse(content); } -exports.Section = Section; -class Offset { - line; - column; - constructor() { - } -} -exports.Offset = Offset; +exports.parseSourceMap = parseSourceMap; class SourceMapEntry { lineNumber; columnNumber; @@ -106,8 +88,7 @@ for (let i = 0; i < base64Digits.length; ++i) { base64Map.set(base64Digits.charAt(i), i); } const sourceMapToSourceList = new WeakMap(); -class TextSourceMap { - #initiator; +class SourceMap { #json; #compiledURLInternal; #sourceMappingURL; @@ -118,17 +99,15 @@ class TextSourceMap { * Implements Source Map V3 model. See https://github.com/google/closure-compiler/wiki/Source-Maps * for format description. */ - constructor(compiledURL, sourceMappingURL, payload, initiator) { - this.#initiator = initiator; + constructor(compiledURL, sourceMappingURL, payload) { this.#json = payload; this.#compiledURLInternal = compiledURL; this.#sourceMappingURL = sourceMappingURL; this.#baseURL = (sourceMappingURL.startsWith('data:') ? compiledURL : sourceMappingURL); this.#mappingsInternal = null; this.#sourceInfos = new Map(); - if (this.#json.sections) { - const sectionWithURL = Boolean(this.#json.sections.find(section => Boolean(section.url))); - if (sectionWithURL) { + if ('sections' in this.#json) { + if (this.#json.sections.find(section => 'url' in section)) { console.warn(`SourceMap "${sourceMappingURL}" contains unsupported "URL" field in one of its sections.`); } } @@ -266,6 +245,8 @@ class TextSourceMap { if (this.#mappingsInternal === null) { this.#mappingsInternal = []; this.eachSection(this.parseMap.bind(this)); + // As per spec, mappings are not necessarily sorted. + this.mappings().sort(SourceMapEntry.compare); this.#computeReverseMappings(this.#mappingsInternal); this.#json = null; } @@ -303,42 +284,43 @@ class TextSourceMap { if (!this.#json) { return; } - if (!this.#json.sections) { - callback(this.#json, 0, 0); - return; + if ('sections' in this.#json) { + for (const section of this.#json.sections) { + if ('map' in section) { + callback(section.map, section.offset.line, section.offset.column); + } + } } - for (const section of this.#json.sections) { - callback(section.map, section.offset.line, section.offset.column); + else { + callback(this.#json, 0, 0); } } parseSources(sourceMap) { const sourcesList = []; - const sourceRoot = sourceMap.sourceRoot || Platform.DevToolsPath.EmptyUrlString; + const sourceRoot = sourceMap.sourceRoot ?? ''; const ignoreList = new Set(sourceMap.x_google_ignoreList); for (let i = 0; i < sourceMap.sources.length; ++i) { let href = sourceMap.sources[i]; // The source map v3 proposal says to prepend the sourceRoot to the source URL // and if the resulting URL is not absolute, then resolve the source URL against - // the source map URL. Appending the sourceRoot (if one exists) is not likely to + // the source map URL. Prepending the sourceRoot (if one exists) is not likely to // be meaningful or useful if the source URL is already absolute though. In this // case, use the source URL as is without prepending the sourceRoot. if (Common.ParsedURL.ParsedURL.isRelativeURL(href)) { if (sourceRoot && !sourceRoot.endsWith('/') && href && !href.startsWith('/')) { - href = Common.ParsedURL.ParsedURL.concatenate(sourceRoot, '/', href); + href = sourceRoot.concat('/', href); } else { - href = Common.ParsedURL.ParsedURL.concatenate(sourceRoot, href); + href = sourceRoot.concat(href); } } - let url = '' || href; + const url = '' || href; const source = sourceMap.sourcesContent && sourceMap.sourcesContent[i]; - if (url === this.#compiledURLInternal && source) { - } sourcesList.push(url); if (!this.#sourceInfos.has(url)) { const content = source ?? null; const ignoreListHint = ignoreList.has(i); - this.#sourceInfos.set(url, new TextSourceMap.SourceInfo(content, ignoreListHint)); + this.#sourceInfos.set(url, { content, ignoreListHint, reverseMappings: null }); } } sourceMapToSourceList.set(sourceMap, sourcesList); @@ -355,8 +337,8 @@ class TextSourceMap { // only reach this point when we are certain // we have the list available. const sources = sourceMapToSourceList.get(map); - const names = map.names || []; - const stringCharIterator = new TextSourceMap.StringCharIterator(map.mappings); + const names = map.names ?? []; + const stringCharIterator = new SourceMap.StringCharIterator(map.mappings); let sourceURL = sources && sources[sourceIndex]; while (true) { if (stringCharIterator.peek() === ',') { @@ -393,8 +375,6 @@ class TextSourceMap { nameIndex += this.decodeVLQ(stringCharIterator); this.mappings().push(new SourceMapEntry(lineNumber, columnNumber, sourceURL, sourceLineNumber, sourceColumnNumber, names[nameIndex])); } - // As per spec, mappings are not necessarily sorted. - this.mappings().sort(SourceMapEntry.compare); } isSeparator(char) { return char === ',' || char === ';'; @@ -403,38 +383,17 @@ class TextSourceMap { // Read unsigned value. let result = 0; let shift = 0; - let digit = TextSourceMap._VLQ_CONTINUATION_MASK; - while (digit & TextSourceMap._VLQ_CONTINUATION_MASK) { + let digit = SourceMap._VLQ_CONTINUATION_MASK; + while (digit & SourceMap._VLQ_CONTINUATION_MASK) { digit = base64Map.get(stringCharIterator.next()) || 0; - result += (digit & TextSourceMap._VLQ_BASE_MASK) << shift; - shift += TextSourceMap._VLQ_BASE_SHIFT; + result += (digit & SourceMap._VLQ_BASE_MASK) << shift; + shift += SourceMap._VLQ_BASE_SHIFT; } // Fix the sign. const negative = result & 1; result >>= 1; return negative ? -result : result; } - reverseMapTextRange(url, textRange) { - function comparator(position, mappingIndex) { - if (position.lineNumber !== mappings[mappingIndex].sourceLineNumber) { - return position.lineNumber - mappings[mappingIndex].sourceLineNumber; - } - return position.columnNumber - mappings[mappingIndex].sourceColumnNumber; - } - const reverseMappings = this.reversedMappings(url); - const mappings = this.mappings(); - if (!reverseMappings.length) { - return null; - } - const startIndex = Platform.ArrayUtilities.lowerBound(reverseMappings, { lineNumber: textRange.startLine, columnNumber: textRange.startColumn }, comparator); - const endIndex = Platform.ArrayUtilities.upperBound(reverseMappings, { lineNumber: textRange.endLine, columnNumber: textRange.endColumn }, comparator); - if (endIndex >= reverseMappings.length) { - return null; - } - const startMapping = mappings[reverseMappings[startIndex]]; - const endMapping = mappings[reverseMappings[endIndex]]; - return new TextUtils.TextRange.TextRange(startMapping.lineNumber, startMapping.columnNumber, endMapping.lineNumber, endMapping.columnNumber); - } mapsOrigin() { const mappings = this.mappings(); if (mappings.length > 0) { @@ -482,17 +441,17 @@ class TextSourceMap { return ranges; } } -exports.TextSourceMap = TextSourceMap; -(function (TextSourceMap) { +exports.SourceMap = SourceMap; +(function (SourceMap) { // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration // eslint-disable-next-line @typescript-eslint/naming-convention - TextSourceMap._VLQ_BASE_SHIFT = 5; + SourceMap._VLQ_BASE_SHIFT = 5; // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration // eslint-disable-next-line @typescript-eslint/naming-convention - TextSourceMap._VLQ_BASE_MASK = (1 << 5) - 1; + SourceMap._VLQ_BASE_MASK = (1 << 5) - 1; // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration // eslint-disable-next-line @typescript-eslint/naming-convention - TextSourceMap._VLQ_CONTINUATION_MASK = 1 << 5; + SourceMap._VLQ_CONTINUATION_MASK = 1 << 5; class StringCharIterator { string; position; @@ -510,18 +469,9 @@ exports.TextSourceMap = TextSourceMap; return this.position < this.string.length; } } - TextSourceMap.StringCharIterator = StringCharIterator; - class SourceInfo { - content; - ignoreListHint; - reverseMappings = null; - constructor(content, ignoreListHint) { - this.content = content; - this.ignoreListHint = ignoreListHint; - } - } - TextSourceMap.SourceInfo = SourceInfo; -})(TextSourceMap = exports.TextSourceMap || (exports.TextSourceMap = {})); + SourceMap.StringCharIterator = StringCharIterator; +})(SourceMap = exports.SourceMap || (exports.SourceMap = {})); -module.exports = TextSourceMap; \ No newline at end of file +module.exports = SourceMap; +SourceMap.parseSourceMap = parseSourceMap; \ No newline at end of file diff --git a/core/lib/cdt/generated/models/trace/Processor.js b/core/lib/cdt/generated/models/trace/Processor.js new file mode 100644 index 000000000000..2da4b91dbd5e --- /dev/null +++ b/core/lib/cdt/generated/models/trace/Processor.js @@ -0,0 +1,215 @@ +// @ts-nocheck +// generated by yarn build-cdt-lib +/* eslint-disable */ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.sortHandlers = exports.TraceProcessor = exports.TraceParseProgressEvent = void 0; +const Handlers = require("./handlers/handlers.js"); +var Status; +(function (Status) { + Status["IDLE"] = "IDLE"; + Status["PARSING"] = "PARSING"; + Status["FINISHED_PARSING"] = "FINISHED_PARSING"; + Status["ERRORED_WHILE_PARSING"] = "ERRORED_WHILE_PARSING"; +})(Status || (Status = {})); +class TraceParseProgressEvent extends Event { + data; + static eventName = 'traceparseprogress'; + constructor(data, init = { bubbles: true }) { + super(TraceParseProgressEvent.eventName, init); + this.data = data; + } +} +exports.TraceParseProgressEvent = TraceParseProgressEvent; +class TraceProcessor extends EventTarget { + // We force the Meta handler to be enabled, so the TraceHandlers type here is + // the model handlers the user passes in and the Meta handler. + // eslint-disable-next-line @typescript-eslint/naming-convention + #traceHandlers; + #pauseDuration; + #eventsPerChunk; + #status = Status.IDLE; + static createWithAllHandlers() { + return new TraceProcessor(Handlers.ModelHandlers); + } + constructor(traceHandlers, { pauseDuration = 1, eventsPerChunk = 15000 } = {}) { + super(); + this.#verifyHandlers(traceHandlers); + this.#traceHandlers = { + Meta: Handlers.ModelHandlers.Meta, + ...traceHandlers, + }; + this.#pauseDuration = pauseDuration; + this.#eventsPerChunk = eventsPerChunk; + } + /** + * When the user passes in a set of handlers, we want to ensure that we have all + * the required handlers. Handlers can depend on other handlers, so if the user + * passes in FooHandler which depends on BarHandler, they must also pass in + * BarHandler too. This method verifies that all dependencies are met, and + * throws if not. + **/ + #verifyHandlers(providedHandlers) { + // Tiny optimisation: if the amount of provided handlers matches the amount + // of handlers in the Handlers.ModelHandlers object, that means that the + // user has passed in every handler we have. So therefore they cannot have + // missed any, and there is no need to iterate through the handlers and + // check the dependencies. + if (Object.keys(providedHandlers).length === Object.keys(Handlers.ModelHandlers).length) { + return; + } + const requiredHandlerKeys = new Set(); + for (const [handlerName, handler] of Object.entries(providedHandlers)) { + requiredHandlerKeys.add(handlerName); + for (const depName of (handler.deps?.() || [])) { + requiredHandlerKeys.add(depName); + } + } + const providedHandlerKeys = new Set(Object.keys(providedHandlers)); + // We always force the Meta handler to be enabled when creating the + // Processor, so if it is missing from the set the user gave us that is OK, + // as we will have enabled it anyway. + requiredHandlerKeys.delete('Meta'); + for (const requiredKey of requiredHandlerKeys) { + if (!providedHandlerKeys.has(requiredKey)) { + throw new Error(`Required handler ${requiredKey} not provided.`); + } + } + } + reset() { + if (this.#status === Status.PARSING) { + throw new Error('Trace processor can\'t reset while parsing.'); + } + const handlers = Object.values(this.#traceHandlers); + for (const handler of handlers) { + handler.reset(); + } + this.#status = Status.IDLE; + } + async parse(traceEvents, freshRecording = false) { + if (this.#status !== Status.IDLE) { + throw new Error(`Trace processor can't start parsing when not idle. Current state: ${this.#status}`); + } + try { + this.#status = Status.PARSING; + await this.#parse(traceEvents, freshRecording); + this.#status = Status.FINISHED_PARSING; + } + catch (e) { + this.#status = Status.ERRORED_WHILE_PARSING; + throw e; + } + } + async #parse(traceEvents, freshRecording) { + // This iterator steps through all events, periodically yielding back to the + // main thread to avoid blocking execution. It uses `dispatchEvent` to + // provide status update events, and other various bits of config like the + // pause duration and frequency. + const traceEventIterator = new TraceEventIterator(traceEvents, this.#pauseDuration, this.#eventsPerChunk); + // Convert to array so that we are able to iterate all handlers multiple times. + const sortedHandlers = [...sortHandlers(this.#traceHandlers).values()]; + // Reset. + for (const handler of sortedHandlers) { + handler.reset(); + } + // Initialize. + for (const handler of sortedHandlers) { + handler.initialize?.(freshRecording); + } + // Handle each event. + for await (const item of traceEventIterator) { + if (item.kind === IteratorItemType.STATUS_UPDATE) { + this.dispatchEvent(new TraceParseProgressEvent(item.data)); + continue; + } + for (const handler of sortedHandlers) { + handler.handleEvent(item.data); + } + } + // Finalize. + for (const handler of sortedHandlers) { + await handler.finalize?.(); + } + } + get data() { + if (this.#status !== Status.FINISHED_PARSING) { + return null; + } + const data = {}; + for (const [name, handler] of Object.entries(this.#traceHandlers)) { + Object.assign(data, { [name]: handler.data() }); + } + return data; + } +} +exports.TraceProcessor = TraceProcessor; +/** + * Some Handlers need data provided by others. Dependencies of a handler handler are + * declared in the `deps` field. + * @returns A map from trace event handler name to trace event hander whose entries + * iterate in such a way that each handler is visited after its dependencies. + */ +function sortHandlers(traceHandlers) { + const sortedMap = new Map(); + const visited = new Set(); + const visitHandler = (handlerName) => { + if (sortedMap.has(handlerName)) { + return; + } + if (visited.has(handlerName)) { + let stackPath = ''; + for (const handler of visited) { + if (stackPath || handler === handlerName) { + stackPath += `${handler}->`; + } + } + stackPath += handlerName; + throw new Error(`Found dependency cycle in trace event handlers: ${stackPath}`); + } + visited.add(handlerName); + const handler = traceHandlers[handlerName]; + if (!handler) { + return; + } + const deps = handler.deps?.(); + if (deps) { + deps.forEach(visitHandler); + } + sortedMap.set(handlerName, handler); + }; + for (const handlerName of Object.keys(traceHandlers)) { + visitHandler(handlerName); + } + return sortedMap; +} +exports.sortHandlers = sortHandlers; +var IteratorItemType; +(function (IteratorItemType) { + IteratorItemType[IteratorItemType["TRACE_EVENT"] = 1] = "TRACE_EVENT"; + IteratorItemType[IteratorItemType["STATUS_UPDATE"] = 2] = "STATUS_UPDATE"; +})(IteratorItemType || (IteratorItemType = {})); +class TraceEventIterator { + traceEvents; + pauseDuration; + eventsPerChunk; + #eventCount; + constructor(traceEvents, pauseDuration, eventsPerChunk) { + this.traceEvents = traceEvents; + this.pauseDuration = pauseDuration; + this.eventsPerChunk = eventsPerChunk; + this.#eventCount = 0; + } + async *[Symbol.asyncIterator]() { + for (let i = 0, length = this.traceEvents.length; i < length; i++) { + // Every so often we take a break just to render. + if (++this.#eventCount % this.eventsPerChunk === 0) { + // Take the opportunity to provide status update events. + yield { kind: IteratorItemType.STATUS_UPDATE, data: { index: i, total: length } }; + // Wait for rendering before resuming. + await new Promise(resolve => setTimeout(resolve, this.pauseDuration)); + } + yield { kind: IteratorItemType.TRACE_EVENT, data: this.traceEvents[i] }; + } + } +} + diff --git a/core/lib/cdt/generated/models/trace/handlers/LayoutShiftsHandler.js b/core/lib/cdt/generated/models/trace/handlers/LayoutShiftsHandler.js new file mode 100644 index 000000000000..282054bbec25 --- /dev/null +++ b/core/lib/cdt/generated/models/trace/handlers/LayoutShiftsHandler.js @@ -0,0 +1,344 @@ +// @ts-nocheck +// generated by yarn build-cdt-lib +/* eslint-disable */ +"use strict"; +const Platform = require('../../../../Platform.js'); +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +Object.defineProperty(exports, "__esModule", { value: true }); +exports.LayoutShiftsThreshold = exports.stateForLayoutShiftScore = exports.deps = exports.data = exports.finalize = exports.findNextScreenshotEventIndex = exports.handleEvent = exports.reset = exports.initialize = exports.MAX_SHIFT_TIME_DELTA = exports.MAX_CLUSTER_DURATION = void 0; +const Helpers = require("../helpers/helpers.js"); +const types_js_1 = require("./types.js"); +; +const MetaHandler_js_1 = require("./MetaHandler.js"); +; +; +const Types = require("../types/types.js"); +// This represents the maximum #time we will allow a cluster to go before we +// reset it. +exports.MAX_CLUSTER_DURATION = Helpers.Timing.millisecondsToMicroseconds(Types.Timing.MilliSeconds(5000)); +// This represents the maximum #time we will allow between layout shift events +// before considering it to be the start of a new cluster. +exports.MAX_SHIFT_TIME_DELTA = Helpers.Timing.millisecondsToMicroseconds(Types.Timing.MilliSeconds(1000)); +// Layout shifts are reported globally to the developer, irrespective of which +// frame they originated in. However, each process does have its own individual +// CLS score, so we need to segment by process. This means Layout Shifts from +// sites with one process (no subframes, or subframes from the same origin) +// will be reported together. In the case of multiple renderers (frames across +// different origins), we offer the developer the ability to switch renderer in +// the UI. +const layoutShiftEvents = []; +// These events denote potential node resizings. We store them to link captured +// layout shifts to the resizing of unsized elements. +const layoutInvalidationEvents = []; +const styleRecalcInvalidationEvents = []; +// Layout shifts happen during PrePaint as part of the rendering lifecycle. +// We determine if a LayoutInvalidation event is a potential root cause of a layout +// shift if the next PrePaint after the LayoutInvalidation is the parent +// node of such shift. +const prePaintEvents = []; +let sessionMaxScore = 0; +let clsWindowID = -1; +const clusters = []; +// The complete timeline of LS score changes in a trace. +// Includes drops to 0 when session windows end. +const scoreRecords = []; +let handlerState = types_js_1.HandlerState.UNINITIALIZED; +function initialize() { + if (handlerState !== types_js_1.HandlerState.UNINITIALIZED) { + throw new Error('LayoutShifts Handler was not reset'); + } + handlerState = types_js_1.HandlerState.INITIALIZED; +} +exports.initialize = initialize; +function reset() { + handlerState = types_js_1.HandlerState.UNINITIALIZED; + layoutShiftEvents.length = 0; + layoutInvalidationEvents.length = 0; + prePaintEvents.length = 0; + clusters.length = 0; + sessionMaxScore = 0; + scoreRecords.length = 0; + clsWindowID = -1; +} +exports.reset = reset; +function handleEvent(event) { + if (handlerState !== types_js_1.HandlerState.INITIALIZED) { + throw new Error('Handler is not initialized'); + } + if (Types.TraceEvents.isTraceEventLayoutShift(event) && true) { + layoutShiftEvents.push(event); + return; + } + if (Types.TraceEvents.isTraceEventLayoutInvalidation(event)) { + layoutInvalidationEvents.push(event); + return; + } + if (Types.TraceEvents.isTraceEventStyleRecalcInvalidation(event)) { + styleRecalcInvalidationEvents.push(event); + } + if (Types.TraceEvents.isTraceEventPrePaint(event)) { + prePaintEvents.push(event); + return; + } +} +exports.handleEvent = handleEvent; +function traceWindowFromTime(time) { + return { + min: time, + max: time, + range: Types.Timing.MicroSeconds(0), + }; +} +function updateTraceWindowMax(traceWindow, newMax) { + traceWindow.max = newMax; + traceWindow.range = Types.Timing.MicroSeconds(traceWindow.max - traceWindow.min); +} +function findNextScreenshotSource(timestamp) { + const screenshots = (0, ScreenshotsHandler_js_1.data)(); + const screenshotIndex = findNextScreenshotEventIndex(screenshots, timestamp); + if (!screenshotIndex) { + return undefined; + } + return `data:img/png;base64,${screenshots[screenshotIndex].args.snapshot}`; +} +function findNextScreenshotEventIndex(screenshots, timestamp) { + return Platform.ArrayUtilities.nearestIndexFromBeginning(screenshots, frame => frame.ts > timestamp); +} +exports.findNextScreenshotEventIndex = findNextScreenshotEventIndex; +function buildScoreRecords() { + const { traceBounds } = (0, MetaHandler_js_1.data)(); + scoreRecords.push({ ts: traceBounds.min, score: 0 }); + for (const cluster of clusters) { + let clusterScore = 0; + if (cluster.events[0].args.data) { + scoreRecords.push({ ts: cluster.clusterWindow.min, score: cluster.events[0].args.data.weighted_score_delta }); + } + for (let i = 0; i < cluster.events.length; i++) { + const event = cluster.events[i]; + if (!event.args.data) { + continue; + } + clusterScore += event.args.data.weighted_score_delta; + scoreRecords.push({ ts: event.ts, score: clusterScore }); + } + scoreRecords.push({ ts: cluster.clusterWindow.max, score: 0 }); + } +} +async function finalize() { + // Ensure the events are sorted by #time ascending. + layoutShiftEvents.sort((a, b) => a.ts - b.ts); + prePaintEvents.sort((a, b) => a.ts - b.ts); + layoutInvalidationEvents.sort((a, b) => a.ts - b.ts); + // Each function transforms the data used by the next, as such the invoke order + // is important. + await buildLayoutShiftsClusters(); + buildScoreRecords(); + handlerState = types_js_1.HandlerState.FINALIZED; +} +exports.finalize = finalize; +async function buildLayoutShiftsClusters() { + const { navigationsByFrameId, mainFrameId, traceBounds } = (0, MetaHandler_js_1.data)(); + const navigations = navigationsByFrameId.get(mainFrameId) || []; + if (layoutShiftEvents.length === 0) { + return; + } + let firstShiftTime = layoutShiftEvents[0].ts; + let lastShiftTime = layoutShiftEvents[0].ts; + let lastShiftNavigation = null; + // Now step through each and create clusters. + // A cluster is equivalent to a session window (see https://web.dev/cls/#what-is-cls). + // To make the line chart clear, we explicitly demark the limits of each session window + // by starting the cumulative score of the window at the time of the first layout shift + // and ending it (dropping the line back to 0) when the window ends according to the + // thresholds (MAX_CLUSTER_DURATION, MAX_SHIFT_TIME_DELTA). + for (const event of layoutShiftEvents) { + // First detect if either the cluster duration or the #time between this and + // the last shift has been exceeded. + const clusterDurationExceeded = event.ts - firstShiftTime > exports.MAX_CLUSTER_DURATION; + const maxTimeDeltaSinceLastShiftExceeded = event.ts - lastShiftTime > exports.MAX_SHIFT_TIME_DELTA; + // Next take a look at navigations. If between this and the last shift we have navigated, + // note it. + const currentShiftNavigation = Platform.ArrayUtilities.nearestIndexFromEnd(navigations, nav => nav.ts < event.ts); + const hasNavigated = lastShiftNavigation !== currentShiftNavigation && currentShiftNavigation !== null; + // If any of the above criteria are met or if we don't have any cluster yet we should + // start a new one. + if (clusterDurationExceeded || maxTimeDeltaSinceLastShiftExceeded || hasNavigated || !clusters.length) { + // The cluster starts #time should be the timestamp of the first layout shift in it. + const clusterStartTime = event.ts; + // If the last session window ended because the max delta time between shifts + // was exceeded set the endtime to MAX_SHIFT_TIME_DELTA microseconds after the + // last shift in the session. + const endTimeByMaxSessionDuration = clusterDurationExceeded ? firstShiftTime + exports.MAX_CLUSTER_DURATION : Infinity; + // If the last session window ended because the max session duration was + // surpassed, set the endtime so that the window length = MAX_CLUSTER_DURATION; + const endTimeByMaxShiftGap = maxTimeDeltaSinceLastShiftExceeded ? lastShiftTime + exports.MAX_SHIFT_TIME_DELTA : Infinity; + // If there was a navigation during the last window, close it at the time + // of the navigation. + const endTimeByNavigation = hasNavigated ? navigations[currentShiftNavigation].ts : Infinity; + // End the previous cluster at the time of the first of the criteria above that was met. + const previousClusterEndTime = Math.min(endTimeByMaxSessionDuration, endTimeByMaxShiftGap, endTimeByNavigation); + // If there is an existing cluster update its closing time. + if (clusters.length > 0) { + const currentCluster = clusters[clusters.length - 1]; + updateTraceWindowMax(currentCluster.clusterWindow, Types.Timing.MicroSeconds(previousClusterEndTime)); + } + clusters.push({ + events: [], + clusterWindow: traceWindowFromTime(clusterStartTime), + clusterCumulativeScore: 0, + scoreWindows: { + good: traceWindowFromTime(clusterStartTime), + needsImprovement: null, + bad: null, + }, + }); + firstShiftTime = clusterStartTime; + } + // Given the above we should have a cluster available, so pick the most + // recent one and append the shift, bump its score and window values accordingly. + const currentCluster = clusters[clusters.length - 1]; + const timeFromNavigation = currentShiftNavigation !== null ? + Types.Timing.MicroSeconds(event.ts - navigations[currentShiftNavigation].ts) : + undefined; + currentCluster.clusterCumulativeScore += event.args.data ? event.args.data.weighted_score_delta : 0; + if (!event.args.data) { + continue; + } + const shift = { + ...event, + args: { + frame: event.args.frame, + data: { + ...event.args.data, + rawEvent: event, + }, + }, + parsedData: { + screenshotSource: null, + timeFromNavigation, + cumulativeWeightedScoreInWindow: currentCluster.clusterCumulativeScore, + // The score of the session window is temporarily set to 0 just + // to initialize it. Since we need to get the score of all shifts + // in the session window to determine its value, its definite + // value is set when stepping through the built clusters. + sessionWindowData: { cumulativeWindowScore: 0, id: clusters.length }, + }, + }; + currentCluster.events.push(shift); + updateTraceWindowMax(currentCluster.clusterWindow, event.ts); + lastShiftTime = event.ts; + lastShiftNavigation = currentShiftNavigation; + } + // Now step through each cluster and set up the times at which the value + // goes from Good, to needs improvement, to Bad. Note that if there is a + // large jump we may go from Good to Bad without ever creating a Needs + // Improvement window at all. + for (const cluster of clusters) { + let weightedScore = 0; + let windowID = -1; + // If this is the last cluster update its window. The cluster duration is determined + // by the minimum between: time to next navigation, trace end time, time to maximum + // cluster duration and time to maximum gap between layout shifts. + if (cluster === clusters[clusters.length - 1]) { + const clusterEndByMaxDuration = exports.MAX_CLUSTER_DURATION + cluster.clusterWindow.min; + const clusterEndByMaxGap = cluster.clusterWindow.max + exports.MAX_SHIFT_TIME_DELTA; + const nextNavigationIndex = Platform.ArrayUtilities.nearestIndexFromBeginning(navigations, nav => nav.ts > cluster.clusterWindow.max); + const nextNavigationTime = nextNavigationIndex ? navigations[nextNavigationIndex].ts : Infinity; + const clusterEnd = Math.min(clusterEndByMaxDuration, clusterEndByMaxGap, traceBounds.max, nextNavigationTime); + updateTraceWindowMax(cluster.clusterWindow, Types.Timing.MicroSeconds(clusterEnd)); + } + for (const shift of cluster.events) { + weightedScore += shift.args.data ? shift.args.data.weighted_score_delta : 0; + windowID = shift.parsedData.sessionWindowData.id; + const ts = shift.ts; + // Update the the CLS score of this shift's session window now that + // we have it. + shift.parsedData.sessionWindowData.cumulativeWindowScore = cluster.clusterCumulativeScore; + if (weightedScore < LayoutShiftsThreshold.NEEDS_IMPROVEMENT) { + // Expand the Good window. + updateTraceWindowMax(cluster.scoreWindows.good, ts); + } + else if (weightedScore >= LayoutShiftsThreshold.NEEDS_IMPROVEMENT && weightedScore < LayoutShiftsThreshold.BAD) { + if (!cluster.scoreWindows.needsImprovement) { + // Close the Good window, and open the needs improvement window. + updateTraceWindowMax(cluster.scoreWindows.good, Types.Timing.MicroSeconds(ts - 1)); + cluster.scoreWindows.needsImprovement = traceWindowFromTime(ts); + } + // Expand the needs improvement window. + updateTraceWindowMax(cluster.scoreWindows.needsImprovement, ts); + } + else if (weightedScore >= LayoutShiftsThreshold.BAD) { + if (!cluster.scoreWindows.bad) { + // We may jump from Good to Bad here, so update whichever window is open. + if (cluster.scoreWindows.needsImprovement) { + updateTraceWindowMax(cluster.scoreWindows.needsImprovement, Types.Timing.MicroSeconds(ts - 1)); + } + else { + updateTraceWindowMax(cluster.scoreWindows.good, Types.Timing.MicroSeconds(ts - 1)); + } + cluster.scoreWindows.bad = traceWindowFromTime(shift.ts); + } + // Expand the Bad window. + updateTraceWindowMax(cluster.scoreWindows.bad, ts); + } + // At this point the windows are set by the timestamps of the events, but the + // next cluster begins at the timestamp of its first event. As such we now + // need to expand the score window to the end of the cluster, and we do so + // by using the Bad widow if it's there, or the NI window, or finally the + // Good window. + if (cluster.scoreWindows.bad) { + updateTraceWindowMax(cluster.scoreWindows.bad, cluster.clusterWindow.max); + } + else if (cluster.scoreWindows.needsImprovement) { + updateTraceWindowMax(cluster.scoreWindows.needsImprovement, cluster.clusterWindow.max); + } + else { + updateTraceWindowMax(cluster.scoreWindows.good, cluster.clusterWindow.max); + } + } + if (weightedScore > sessionMaxScore) { + clsWindowID = windowID; + sessionMaxScore = weightedScore; + } + } +} +function data() { + if (handlerState !== types_js_1.HandlerState.FINALIZED) { + throw new Error('Layout Shifts Handler is not finalized'); + } + return { + clusters: [...clusters], + sessionMaxScore: sessionMaxScore, + clsWindowID, + prePaintEvents: [...prePaintEvents], + layoutInvalidationEvents: [...layoutInvalidationEvents], + styleRecalcInvalidationEvents: [], + scoreRecords: [...scoreRecords], + }; +} +exports.data = data; +function deps() { + return ['Meta']; +} +exports.deps = deps; +function stateForLayoutShiftScore(score) { + let state = PageLoadMetricsHandler_js_1.ScoreClassification.GOOD; + if (score >= LayoutShiftsThreshold.NEEDS_IMPROVEMENT) { + state = PageLoadMetricsHandler_js_1.ScoreClassification.OK; + } + if (score >= LayoutShiftsThreshold.BAD) { + state = PageLoadMetricsHandler_js_1.ScoreClassification.BAD; + } + return state; +} +exports.stateForLayoutShiftScore = stateForLayoutShiftScore; +// Based on https://web.dev/cls/ +var LayoutShiftsThreshold; +(function (LayoutShiftsThreshold) { + LayoutShiftsThreshold[LayoutShiftsThreshold["GOOD"] = 0] = "GOOD"; + LayoutShiftsThreshold[LayoutShiftsThreshold["NEEDS_IMPROVEMENT"] = 0.1] = "NEEDS_IMPROVEMENT"; + LayoutShiftsThreshold[LayoutShiftsThreshold["BAD"] = 0.25] = "BAD"; +})(LayoutShiftsThreshold = exports.LayoutShiftsThreshold || (exports.LayoutShiftsThreshold = {})); + diff --git a/core/lib/cdt/generated/models/trace/handlers/MetaHandler.js b/core/lib/cdt/generated/models/trace/handlers/MetaHandler.js new file mode 100644 index 000000000000..9f8063613473 --- /dev/null +++ b/core/lib/cdt/generated/models/trace/handlers/MetaHandler.js @@ -0,0 +1,277 @@ +// @ts-nocheck +// generated by yarn build-cdt-lib +/* eslint-disable */ +"use strict"; +const Platform = require('../../../../Platform.js'); +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +Object.defineProperty(exports, "__esModule", { value: true }); +exports.data = exports.finalize = exports.handleEvent = exports.initialize = exports.reset = void 0; +; +const Types = require("../types/types.js"); +const types_js_1 = require("./types.js"); +// We track the renderer processes we see in each frame on the way through the trace. +const rendererProcessesByFrameId = new Map(); +// We will often want to key data by Frame IDs, and commonly we'll care most +// about the main frame's ID, so we store and expose that. +let mainFrameId = ''; +let mainFrameURL = ''; +// We will often want to key data by the browser process, GPU process and top +// level renderer IDs, so keep a track on those. +let browserProcessId = Types.TraceEvents.ProcessID(-1); +let browserThreadId = Types.TraceEvents.ThreadID(-1); +let gpuProcessId = Types.TraceEvents.ProcessID(-1); +let gpuThreadId = Types.TraceEvents.ThreadID(-1); +let viewportRect = null; +const topLevelRendererIds = new Set(); +const traceBounds = { + min: Types.Timing.MicroSeconds(Number.POSITIVE_INFINITY), + max: Types.Timing.MicroSeconds(Number.NEGATIVE_INFINITY), + range: Types.Timing.MicroSeconds(Number.POSITIVE_INFINITY), +}; +/** + * These represent the user navigating. Values such as First Contentful Paint, + * etc, are relative to the navigation. + * + * We store navigation events both by the frame and navigation ID. This means + * when we need to look them up, we can use whichever ID we have. + * + * Note that these Maps will have the same values in them; these are just keyed + * differently to make look-ups easier. + */ +const navigationsByFrameId = new Map(); +const navigationsByNavigationId = new Map(); +// Represents all the threads in the trace, organized by process. This is mostly for internal +// bookkeeping so that during the finalize pass we can obtain the main and browser thread IDs. +const threadsInProcess = new Map(); +let traceStartedTime = Types.Timing.MicroSeconds(-1); +const eventPhasesOfInterestForTraceBounds = new Set([ + Types.TraceEvents.Phase.BEGIN, + Types.TraceEvents.Phase.END, + Types.TraceEvents.Phase.COMPLETE, + Types.TraceEvents.Phase.INSTANT, +]); +let handlerState = types_js_1.HandlerState.UNINITIALIZED; +function reset() { + navigationsByFrameId.clear(); + navigationsByNavigationId.clear(); + browserProcessId = Types.TraceEvents.ProcessID(-1); + browserThreadId = Types.TraceEvents.ThreadID(-1); + gpuProcessId = Types.TraceEvents.ProcessID(-1); + gpuThreadId = Types.TraceEvents.ThreadID(-1); + viewportRect = null; + topLevelRendererIds.clear(); + threadsInProcess.clear(); + rendererProcessesByFrameId.clear(); + traceBounds.min = Types.Timing.MicroSeconds(Number.POSITIVE_INFINITY); + traceBounds.max = Types.Timing.MicroSeconds(Number.NEGATIVE_INFINITY); + traceBounds.range = Types.Timing.MicroSeconds(Number.POSITIVE_INFINITY); + traceStartedTime = Types.Timing.MicroSeconds(-1); + handlerState = types_js_1.HandlerState.UNINITIALIZED; +} +exports.reset = reset; +function initialize() { + if (handlerState !== types_js_1.HandlerState.UNINITIALIZED) { + throw new Error('Meta Handler was not reset'); + } + handlerState = types_js_1.HandlerState.INITIALIZED; +} +exports.initialize = initialize; +function updateRendererProcessByFrame(event, frame) { + const rendererProcessInFrame = Platform.MapUtilities.getWithDefault(rendererProcessesByFrameId, frame.frame, () => new Map()); + const rendererProcessInfo = Platform.MapUtilities.getWithDefault(rendererProcessInFrame, frame.processId, () => { + return { + frame, + window: { + min: Types.Timing.MicroSeconds(0), + max: Types.Timing.MicroSeconds(0), + range: Types.Timing.MicroSeconds(0), + }, + }; + }); + // If this window was already created, do nothing. + if (rendererProcessInfo.window.min !== Types.Timing.MicroSeconds(0)) { + return; + } + // For now we store the time of the event as the min. In the finalize we step + // through each of these windows and update their max and range values. + rendererProcessInfo.window.min = event.ts; +} +function handleEvent(event) { + if (handlerState !== types_js_1.HandlerState.INITIALIZED) { + throw new Error('Meta Handler is not initialized'); + } + // If there is a timestamp (which meta events do not have), and the event does + // not end with ::UMA then it, and the event is in the set of valid phases, + // then it should be included for the purposes of calculating the trace bounds. + // The UMA events in particular seem to be reported on page unloading, which + // often extends the bounds of the trace unhelpfully. + if (event.ts !== 0 && !event.name.endsWith('::UMA') && eventPhasesOfInterestForTraceBounds.has(event.ph)) { + traceBounds.min = Types.Timing.MicroSeconds(Math.min(event.ts, traceBounds.min)); + const eventDuration = event.dur || Types.Timing.MicroSeconds(0); + traceBounds.max = Types.Timing.MicroSeconds(Math.max(event.ts + eventDuration, traceBounds.max)); + } + if (Types.TraceEvents.isProcessName(event) && + (event.args.name === 'Browser' || event.args.name === 'HeadlessBrowser')) { + browserProcessId = event.pid; + return; + } + if (Types.TraceEvents.isProcessName(event) && (event.args.name === 'Gpu' || event.args.name === 'GPU Process')) { + gpuProcessId = event.pid; + return; + } + if (Types.TraceEvents.isThreadName(event) && event.args.name === 'CrGpuMain') { + gpuThreadId = event.tid; + return; + } + if (Types.TraceEvents.isThreadName(event) && event.args.name === 'CrBrowserMain') { + browserThreadId = event.tid; + } + if (Types.TraceEvents.isTraceEventMainFrameViewport(event) && viewportRect === null) { + const rectAsArray = event.args.data.viewport_rect; + const viewportX = rectAsArray[0]; + const viewportY = rectAsArray[1]; + const viewportWidth = rectAsArray[2]; + const viewportHeight = rectAsArray[5]; + viewportRect = null; + } + // The TracingStartedInBrowser event includes the data on which frames are + // in scope at the start of the trace. We use this to identify the frame with + // no parent, i.e. the top level frame. + if (Types.TraceEvents.isTraceEventTracingStartedInBrowser(event)) { + traceStartedTime = event.ts; + if (!event.args.data) { + throw new Error('No frames found in trace data'); + } + for (const frame of (event.args.data.frames ?? [])) { + updateRendererProcessByFrame(event, frame); + if (frame.parent) { + continue; + } + mainFrameId = frame.frame; + mainFrameURL = frame.url; + topLevelRendererIds.add(frame.processId); + } + return; + } + // FrameCommittedInBrowser events tell us information about each frame + // and we use these to track how long each individual renderer is active + // for. We track all renderers here (top level and those in frames), but + // for convenience we also populate a set of top level renderer IDs. + if (Types.TraceEvents.isTraceEventFrameCommittedInBrowser(event)) { + const frame = event.args.data; + if (!frame) { + return; + } + updateRendererProcessByFrame(event, frame); + if (frame.parent) { + return; + } + topLevelRendererIds.add(frame.processId); + return; + } + if (Types.TraceEvents.isTraceEventCommitLoad(event)) { + const frameData = event.args.data; + if (!frameData) { + return; + } + const { frame, name, url } = frameData; + updateRendererProcessByFrame(event, { processId: event.pid, frame, name, url }); + return; + } + // Track all threads based on the process & thread IDs. + if (Types.TraceEvents.isThreadName(event)) { + const threads = Platform.MapUtilities.getWithDefault(threadsInProcess, event.pid, () => new Map()); + threads.set(event.tid, event); + return; + } + // Track all navigation events. Note that there can be navigation start events + // but where the documentLoaderURL is empty. As far as the trace rendering is + // concerned, these events are noise so we filter them out here. + if (Types.TraceEvents.isTraceEventNavigationStartWithURL(event) && event.args.data) { + const navigationId = event.args.data.navigationId; + if (navigationsByNavigationId.has(navigationId)) { + throw new Error('Found multiple navigation start events with the same navigation ID.'); + } + navigationsByNavigationId.set(navigationId, event); + const frameId = event.args.frame; + const existingFrameNavigations = navigationsByFrameId.get(frameId) || []; + existingFrameNavigations.push(event); + navigationsByFrameId.set(frameId, existingFrameNavigations); + return; + } +} +exports.handleEvent = handleEvent; +async function finalize() { + if (handlerState !== types_js_1.HandlerState.INITIALIZED) { + throw new Error('Handler is not initialized'); + } + traceBounds.min = traceStartedTime; + traceBounds.range = Types.Timing.MicroSeconds(traceBounds.max - traceBounds.min); + // If we go from foo.com to example.com we will get a new renderer, and + // therefore the "top level renderer" will have a different PID as it has + // changed. Here we step through each renderer process and updated its window + // bounds, such that we end up with the time ranges in the trace for when + // each particular renderer started and stopped being the main renderer + // process. + for (const [, processWindows] of rendererProcessesByFrameId) { + const processWindowValues = [...processWindows.values()]; + for (let i = 0; i < processWindowValues.length; i++) { + const currentWindow = processWindowValues[i]; + const nextWindow = processWindowValues[i + 1]; + // For the last window we set its max to be positive infinity. + // TODO: Move the trace bounds handler into meta so we can clamp first and last windows. + if (!nextWindow) { + currentWindow.window.max = Types.Timing.MicroSeconds(traceBounds.max); + currentWindow.window.range = Types.Timing.MicroSeconds(traceBounds.max - currentWindow.window.min); + } + else { + currentWindow.window.max = Types.Timing.MicroSeconds(nextWindow.window.min - 1); + currentWindow.window.range = Types.Timing.MicroSeconds(currentWindow.window.max - currentWindow.window.min); + } + } + } + // Frame ids which we didn't register using either the TracingStartedInBrowser or + // the FrameCommittedInBrowser events are considered noise, so we filter them out, as well + // as the navigations that belong to such frames. + for (const [frameId, navigations] of navigationsByFrameId) { + // The frames in the rendererProcessesByFrameId map come only from the + // TracingStartedInBrowser and FrameCommittedInBrowser events, so we can use it as point + // of comparison to determine if a frameId should be discarded. + if (rendererProcessesByFrameId.has(frameId)) { + continue; + } + navigationsByFrameId.delete(frameId); + for (const navigation of navigations) { + if (!navigation.args.data) { + continue; + } + navigationsByNavigationId.delete(navigation.args.data.navigationId); + } + } + handlerState = types_js_1.HandlerState.FINALIZED; +} +exports.finalize = finalize; +function data() { + if (handlerState !== types_js_1.HandlerState.FINALIZED) { + throw new Error('Meta Handler is not finalized'); + } + return { + traceBounds: { ...traceBounds }, + browserProcessId, + browserThreadId, + gpuProcessId, + gpuThreadId: gpuThreadId === Types.TraceEvents.ThreadID(-1) ? undefined : gpuThreadId, + viewportRect: viewportRect || undefined, + mainFrameId, + mainFrameURL, + navigationsByFrameId: new Map(navigationsByFrameId), + navigationsByNavigationId: new Map(navigationsByNavigationId), + threadsInProcess: new Map(threadsInProcess), + rendererProcessesByFrame: new Map(rendererProcessesByFrameId), + topLevelRendererIds: new Set(topLevelRendererIds), + }; +} +exports.data = data; + diff --git a/core/lib/cdt/generated/models/trace/handlers/handlers.js b/core/lib/cdt/generated/models/trace/handlers/handlers.js new file mode 100644 index 000000000000..8a00bd470e25 --- /dev/null +++ b/core/lib/cdt/generated/models/trace/handlers/handlers.js @@ -0,0 +1,9 @@ +// @ts-nocheck +// generated by yarn build-cdt-lib +/* eslint-disable */ +"use strict"; + + module.exports.ModelHandlers = { + Meta: require('./MetaHandler.js'), + LayoutShiftsHandler: require('./LayoutShiftsHandler.js'), + }; diff --git a/core/lib/cdt/generated/models/trace/handlers/types.js b/core/lib/cdt/generated/models/trace/handlers/types.js new file mode 100644 index 000000000000..a94842eda252 --- /dev/null +++ b/core/lib/cdt/generated/models/trace/handlers/types.js @@ -0,0 +1,194 @@ +// @ts-nocheck +// generated by yarn build-cdt-lib +/* eslint-disable */ +"use strict"; +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +Object.defineProperty(exports, "__esModule", { value: true }); +exports.KNOWN_EVENTS = exports.KnownEventName = exports.EventCategory = exports.HandlerState = void 0; +var HandlerState; +(function (HandlerState) { + HandlerState[HandlerState["UNINITIALIZED"] = 1] = "UNINITIALIZED"; + HandlerState[HandlerState["INITIALIZED"] = 2] = "INITIALIZED"; + HandlerState[HandlerState["FINALIZED"] = 3] = "FINALIZED"; +})(HandlerState = exports.HandlerState || (exports.HandlerState = {})); +var EventCategory; +(function (EventCategory) { + EventCategory["Parse"] = "Parse"; + EventCategory["V8"] = "V8"; + EventCategory["Js"] = "Js"; + EventCategory["Gc"] = "Gc"; + EventCategory["Layout"] = "Layout"; + EventCategory["Paint"] = "Paint"; + EventCategory["Load"] = "Load"; + EventCategory["Other"] = "Other"; +})(EventCategory = exports.EventCategory || (exports.EventCategory = {})); +var KnownEventName; +(function (KnownEventName) { + /* Task/Other */ + KnownEventName["Program"] = "Program"; + KnownEventName["RunTask"] = "RunTask"; + KnownEventName["AsyncTask"] = "AsyncTask"; + /* Load */ + KnownEventName["XHRLoad"] = "XHRLoad"; + KnownEventName["XHRReadyStateChange"] = "XHRReadyStateChange"; + /* Parse */ + KnownEventName["ParseHTML"] = "ParseHTML"; + KnownEventName["ParseCSS"] = "ParseAuthorStyleSheet"; + /* V8 */ + KnownEventName["CompileScript"] = "V8.CompileScript"; + KnownEventName["CompileCode"] = "V8.CompileCode"; + KnownEventName["CompileModule"] = "V8.CompileModule"; + KnownEventName["Optimize"] = "V8.OptimizeCode"; + KnownEventName["WasmStreamFromResponseCallback"] = "v8.wasm.streamFromResponseCallback"; + KnownEventName["WasmCompiledModule"] = "v8.wasm.compiledModule"; + KnownEventName["WasmCachedModule"] = "v8.wasm.cachedModule"; + KnownEventName["WasmModuleCacheHit"] = "v8.wasm.moduleCacheHit"; + KnownEventName["WasmModuleCacheInvalid"] = "v8.wasm.moduleCacheInvalid"; + /* Js */ + KnownEventName["RunMicrotasks"] = "RunMicrotasks"; + KnownEventName["EvaluateScript"] = "EvaluateScript"; + KnownEventName["FunctionCall"] = "FunctionCall"; + KnownEventName["EventDispatch"] = "EventDispatch"; + KnownEventName["RequestMainThreadFrame"] = "RequestMainThreadFrame"; + KnownEventName["RequestAnimationFrame"] = "RequestAnimationFrame"; + KnownEventName["CancelAnimationFrame"] = "CancelAnimationFrame"; + KnownEventName["FireAnimationFrame"] = "FireAnimationFrame"; + KnownEventName["RequestIdleCallback"] = "RequestIdleCallback"; + KnownEventName["CancelIdleCallback"] = "CancelIdleCallback"; + KnownEventName["FireIdleCallback"] = "FireIdleCallback"; + KnownEventName["TimerInstall"] = "TimerInstall"; + KnownEventName["TimerRemove"] = "TimerRemove"; + KnownEventName["TimerFire"] = "TimerFire"; + KnownEventName["WebSocketCreate"] = "WebSocketCreate"; + KnownEventName["WebSocketSendHandshake"] = "WebSocketSendHandshakeRequest"; + KnownEventName["WebSocketReceiveHandshake"] = "WebSocketReceiveHandshakeResponse"; + KnownEventName["WebSocketDestroy"] = "WebSocketDestroy"; + KnownEventName["CryptoDoEncrypt"] = "DoEncrypt"; + KnownEventName["CryptoDoEncryptReply"] = "DoEncryptReply"; + KnownEventName["CryptoDoDecrypt"] = "DoDecrypt"; + KnownEventName["CryptoDoDecryptReply"] = "DoDecryptReply"; + KnownEventName["CryptoDoDigest"] = "DoDigest"; + KnownEventName["CryptoDoDigestReply"] = "DoDigestReply"; + KnownEventName["CryptoDoSign"] = "DoSign"; + KnownEventName["CryptoDoSignReply"] = "DoSignReply"; + KnownEventName["CryptoDoVerify"] = "DoVerify"; + KnownEventName["CryptoDoVerifyReply"] = "DoVerifyReply"; + /* Gc */ + KnownEventName["GC"] = "GCEvent"; + KnownEventName["DOMGC"] = "BlinkGC.AtomicPhase"; + KnownEventName["IncrementalGCMarking"] = "V8.GCIncrementalMarking"; + KnownEventName["MajorGC"] = "MajorGC"; + KnownEventName["MinorGC"] = "MinorGC"; + /* Layout (a.k.a "Rendering") */ + KnownEventName["ScheduleStyleRecalculation"] = "ScheduleStyleRecalculation"; + KnownEventName["RecalculateStyles"] = "RecalculateStyles"; + KnownEventName["Layout"] = "Layout"; + KnownEventName["UpdateLayoutTree"] = "UpdateLayoutTree"; + KnownEventName["InvalidateLayout"] = "InvalidateLayout"; + KnownEventName["LayoutInvalidationTracking"] = "LayoutInvalidationTracking"; + KnownEventName["ComputeIntersections"] = "ComputeIntersections"; + KnownEventName["HitTest"] = "HitTest"; + KnownEventName["PrePaint"] = "PrePaint"; + /* Paint */ + KnownEventName["ScrollLayer"] = "ScrollLayer"; + KnownEventName["UpdateLayer"] = "UpdateLayer"; + KnownEventName["PaintSetup"] = "PaintSetup"; + KnownEventName["Paint"] = "Paint"; + KnownEventName["PaintImage"] = "PaintImage"; + KnownEventName["Commit"] = "Commit"; + KnownEventName["CompositeLayers"] = "CompositeLayers"; + KnownEventName["RasterTask"] = "RasterTask"; + KnownEventName["ImageDecodeTask"] = "ImageDecodeTask"; + KnownEventName["ImageUploadTask"] = "ImageUploadTask"; + KnownEventName["DecodeImage"] = "Decode Image"; + KnownEventName["ResizeImage"] = "Resize Image"; + KnownEventName["DrawLazyPixelRef"] = "Draw LazyPixelRef"; + KnownEventName["DecodeLazyPixelRef"] = "Decode LazyPixelRef"; + KnownEventName["GPUTask"] = "GPUTask"; +})(KnownEventName = exports.KnownEventName || (exports.KnownEventName = {})); +exports.KNOWN_EVENTS = new Map([ + /* Task/Other */ + [KnownEventName.Program, { category: EventCategory.Other, label: 'Other' }], + [KnownEventName.RunTask, { category: EventCategory.Other, label: 'Run Task' }], + [KnownEventName.AsyncTask, { category: EventCategory.Other, label: 'Async Task' }], + /* Load */ + [KnownEventName.XHRLoad, { category: EventCategory.Load, label: 'Load' }], + [KnownEventName.XHRReadyStateChange, { category: EventCategory.Load, label: 'ReadyStateChange' }], + /* Parse */ + [KnownEventName.ParseHTML, { category: EventCategory.Parse, label: 'Parse HTML' }], + [KnownEventName.ParseCSS, { category: EventCategory.Parse, label: 'Parse StyleSheet' }], + /* V8 */ + [KnownEventName.CompileScript, { category: EventCategory.V8, label: 'Compile Script' }], + [KnownEventName.CompileCode, { category: EventCategory.V8, label: 'Compile Code' }], + [KnownEventName.CompileModule, { category: EventCategory.V8, label: 'Compile Module' }], + [KnownEventName.Optimize, { category: EventCategory.V8, label: 'Optimize' }], + [KnownEventName.WasmStreamFromResponseCallback, { category: EventCategory.Js, label: 'Streaming Wasm Response' }], + [KnownEventName.WasmCompiledModule, { category: EventCategory.Js, label: 'Compiled Wasm Module' }], + [KnownEventName.WasmCachedModule, { category: EventCategory.Js, label: 'Cached Wasm Module' }], + [KnownEventName.WasmModuleCacheHit, { category: EventCategory.Js, label: 'Wasm Module Cache Hit' }], + [KnownEventName.WasmModuleCacheInvalid, { category: EventCategory.Js, label: 'Wasm Module Cache Invalid' }], + /* Js */ + [KnownEventName.RunMicrotasks, { category: EventCategory.Js, label: 'Run Microtasks' }], + [KnownEventName.EvaluateScript, { category: EventCategory.Js, label: 'Evaluate Script' }], + [KnownEventName.FunctionCall, { category: EventCategory.Js, label: 'Function Call' }], + [KnownEventName.EventDispatch, { category: EventCategory.Js, label: 'Event' }], + [KnownEventName.RequestMainThreadFrame, { category: EventCategory.Js, label: 'Request Main Thread Frame' }], + [KnownEventName.RequestAnimationFrame, { category: EventCategory.Js, label: 'Request Animation Frame' }], + [KnownEventName.CancelAnimationFrame, { category: EventCategory.Js, label: 'Cancel Animation Frame' }], + [KnownEventName.FireAnimationFrame, { category: EventCategory.Js, label: 'Animation Frame' }], + [KnownEventName.RequestIdleCallback, { category: EventCategory.Js, label: 'Request Idle Callback' }], + [KnownEventName.CancelIdleCallback, { category: EventCategory.Js, label: 'Cancel Idle Callback' }], + [KnownEventName.FireIdleCallback, { category: EventCategory.Js, label: 'Idle Callback' }], + [KnownEventName.TimerInstall, { category: EventCategory.Js, label: 'Timer Installed' }], + [KnownEventName.TimerRemove, { category: EventCategory.Js, label: 'Timer Removed' }], + [KnownEventName.TimerFire, { category: EventCategory.Js, label: 'Timer Fired' }], + [KnownEventName.WebSocketCreate, { category: EventCategory.Js, label: 'Create WebSocket' }], + [KnownEventName.WebSocketSendHandshake, { category: EventCategory.Js, label: 'Send WebSocket Handshake' }], + [KnownEventName.WebSocketReceiveHandshake, { category: EventCategory.Js, label: 'Receive WebSocket Handshake' }], + [KnownEventName.WebSocketDestroy, { category: EventCategory.Js, label: 'Destroy WebSocket' }], + [KnownEventName.CryptoDoEncrypt, { category: EventCategory.Js, label: 'Crypto Encrypt' }], + [KnownEventName.CryptoDoEncryptReply, { category: EventCategory.Js, label: 'Crypto Encrypt Reply' }], + [KnownEventName.CryptoDoDecrypt, { category: EventCategory.Js, label: 'Crypto Decrypt' }], + [KnownEventName.CryptoDoDecryptReply, { category: EventCategory.Js, label: 'Crypto Decrypt Reply' }], + [KnownEventName.CryptoDoDigest, { category: EventCategory.Js, label: 'Crypto Digest' }], + [KnownEventName.CryptoDoDigestReply, { category: EventCategory.Js, label: 'Crypto Digest Reply' }], + [KnownEventName.CryptoDoSign, { category: EventCategory.Js, label: 'Crypto Sign' }], + [KnownEventName.CryptoDoSignReply, { category: EventCategory.Js, label: 'Crypto Sign Reply' }], + [KnownEventName.CryptoDoVerify, { category: EventCategory.Js, label: 'Crypto Verify' }], + [KnownEventName.CryptoDoVerifyReply, { category: EventCategory.Js, label: 'Crypto Verify Reply' }], + /* Gc */ + [KnownEventName.GC, { category: EventCategory.Gc, label: 'GC' }], + [KnownEventName.DOMGC, { category: EventCategory.Gc, label: 'DOM GC' }], + [KnownEventName.IncrementalGCMarking, { category: EventCategory.Gc, label: 'Incremental GC' }], + [KnownEventName.MajorGC, { category: EventCategory.Gc, label: 'Major GC' }], + [KnownEventName.MinorGC, { category: EventCategory.Gc, label: 'Minor GC' }], + /* Layout (a.k.a "Rendering") */ + [KnownEventName.ScheduleStyleRecalculation, { category: EventCategory.Layout, label: 'Schedule Recalculate Style' }], + [KnownEventName.RecalculateStyles, { category: EventCategory.Layout, label: 'Recalculate Style' }], + [KnownEventName.Layout, { category: EventCategory.Layout, label: 'Layout' }], + [KnownEventName.UpdateLayoutTree, { category: EventCategory.Layout, label: 'Recalculate Style' }], + [KnownEventName.InvalidateLayout, { category: EventCategory.Layout, label: 'Invalidate Layout' }], + [KnownEventName.LayoutInvalidationTracking, { category: EventCategory.Layout, label: 'Layout Invalidation' }], + [KnownEventName.ComputeIntersections, { category: EventCategory.Paint, label: 'Compute Intersections' }], + [KnownEventName.HitTest, { category: EventCategory.Layout, label: 'Hit Test' }], + [KnownEventName.PrePaint, { category: EventCategory.Layout, label: 'Pre-Paint' }], + /* Paint */ + [KnownEventName.ScrollLayer, { category: EventCategory.Paint, label: 'Scroll' }], + [KnownEventName.UpdateLayer, { category: EventCategory.Paint, label: 'Update Layer' }], + [KnownEventName.PaintSetup, { category: EventCategory.Paint, label: 'Paint Setup' }], + [KnownEventName.Paint, { category: EventCategory.Paint, label: 'Paint' }], + [KnownEventName.PaintImage, { category: EventCategory.Paint, label: 'Paint Image' }], + [KnownEventName.Commit, { category: EventCategory.Paint, label: 'Commit' }], + [KnownEventName.CompositeLayers, { category: EventCategory.Paint, label: 'Composite Layers' }], + [KnownEventName.RasterTask, { category: EventCategory.Paint, label: 'Raster' }], + [KnownEventName.ImageDecodeTask, { category: EventCategory.Paint, label: 'Decode Image Task' }], + [KnownEventName.ImageUploadTask, { category: EventCategory.Paint, label: 'Upload Image Task' }], + [KnownEventName.DecodeImage, { category: EventCategory.Paint, label: 'Decode Image' }], + [KnownEventName.ResizeImage, { category: EventCategory.Paint, label: 'Resize Image' }], + [KnownEventName.DrawLazyPixelRef, { category: EventCategory.Paint, label: 'Draw LazyPixelRef' }], + [KnownEventName.DecodeLazyPixelRef, { category: EventCategory.Paint, label: 'Decode LazyPixelRef' }], + [KnownEventName.GPUTask, { category: EventCategory.Paint, label: 'GPU Task' }], +]); + diff --git a/core/lib/cdt/generated/models/trace/helpers/Timing.js b/core/lib/cdt/generated/models/trace/helpers/Timing.js new file mode 100644 index 000000000000..9a147f57d364 --- /dev/null +++ b/core/lib/cdt/generated/models/trace/helpers/Timing.js @@ -0,0 +1,110 @@ +// @ts-nocheck +// generated by yarn build-cdt-lib +/* eslint-disable */ +"use strict"; +const Platform = require('../../../../Platform.js'); +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +Object.defineProperty(exports, "__esModule", { value: true }); +exports.timeStampForEventAdjustedByClosestNavigation = exports.formatMicrosecondsTime = exports.detectBestTimeUnit = exports.microSecondsToMilliseconds = exports.secondsToMicroseconds = exports.secondsToMilliseconds = exports.millisecondsToMicroseconds = void 0; +; +const Types = require("../types/types.js"); +const Trace_js_1 = require("./Trace.js"); +const millisecondsToMicroseconds = (value) => Types.Timing.MicroSeconds(value * 1000); +exports.millisecondsToMicroseconds = millisecondsToMicroseconds; +const secondsToMilliseconds = (value) => Types.Timing.MilliSeconds(value * 1000); +exports.secondsToMilliseconds = secondsToMilliseconds; +const secondsToMicroseconds = (value) => (0, exports.millisecondsToMicroseconds)((0, exports.secondsToMilliseconds)(value)); +exports.secondsToMicroseconds = secondsToMicroseconds; +const microSecondsToMilliseconds = (value) => Types.Timing.MilliSeconds(value / 1000); +exports.microSecondsToMilliseconds = microSecondsToMilliseconds; +function detectBestTimeUnit(timeInMicroseconds) { + if (timeInMicroseconds < 1000) { + return Types.Timing.TimeUnit.MICROSECONDS; + } + const timeInMilliseconds = timeInMicroseconds / 1000; + if (timeInMilliseconds < 1000) { + return Types.Timing.TimeUnit.MILLISECONDS; + } + const timeInSeconds = timeInMilliseconds / 1000; + if (timeInSeconds < 60) { + return Types.Timing.TimeUnit.SECONDS; + } + return Types.Timing.TimeUnit.MINUTES; +} +exports.detectBestTimeUnit = detectBestTimeUnit; +const defaultFormatOptions = { + style: 'unit', + unit: 'millisecond', + unitDisplay: 'narrow', +}; +// Create a bunch of common formatters up front, so that we're not creating +// them repeatedly during rendering. +const serialize = (value) => JSON.stringify(value); +const formatterFactory = (key) => { + return new Intl.NumberFormat('en', key ? JSON.parse(key) : {}); +}; +const formatters = new Map(); +// Microsecond Formatter. +Platform.MapUtilities.getWithDefault(formatters, serialize({ style: 'decimal' }), formatterFactory); +// Millisecond Formatter +Platform.MapUtilities.getWithDefault(formatters, serialize(defaultFormatOptions), formatterFactory); +// Second Formatter +Platform.MapUtilities.getWithDefault(formatters, serialize({ ...defaultFormatOptions, unit: 'second' }), formatterFactory); +// Minute Formatter +Platform.MapUtilities.getWithDefault(formatters, serialize({ ...defaultFormatOptions, unit: 'minute' }), formatterFactory); +function formatMicrosecondsTime(timeInMicroseconds, opts = {}) { + if (!opts.format) { + opts.format = detectBestTimeUnit(timeInMicroseconds); + } + const timeInMilliseconds = timeInMicroseconds / 1000; + const timeInSeconds = timeInMilliseconds / 1000; + const formatterOpts = { ...defaultFormatOptions, ...opts }; + switch (opts.format) { + case Types.Timing.TimeUnit.MICROSECONDS: { + const formatter = Platform.MapUtilities.getWithDefault(formatters, serialize({ style: 'decimal' }), formatterFactory); + return `${formatter.format(timeInMicroseconds)}μs`; + } + case Types.Timing.TimeUnit.MILLISECONDS: { + const formatter = Platform.MapUtilities.getWithDefault(formatters, serialize(formatterOpts), formatterFactory); + return formatter.format(timeInMilliseconds); + } + case Types.Timing.TimeUnit.SECONDS: { + const formatter = Platform.MapUtilities.getWithDefault(formatters, serialize({ ...formatterOpts, unit: 'second' }), formatterFactory); + return formatter.format(timeInSeconds); + } + default: { + // Switch to mins & seconds. + const minuteFormatter = Platform.MapUtilities.getWithDefault(formatters, serialize({ ...formatterOpts, unit: 'minute' }), formatterFactory); + const secondFormatter = Platform.MapUtilities.getWithDefault(formatters, serialize({ ...formatterOpts, unit: 'second' }), formatterFactory); + const timeInMinutes = timeInSeconds / 60; + const [mins, divider, fraction] = minuteFormatter.formatToParts(timeInMinutes); + let seconds = 0; + if (divider && fraction) { + // Convert the fraction value (a string) to the nearest second. + seconds = Math.round(Number(`0.${fraction.value}`) * 60); + } + return `${minuteFormatter.format(Number(mins.value))} ${secondFormatter.format(seconds)}`; + } + } +} +exports.formatMicrosecondsTime = formatMicrosecondsTime; +function timeStampForEventAdjustedByClosestNavigation(event, traceBounds, navigationsByNavigationId, navigationsByFrameId) { + let eventTimeStamp = event.ts - traceBounds.min; + if (event.args?.data?.navigationId) { + const navigationForEvent = navigationsByNavigationId.get(event.args.data.navigationId); + if (navigationForEvent) { + eventTimeStamp = event.ts - navigationForEvent.ts; + } + } + else if (event.args?.data?.frame) { + const navigationForEvent = (0, Trace_js_1.getNavigationForTraceEvent)(event, event.args.data.frame, navigationsByFrameId); + if (navigationForEvent) { + eventTimeStamp = event.ts - navigationForEvent.ts; + } + } + return Types.Timing.MicroSeconds(eventTimeStamp); +} +exports.timeStampForEventAdjustedByClosestNavigation = timeStampForEventAdjustedByClosestNavigation; + diff --git a/core/lib/cdt/generated/models/trace/helpers/Trace.js b/core/lib/cdt/generated/models/trace/helpers/Trace.js new file mode 100644 index 000000000000..633372a69b95 --- /dev/null +++ b/core/lib/cdt/generated/models/trace/helpers/Trace.js @@ -0,0 +1,91 @@ +// @ts-nocheck +// generated by yarn build-cdt-lib +/* eslint-disable */ +"use strict"; +const Platform = require('../../../../Platform.js'); +const Common = require('../../../../Common.js'); +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +Object.defineProperty(exports, "__esModule", { value: true }); +exports.extractId = exports.getNavigationForTraceEvent = exports.sortTraceEventsInPlace = exports.addEventToProcessThread = exports.extractOriginFromTrace = void 0; +; +; +function extractOriginFromTrace(firstNavigationURL) { + const url = Common.ParsedURL.ParsedURL.fromString(firstNavigationURL); + if (url) { + // We do this to save some space in the toolbar - seeing the `www` is less + // useful than seeing `foo.com` if it's truncated at narrow widths + if (url.host.startsWith('www.')) { + return url.host.slice(4); + } + return url.host; + } + return null; +} +exports.extractOriginFromTrace = extractOriginFromTrace; +// Each thread contains events. Events indicate the thread and process IDs, which are +// used to store the event in the correct process thread entry below. +function addEventToProcessThread(event, eventsInProcessThread) { + const { tid, pid } = event; + let eventsInThread = eventsInProcessThread.get(pid); + if (!eventsInThread) { + eventsInThread = new Map(); + } + let events = eventsInThread.get(tid); + if (!events) { + events = []; + } + events.push(event); + eventsInThread.set(event.tid, events); + eventsInProcessThread.set(event.pid, eventsInThread); +} +exports.addEventToProcessThread = addEventToProcessThread; +/** + * Sorts all the events in place, in order, by their start time. If they have + * the same start time, orders them by longest first. + */ +function sortTraceEventsInPlace(events) { + events.sort((a, b) => { + const aBeginTime = a.ts; + const bBeginTime = b.ts; + if (aBeginTime < bBeginTime) { + return -1; + } + if (aBeginTime > bBeginTime) { + return 1; + } + const aDuration = a.dur ?? 0; + const bDuration = b.dur ?? 0; + const aEndTime = aBeginTime + aDuration; + const bEndTime = bBeginTime + bDuration; + if (aEndTime > bEndTime) { + return -1; + } + if (aEndTime < bEndTime) { + return 1; + } + return 0; + }); +} +exports.sortTraceEventsInPlace = sortTraceEventsInPlace; +function getNavigationForTraceEvent(event, eventFrameId, navigationsByFrameId) { + const navigations = navigationsByFrameId.get(eventFrameId); + if (!navigations || eventFrameId === '') { + // This event's navigation has been filtered out by the meta handler as a noise event + // or contains an empty frameId. + return null; + } + const eventNavigationIndex = Platform.ArrayUtilities.nearestIndexFromEnd(navigations, navigation => navigation.ts <= event.ts); + if (eventNavigationIndex === null) { + // This event's navigation has been filtered out by the meta handler as a noise event. + return null; + } + return navigations[eventNavigationIndex]; +} +exports.getNavigationForTraceEvent = getNavigationForTraceEvent; +function extractId(event) { + return event.id || event.id2?.global || event.id2?.local; +} +exports.extractId = extractId; + diff --git a/core/lib/cdt/generated/models/trace/helpers/helpers.js b/core/lib/cdt/generated/models/trace/helpers/helpers.js new file mode 100644 index 000000000000..a40c5e1fb9e0 --- /dev/null +++ b/core/lib/cdt/generated/models/trace/helpers/helpers.js @@ -0,0 +1,12 @@ +// @ts-nocheck +// generated by yarn build-cdt-lib +/* eslint-disable */ +"use strict"; +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Trace = exports.Timing = void 0; +exports.Timing = require("./Timing.js"); +exports.Trace = require("./Trace.js"); + diff --git a/core/lib/cdt/generated/models/trace/types/Timing.js b/core/lib/cdt/generated/models/trace/types/Timing.js new file mode 100644 index 000000000000..c794402b8c53 --- /dev/null +++ b/core/lib/cdt/generated/models/trace/types/Timing.js @@ -0,0 +1,42 @@ +// @ts-nocheck +// generated by yarn build-cdt-lib +/* eslint-disable */ +"use strict"; +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TimeUnit = exports.Seconds = exports.MilliSeconds = exports.MicroSeconds = void 0; +/* eslint-disable no-unused-private-class-members */ +class MicroSecondsTag { + #microSecondsTag; +} +// eslint-disable-next-line @typescript-eslint/naming-convention +function MicroSeconds(value) { + return value; +} +exports.MicroSeconds = MicroSeconds; +class MilliSecondsTag { + #milliSecondsTag; +} +// eslint-disable-next-line @typescript-eslint/naming-convention +function MilliSeconds(value) { + return value; +} +exports.MilliSeconds = MilliSeconds; +class SecondsTag { + #secondsTag; +} +// eslint-disable-next-line @typescript-eslint/naming-convention +function Seconds(value) { + return value; +} +exports.Seconds = Seconds; +var TimeUnit; +(function (TimeUnit) { + TimeUnit[TimeUnit["MICROSECONDS"] = 0] = "MICROSECONDS"; + TimeUnit[TimeUnit["MILLISECONDS"] = 1] = "MILLISECONDS"; + TimeUnit[TimeUnit["SECONDS"] = 2] = "SECONDS"; + TimeUnit[TimeUnit["MINUTES"] = 3] = "MINUTES"; +})(TimeUnit = exports.TimeUnit || (exports.TimeUnit = {})); + diff --git a/core/lib/cdt/generated/models/trace/types/TraceEvents.js b/core/lib/cdt/generated/models/trace/types/TraceEvents.js new file mode 100644 index 000000000000..26cefdfbc9fa --- /dev/null +++ b/core/lib/cdt/generated/models/trace/types/TraceEvents.js @@ -0,0 +1,326 @@ +// @ts-nocheck +// generated by yarn build-cdt-lib +/* eslint-disable */ +"use strict"; +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +Object.defineProperty(exports, "__esModule", { value: true }); +exports.isSyntheticUserTimingTraceEvent = exports.isTraceEventMainFrameViewport = exports.isTraceEventNavigationStartWithURL = exports.isTraceEventPrePaint = exports.isSyntheticNetworkRequestDetailsEvent = exports.isTraceEventResourceReceivedData = exports.isTraceEventResourceWillSendRequest = exports.isTraceEventResourceFinish = exports.isTraceEventResourceReceiveResponse = exports.isTraceEventResourceSendRequest = exports.isTraceEventProfileChunk = exports.isTraceEventProfile = exports.isTraceEventGPUTask = exports.isTraceEventEventTimingStart = exports.isTraceEventEventTimingEnd = exports.isTraceEventEventTiming = exports.isTraceEventInteractiveTime = exports.isTraceEventMarkDOMContent = exports.isTraceEventFirstPaint = exports.isTraceEventMarkLoad = exports.isTraceEventLargestTextPaintCandidate = exports.isTraceEventLargestImagePaintCandidate = exports.isTraceEventLargestContentfulPaintCandidate = exports.isTraceEventFirstContentfulPaint = exports.isTraceEventStyleRecalcInvalidation = exports.isTraceEventLayoutInvalidation = exports.isTraceEventLayoutShift = exports.isTraceEventAnimation = exports.isTraceEventNavigationStart = exports.isTraceEventCommitLoad = exports.isTraceEventFrameCommittedInBrowser = exports.isTraceEventTracingStartedInBrowser = exports.isProcessName = exports.isThreadName = exports.isTraceEventRendererEvent = exports.isTraceEventInstant = exports.isTraceEventDispatch = exports.isTraceEventComplete = exports.ThreadID = exports.ProcessID = exports.CallFrameID = exports.ProfileID = exports.isSyntheticInteractionEvent = exports.StyleRecalcInvalidationReason = exports.LayoutInvalidationReason = exports.TraceEventScope = exports.isFlowPhase = exports.isAsyncPhase = exports.isNestableAsyncPhase = exports.Phase = void 0; +exports.isSyntheticLayoutShift = exports.isTraceEventAsyncPhase = exports.isTraceEventTimeStamp = exports.isTraceEventConsoleTime = exports.isTraceEventPerformanceMark = exports.isTraceEventPerformanceMeasure = exports.isSyntheticConsoleTimingTraceEvent = void 0; +// Trace Events. +var Phase; +(function (Phase) { + // Standard + Phase["BEGIN"] = "B"; + Phase["END"] = "E"; + Phase["COMPLETE"] = "X"; + Phase["INSTANT"] = "I"; + Phase["COUNTER"] = "C"; + // Async + Phase["ASYNC_NESTABLE_START"] = "b"; + Phase["ASYNC_NESTABLE_INSTANT"] = "n"; + Phase["ASYNC_NESTABLE_END"] = "e"; + Phase["ASYNC_STEP_INTO"] = "T"; + Phase["ASYNC_BEGIN"] = "S"; + Phase["ASYNC_END"] = "F"; + Phase["ASYNC_STEP_PAST"] = "p"; + // Flow + Phase["FLOW_START"] = "s"; + Phase["FLOW_STEP"] = "t"; + Phase["FLOW_END"] = "f"; + // Sample + Phase["SAMPLE"] = "P"; + // Object + Phase["OBJECT_CREATED"] = "N"; + Phase["OBJECT_SNAPSHOT"] = "O"; + Phase["OBJECT_DESTROYED"] = "D"; + // Metadata + Phase["METADATA"] = "M"; + // Memory Dump + Phase["MEMORY_DUMP_GLOBAL"] = "V"; + Phase["MEMORY_DUMP_PROCESS"] = "v"; + // Mark + Phase["MARK"] = "R"; + // Clock sync + Phase["CLOCK_SYNC"] = "c"; +})(Phase = exports.Phase || (exports.Phase = {})); +function isNestableAsyncPhase(phase) { + return phase === Phase.ASYNC_NESTABLE_START || phase === Phase.ASYNC_NESTABLE_END || + phase === Phase.ASYNC_NESTABLE_INSTANT; +} +exports.isNestableAsyncPhase = isNestableAsyncPhase; +function isAsyncPhase(phase) { + return isNestableAsyncPhase(phase) || phase === Phase.ASYNC_BEGIN || phase === Phase.ASYNC_STEP_INTO || + phase === Phase.ASYNC_END || phase === Phase.ASYNC_STEP_PAST; +} +exports.isAsyncPhase = isAsyncPhase; +function isFlowPhase(phase) { + return phase === Phase.FLOW_START || phase === Phase.FLOW_STEP || phase === Phase.FLOW_END; +} +exports.isFlowPhase = isFlowPhase; +var TraceEventScope; +(function (TraceEventScope) { + TraceEventScope["THREAD"] = "t"; + TraceEventScope["PROCESS"] = "p"; + TraceEventScope["GLOBAL"] = "g"; +})(TraceEventScope = exports.TraceEventScope || (exports.TraceEventScope = {})); +var LayoutInvalidationReason; +(function (LayoutInvalidationReason) { + LayoutInvalidationReason["SIZE_CHANGED"] = "Size changed"; + LayoutInvalidationReason["ATTRIBUTE"] = "Attribute"; + LayoutInvalidationReason["ADDED_TO_LAYOUT"] = "Added to layout"; + LayoutInvalidationReason["SCROLLBAR_CHANGED"] = "Scrollbar changed"; + LayoutInvalidationReason["REMOVED_FROM_LAYOUT"] = "Removed from layout"; + LayoutInvalidationReason["STYLE_CHANGED"] = "Style changed"; + LayoutInvalidationReason["FONTS_CHANGED"] = "Fonts changed"; + LayoutInvalidationReason["UNKNOWN"] = "Unknown"; +})(LayoutInvalidationReason = exports.LayoutInvalidationReason || (exports.LayoutInvalidationReason = {})); +var StyleRecalcInvalidationReason; +(function (StyleRecalcInvalidationReason) { + StyleRecalcInvalidationReason["ANIMATION"] = "Animation"; +})(StyleRecalcInvalidationReason = exports.StyleRecalcInvalidationReason || (exports.StyleRecalcInvalidationReason = {})); +function isSyntheticInteractionEvent(event) { + return Boolean('interactionId' in event && event.args?.data && 'beginEvent' in event.args.data && 'endEvent' in event.args.data); +} +exports.isSyntheticInteractionEvent = isSyntheticInteractionEvent; +class ProfileIdTag { + #profileIdTag; +} +// eslint-disable-next-line @typescript-eslint/naming-convention +function ProfileID(value) { + return value; +} +exports.ProfileID = ProfileID; +class CallFrameIdTag { + #callFrameIdTag; +} +// eslint-disable-next-line @typescript-eslint/naming-convention +function CallFrameID(value) { + return value; +} +exports.CallFrameID = CallFrameID; +class ProcessIdTag { + #processIdTag; +} +// eslint-disable-next-line @typescript-eslint/naming-convention +function ProcessID(value) { + return value; +} +exports.ProcessID = ProcessID; +class ThreadIdTag { + #threadIdTag; +} +// eslint-disable-next-line @typescript-eslint/naming-convention +function ThreadID(value) { + return value; +} +exports.ThreadID = ThreadID; +function isTraceEventComplete(event) { + return event.ph === Phase.COMPLETE; +} +exports.isTraceEventComplete = isTraceEventComplete; +function isTraceEventDispatch(event) { + return event.name === 'EventDispatch'; +} +exports.isTraceEventDispatch = isTraceEventDispatch; +function isTraceEventInstant(event) { + return event.ph === Phase.INSTANT; +} +exports.isTraceEventInstant = isTraceEventInstant; +function isTraceEventRendererEvent(event) { + return isTraceEventInstant(event) || isTraceEventComplete(event); +} +exports.isTraceEventRendererEvent = isTraceEventRendererEvent; +function isThreadName(traceEventData) { + return traceEventData.name === 'thread_name'; +} +exports.isThreadName = isThreadName; +function isProcessName(traceEventData) { + return traceEventData.name === 'process_name'; +} +exports.isProcessName = isProcessName; +function isTraceEventTracingStartedInBrowser(traceEventData) { + return traceEventData.name === 'TracingStartedInBrowser'; +} +exports.isTraceEventTracingStartedInBrowser = isTraceEventTracingStartedInBrowser; +function isTraceEventFrameCommittedInBrowser(traceEventData) { + return traceEventData.name === 'FrameCommittedInBrowser'; +} +exports.isTraceEventFrameCommittedInBrowser = isTraceEventFrameCommittedInBrowser; +function isTraceEventCommitLoad(traceEventData) { + return traceEventData.name === 'CommitLoad'; +} +exports.isTraceEventCommitLoad = isTraceEventCommitLoad; +function isTraceEventNavigationStart(traceEventData) { + return traceEventData.name === 'navigationStart'; +} +exports.isTraceEventNavigationStart = isTraceEventNavigationStart; +function isTraceEventAnimation(traceEventData) { + return traceEventData.name === 'Animation'; +} +exports.isTraceEventAnimation = isTraceEventAnimation; +function isTraceEventLayoutShift(traceEventData) { + return traceEventData.name === 'LayoutShift'; +} +exports.isTraceEventLayoutShift = isTraceEventLayoutShift; +function isTraceEventLayoutInvalidation(traceEventData) { + return traceEventData.name === 'LayoutInvalidationTracking' || + traceEventData.name === 'ScheduleStyleInvalidationTracking'; +} +exports.isTraceEventLayoutInvalidation = isTraceEventLayoutInvalidation; +function isTraceEventStyleRecalcInvalidation(traceEventData) { + return traceEventData.name === 'StyleRecalcInvalidationTracking'; +} +exports.isTraceEventStyleRecalcInvalidation = isTraceEventStyleRecalcInvalidation; +function isTraceEventFirstContentfulPaint(traceEventData) { + return traceEventData.name === 'firstContentfulPaint'; +} +exports.isTraceEventFirstContentfulPaint = isTraceEventFirstContentfulPaint; +function isTraceEventLargestContentfulPaintCandidate(traceEventData) { + return traceEventData.name === 'largestContentfulPaint::Candidate'; +} +exports.isTraceEventLargestContentfulPaintCandidate = isTraceEventLargestContentfulPaintCandidate; +function isTraceEventLargestImagePaintCandidate(traceEventData) { + return traceEventData.name === 'LargestImagePaint::Candidate'; +} +exports.isTraceEventLargestImagePaintCandidate = isTraceEventLargestImagePaintCandidate; +function isTraceEventLargestTextPaintCandidate(traceEventData) { + return traceEventData.name === 'LargestTextPaint::Candidate'; +} +exports.isTraceEventLargestTextPaintCandidate = isTraceEventLargestTextPaintCandidate; +function isTraceEventMarkLoad(traceEventData) { + return traceEventData.name === 'MarkLoad'; +} +exports.isTraceEventMarkLoad = isTraceEventMarkLoad; +function isTraceEventFirstPaint(traceEventData) { + return traceEventData.name === 'firstPaint'; +} +exports.isTraceEventFirstPaint = isTraceEventFirstPaint; +function isTraceEventMarkDOMContent(traceEventData) { + return traceEventData.name === 'MarkDOMContent'; +} +exports.isTraceEventMarkDOMContent = isTraceEventMarkDOMContent; +function isTraceEventInteractiveTime(traceEventData) { + return traceEventData.name === 'InteractiveTime'; +} +exports.isTraceEventInteractiveTime = isTraceEventInteractiveTime; +function isTraceEventEventTiming(traceEventData) { + return traceEventData.name === 'EventTiming'; +} +exports.isTraceEventEventTiming = isTraceEventEventTiming; +function isTraceEventEventTimingEnd(traceEventData) { + return isTraceEventEventTiming(traceEventData) && traceEventData.ph === Phase.ASYNC_NESTABLE_END; +} +exports.isTraceEventEventTimingEnd = isTraceEventEventTimingEnd; +function isTraceEventEventTimingStart(traceEventData) { + return isTraceEventEventTiming(traceEventData) && traceEventData.ph === Phase.ASYNC_NESTABLE_START; +} +exports.isTraceEventEventTimingStart = isTraceEventEventTimingStart; +function isTraceEventGPUTask(traceEventData) { + return traceEventData.name === 'GPUTask'; +} +exports.isTraceEventGPUTask = isTraceEventGPUTask; +function isTraceEventProfile(traceEventData) { + return traceEventData.name === 'Profile'; +} +exports.isTraceEventProfile = isTraceEventProfile; +function isTraceEventProfileChunk(traceEventData) { + return traceEventData.name === 'ProfileChunk'; +} +exports.isTraceEventProfileChunk = isTraceEventProfileChunk; +function isTraceEventResourceSendRequest(traceEventData) { + return traceEventData.name === 'ResourceSendRequest'; +} +exports.isTraceEventResourceSendRequest = isTraceEventResourceSendRequest; +function isTraceEventResourceReceiveResponse(traceEventData) { + return traceEventData.name === 'ResourceReceiveResponse'; +} +exports.isTraceEventResourceReceiveResponse = isTraceEventResourceReceiveResponse; +function isTraceEventResourceFinish(traceEventData) { + return traceEventData.name === 'ResourceFinish'; +} +exports.isTraceEventResourceFinish = isTraceEventResourceFinish; +function isTraceEventResourceWillSendRequest(traceEventData) { + return traceEventData.name === 'ResourceWillSendRequest'; +} +exports.isTraceEventResourceWillSendRequest = isTraceEventResourceWillSendRequest; +function isTraceEventResourceReceivedData(traceEventData) { + return traceEventData.name === 'ResourceReceivedData'; +} +exports.isTraceEventResourceReceivedData = isTraceEventResourceReceivedData; +function isSyntheticNetworkRequestDetailsEvent(traceEventData) { + return traceEventData.name === 'SyntheticNetworkRequest'; +} +exports.isSyntheticNetworkRequestDetailsEvent = isSyntheticNetworkRequestDetailsEvent; +function isTraceEventPrePaint(traceEventData) { + return traceEventData.name === 'PrePaint'; +} +exports.isTraceEventPrePaint = isTraceEventPrePaint; +function isTraceEventNavigationStartWithURL(event) { + return Boolean(isTraceEventNavigationStart(event) && event.args.data && event.args.data.documentLoaderURL !== ''); +} +exports.isTraceEventNavigationStartWithURL = isTraceEventNavigationStartWithURL; +function isTraceEventMainFrameViewport(traceEventData) { + return traceEventData.name === 'PaintTimingVisualizer::Viewport'; +} +exports.isTraceEventMainFrameViewport = isTraceEventMainFrameViewport; +function isSyntheticUserTimingTraceEvent(traceEventData) { + if (traceEventData.cat !== 'blink.user_timing') { + return false; + } + const data = traceEventData.args?.data; + if (!data) { + return false; + } + return 'beginEvent' in data && 'endEvent' in data; +} +exports.isSyntheticUserTimingTraceEvent = isSyntheticUserTimingTraceEvent; +function isSyntheticConsoleTimingTraceEvent(traceEventData) { + if (traceEventData.cat !== 'blink.console') { + return false; + } + const data = traceEventData.args?.data; + if (!data) { + return false; + } + return 'beginEvent' in data && 'endEvent' in data; +} +exports.isSyntheticConsoleTimingTraceEvent = isSyntheticConsoleTimingTraceEvent; +function isTraceEventPerformanceMeasure(traceEventData) { + return isTraceEventAsyncPhase(traceEventData) && traceEventData.cat === 'blink.user_timing'; +} +exports.isTraceEventPerformanceMeasure = isTraceEventPerformanceMeasure; +function isTraceEventPerformanceMark(traceEventData) { + return traceEventData.ph === Phase.MARK && traceEventData.cat === 'blink.user_timing'; +} +exports.isTraceEventPerformanceMark = isTraceEventPerformanceMark; +function isTraceEventConsoleTime(traceEventData) { + return isTraceEventAsyncPhase(traceEventData) && traceEventData.cat === 'blink.console'; +} +exports.isTraceEventConsoleTime = isTraceEventConsoleTime; +function isTraceEventTimeStamp(traceEventData) { + return traceEventData.ph === Phase.INSTANT && traceEventData.name === 'TimeStamp'; +} +exports.isTraceEventTimeStamp = isTraceEventTimeStamp; +function isTraceEventAsyncPhase(traceEventData) { + const asyncPhases = new Set([ + Phase.ASYNC_NESTABLE_START, + Phase.ASYNC_NESTABLE_INSTANT, + Phase.ASYNC_NESTABLE_END, + Phase.ASYNC_STEP_INTO, + Phase.ASYNC_BEGIN, + Phase.ASYNC_END, + Phase.ASYNC_STEP_PAST, + ]); + return asyncPhases.has(traceEventData.ph); +} +exports.isTraceEventAsyncPhase = isTraceEventAsyncPhase; +function isSyntheticLayoutShift(traceEventData) { + if (!isTraceEventLayoutShift(traceEventData) || !traceEventData.args.data) { + return false; + } + return 'rawEvent' in traceEventData.args.data; +} +exports.isSyntheticLayoutShift = isSyntheticLayoutShift; + diff --git a/core/lib/cdt/generated/models/trace/types/types.js b/core/lib/cdt/generated/models/trace/types/types.js new file mode 100644 index 000000000000..8539d3e037fe --- /dev/null +++ b/core/lib/cdt/generated/models/trace/types/types.js @@ -0,0 +1,12 @@ +// @ts-nocheck +// generated by yarn build-cdt-lib +/* eslint-disable */ +"use strict"; +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TraceEvents = exports.Timing = void 0; +exports.Timing = require("./Timing.js"); +exports.TraceEvents = require("./TraceEvents.js"); + From eed101305491ffba6883d3cac67a78ca1b7b359c Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Tue, 30 May 2023 15:13:52 -0700 Subject: [PATCH 2/6] core(cumulative-layout-shift): remove old cls --- core/audits/layout-shift-elements.js | 2 +- .../audits/metrics/cumulative-layout-shift.js | 9 +- .../metrics/cumulative-layout-shift.js | 27 +---- core/computed/metrics/timing-summary.js | 12 +- .../audits/__snapshots__/metrics-test.js.snap | 20 ---- core/test/audits/metrics-test.js | 17 +-- .../metrics/cumulative-layout-shift-test.js | 7 -- .../metrics/cumulative-layout-shift-test.js | 110 ++++-------------- .../computed/metrics/timing-summary-test.js | 110 +++++++++--------- .../reports/sample-flow-result.json | 41 +------ core/test/results/sample_v2.json | 15 +-- types/artifacts.d.ts | 2 - 12 files changed, 84 insertions(+), 288 deletions(-) diff --git a/core/audits/layout-shift-elements.js b/core/audits/layout-shift-elements.js index e8181caae0be..b444495e1285 100644 --- a/core/audits/layout-shift-elements.js +++ b/core/audits/layout-shift-elements.js @@ -63,7 +63,7 @@ class LayoutShiftElements extends Audit { {nodeCount: clsElementData.length}); } - const {cumulativeLayoutShift: clsSavings} = + const clsSavings = await CumulativeLayoutShift.request(artifacts.traces[Audit.DEFAULT_PASS], context); return { diff --git a/core/audits/metrics/cumulative-layout-shift.js b/core/audits/metrics/cumulative-layout-shift.js index 3bddd6fe46e7..7898c5d238b8 100644 --- a/core/audits/metrics/cumulative-layout-shift.js +++ b/core/audits/metrics/cumulative-layout-shift.js @@ -54,13 +54,7 @@ class CumulativeLayoutShift extends Audit { */ static async audit(artifacts, context) { const trace = artifacts.traces[Audit.DEFAULT_PASS]; - const {cumulativeLayoutShift, ...rest} = await ComputedCLS.request(trace, context); - - /** @type {LH.Audit.Details.DebugData} */ - const details = { - type: 'debugdata', - items: [rest], - }; + const cumulativeLayoutShift = await ComputedCLS.request(trace, context); return { score: Audit.computeLogNormalScore( @@ -70,7 +64,6 @@ class CumulativeLayoutShift extends Audit { numericValue: cumulativeLayoutShift, numericUnit: 'unitless', displayValue: cumulativeLayoutShift.toLocaleString(context.settings.locale), - details, }; } } diff --git a/core/computed/metrics/cumulative-layout-shift.js b/core/computed/metrics/cumulative-layout-shift.js index 122417a39e61..8ddab2ff74e5 100644 --- a/core/computed/metrics/cumulative-layout-shift.js +++ b/core/computed/metrics/cumulative-layout-shift.js @@ -101,36 +101,15 @@ class CumulativeLayoutShift { return maxScore; } - /** - * Sum all layout shift events from the entire trace. - * @param {Array} layoutShiftEvents - * @return {number} - */ - static calculateTotalCumulativeLayoutShift(layoutShiftEvents) { - return layoutShiftEvents.reduce((sum, e) => sum += e.weightedScore, 0); - } - /** * @param {LH.Trace} trace * @param {LH.Artifacts.ComputedContext} context - * @return {Promise<{cumulativeLayoutShift: number, cumulativeLayoutShiftMainFrame: number, totalCumulativeLayoutShift: number}>} + * @return {Promise} */ static async compute_(trace, context) { const processedTrace = await ProcessedTrace.request(trace, context); - - const allFrameShiftEvents = - CumulativeLayoutShift.getLayoutShiftEvents(processedTrace); - const mainFrameShiftEvents = allFrameShiftEvents.filter(e => e.isMainFrame); - - // The original Cumulative Layout Shift metric, the sum of all main-frame shift events. - const totalCumulativeLayoutShift = - CumulativeLayoutShift.calculateTotalCumulativeLayoutShift(mainFrameShiftEvents); - - return { - cumulativeLayoutShift: CumulativeLayoutShift.calculate(allFrameShiftEvents), - cumulativeLayoutShiftMainFrame: CumulativeLayoutShift.calculate(mainFrameShiftEvents), - totalCumulativeLayoutShift, - }; + const allFrameShiftEvents = CumulativeLayoutShift.getLayoutShiftEvents(processedTrace); + return CumulativeLayoutShift.calculate(allFrameShiftEvents); } } diff --git a/core/computed/metrics/timing-summary.js b/core/computed/metrics/timing-summary.js index bbbfebcd694c..dd422b897807 100644 --- a/core/computed/metrics/timing-summary.js +++ b/core/computed/metrics/timing-summary.js @@ -55,19 +55,13 @@ class TimingSummary { const largestContentfulPaint = await requestOrUndefined(LargestContentfulPaint, metricComputationData); const largestContentfulPaintAllFrames = await requestOrUndefined(LargestContentfulPaintAllFrames, metricComputationData); const interactive = await requestOrUndefined(Interactive, metricComputationData); - const cumulativeLayoutShiftValues = await requestOrUndefined(CumulativeLayoutShift, trace); + const cumulativeLayoutShift = await requestOrUndefined(CumulativeLayoutShift, trace); const maxPotentialFID = await requestOrUndefined(MaxPotentialFID, metricComputationData); const speedIndex = await requestOrUndefined(SpeedIndex, metricComputationData); const totalBlockingTime = await requestOrUndefined(TotalBlockingTime, metricComputationData); const lcpBreakdown = await requestOrUndefined(LCPBreakdown, metricComputationData); const ttfb = await requestOrUndefined(TimeToFirstByte, metricComputationData); - const { - cumulativeLayoutShift, - cumulativeLayoutShiftMainFrame, - totalCumulativeLayoutShift, - } = cumulativeLayoutShiftValues || {}; - /** @type {LH.Artifacts.TimingSummary} */ const metrics = { // Include the simulated/observed performance metrics @@ -88,8 +82,6 @@ class TimingSummary { totalBlockingTime: totalBlockingTime?.timing, maxPotentialFID: maxPotentialFID?.timing, cumulativeLayoutShift, - cumulativeLayoutShiftMainFrame, - totalCumulativeLayoutShift, lcpLoadStart: lcpBreakdown?.loadStart, lcpLoadEnd: lcpBreakdown?.loadEnd, @@ -122,8 +114,6 @@ class TimingSummary { observedDomContentLoaded: processedNavigation?.timings.domContentLoaded, observedDomContentLoadedTs: processedNavigation?.timestamps.domContentLoaded, observedCumulativeLayoutShift: cumulativeLayoutShift, - observedCumulativeLayoutShiftMainFrame: cumulativeLayoutShiftMainFrame, - observedTotalCumulativeLayoutShift: totalCumulativeLayoutShift, // Include some visual metrics from speedline observedFirstVisualChange: speedline.first, diff --git a/core/test/audits/__snapshots__/metrics-test.js.snap b/core/test/audits/__snapshots__/metrics-test.js.snap index 2783540127c9..c035f66a5366 100644 --- a/core/test/audits/__snapshots__/metrics-test.js.snap +++ b/core/test/audits/__snapshots__/metrics-test.js.snap @@ -3,7 +3,6 @@ exports[`Performance: metrics evaluates valid input (with image lcp) correctly 1`] = ` Object { "cumulativeLayoutShift": 0, - "cumulativeLayoutShiftMainFrame": 0, "firstContentfulPaint": 3313, "firstContentfulPaintAllFrames": undefined, "firstContentfulPaintAllFramesTs": undefined, @@ -20,7 +19,6 @@ Object { "lcpLoadStart": 5449, "maxPotentialFID": 160, "observedCumulativeLayoutShift": 0, - "observedCumulativeLayoutShiftMainFrame": 0, "observedDomContentLoaded": 2474, "observedDomContentLoadedTs": 760623117637, "observedFirstContentfulPaint": 2685, @@ -47,7 +45,6 @@ Object { "observedSpeedIndexTs": 760623438456, "observedTimeOrigin": 0, "observedTimeOriginTs": 760620643599, - "observedTotalCumulativeLayoutShift": 0, "observedTraceEnd": 4778, "observedTraceEndTs": 760625421283, "speedIndex": 6313, @@ -55,14 +52,12 @@ Object { "timeToFirstByte": 2394, "timeToFirstByteTs": undefined, "totalBlockingTime": 162, - "totalCumulativeLayoutShift": 0, } `; exports[`Performance: metrics evaluates valid input (with lcp from all frames) correctly 1`] = ` Object { "cumulativeLayoutShift": undefined, - "cumulativeLayoutShiftMainFrame": undefined, "firstContentfulPaint": 863, "firstContentfulPaintAllFrames": 683, "firstContentfulPaintAllFramesTs": 23466705983, @@ -79,7 +74,6 @@ Object { "lcpLoadStart": undefined, "maxPotentialFID": 16, "observedCumulativeLayoutShift": undefined, - "observedCumulativeLayoutShiftMainFrame": undefined, "observedDomContentLoaded": 596, "observedDomContentLoadedTs": 23466619325, "observedFirstContentfulPaint": 863, @@ -106,7 +100,6 @@ Object { "observedSpeedIndexTs": 23467605703, "observedTimeOrigin": 0, "observedTimeOriginTs": 23466023130, - "observedTotalCumulativeLayoutShift": undefined, "observedTraceEnd": 6006, "observedTraceEndTs": 23472029453, "speedIndex": 1583, @@ -114,14 +107,12 @@ Object { "timeToFirstByte": 565, "timeToFirstByteTs": 23466588051, "totalBlockingTime": 0, - "totalCumulativeLayoutShift": undefined, } `; exports[`Performance: metrics evaluates valid input (with lcp) correctly 1`] = ` Object { "cumulativeLayoutShift": 0, - "cumulativeLayoutShiftMainFrame": 0, "firstContentfulPaint": 2291, "firstContentfulPaintAllFrames": undefined, "firstContentfulPaintAllFramesTs": undefined, @@ -138,7 +129,6 @@ Object { "lcpLoadStart": undefined, "maxPotentialFID": 1336, "observedCumulativeLayoutShift": 0, - "observedCumulativeLayoutShiftMainFrame": 0, "observedDomContentLoaded": 1513, "observedDomContentLoadedTs": 713038536140, "observedFirstContentfulPaint": 1122, @@ -165,7 +155,6 @@ Object { "observedSpeedIndexTs": 713038416494, "observedTimeOrigin": 0, "observedTimeOriginTs": 713037023064, - "observedTotalCumulativeLayoutShift": 0, "observedTraceEnd": 7416, "observedTraceEndTs": 713044439102, "speedIndex": 3683, @@ -173,14 +162,12 @@ Object { "timeToFirstByte": 611, "timeToFirstByteTs": undefined, "totalBlockingTime": 1205, - "totalCumulativeLayoutShift": 0, } `; exports[`Performance: metrics evaluates valid input correctly (throttlingMethod=provided) 1`] = ` Object { "cumulativeLayoutShift": 0, - "cumulativeLayoutShiftMainFrame": 0, "firstContentfulPaint": 499, "firstContentfulPaintAllFrames": 499, "firstContentfulPaintAllFramesTs": 225414670885, @@ -197,7 +184,6 @@ Object { "lcpLoadStart": undefined, "maxPotentialFID": 198, "observedCumulativeLayoutShift": 0, - "observedCumulativeLayoutShiftMainFrame": 0, "observedDomContentLoaded": 560, "observedDomContentLoadedTs": 225414732309, "observedFirstContentfulPaint": 499, @@ -224,7 +210,6 @@ Object { "observedSpeedIndexTs": 225414776724, "observedTimeOrigin": 0, "observedTimeOriginTs": 225414172015, - "observedTotalCumulativeLayoutShift": 0, "observedTraceEnd": 12540, "observedTraceEndTs": 225426711887, "speedIndex": 605, @@ -232,14 +217,12 @@ Object { "timeToFirstByte": 261, "timeToFirstByteTs": 225414432704, "totalBlockingTime": 48, - "totalCumulativeLayoutShift": 0, } `; exports[`Performance: metrics evaluates valid input correctly 1`] = ` Object { "cumulativeLayoutShift": 0, - "cumulativeLayoutShiftMainFrame": 0, "firstContentfulPaint": 1337, "firstContentfulPaintAllFrames": undefined, "firstContentfulPaintAllFramesTs": undefined, @@ -256,7 +239,6 @@ Object { "lcpLoadStart": undefined, "maxPotentialFID": 396, "observedCumulativeLayoutShift": 0, - "observedCumulativeLayoutShiftMainFrame": 0, "observedDomContentLoaded": 560, "observedDomContentLoadedTs": 225414732309, "observedFirstContentfulPaint": 499, @@ -283,7 +265,6 @@ Object { "observedSpeedIndexTs": 225414776724, "observedTimeOrigin": 0, "observedTimeOriginTs": 225414172015, - "observedTotalCumulativeLayoutShift": 0, "observedTraceEnd": 12540, "observedTraceEndTs": 225426711887, "speedIndex": 1676, @@ -291,6 +272,5 @@ Object { "timeToFirstByte": 760, "timeToFirstByteTs": undefined, "totalBlockingTime": 777, - "totalCumulativeLayoutShift": 0, } `; diff --git a/core/test/audits/metrics-test.js b/core/test/audits/metrics-test.js index e44d42ae7342..23a6615a0ffe 100644 --- a/core/test/audits/metrics-test.js +++ b/core/test/audits/metrics-test.js @@ -150,15 +150,11 @@ describe('Performance: metrics', () => { const {details} = await MetricsAudit.audit(artifacts, context); expect(details.items[0]).toMatchObject({ cumulativeLayoutShift: undefined, - cumulativeLayoutShiftMainFrame: undefined, - totalCumulativeLayoutShift: undefined, observedCumulativeLayoutShift: undefined, - observedCumulativeLayoutShiftMainFrame: undefined, - observedTotalCumulativeLayoutShift: undefined, }); }); - it('evaluates new CLS correctly across all frames', async () => { + it('evaluates CLS correctly across all frames', async () => { const URL = getURLArtifactFromDevtoolsLog(clsAllFramesDevtoolsLog); const artifacts = { URL, @@ -177,15 +173,9 @@ describe('Performance: metrics', () => { }; const {details} = await MetricsAudit.audit(artifacts, context); - // Only a single main-frame shift event, so mfCls and oldCls are equal. expect(details.items[0]).toMatchObject({ cumulativeLayoutShift: expect.toBeApproximately(0.026463, 6), - cumulativeLayoutShiftMainFrame: expect.toBeApproximately(0.001166, 6), - totalCumulativeLayoutShift: expect.toBeApproximately(0.001166, 6), - observedCumulativeLayoutShift: expect.toBeApproximately(0.026463, 6), - observedCumulativeLayoutShiftMainFrame: expect.toBeApproximately(0.001166, 6), - observedTotalCumulativeLayoutShift: expect.toBeApproximately(0.001166, 6), }); }); @@ -232,12 +222,7 @@ describe('Performance: metrics', () => { const {details} = await MetricsAudit.audit(artifacts, context); expect(details.items[0]).toMatchObject({ cumulativeLayoutShift: expect.toBeApproximately(2.268816, 6), - cumulativeLayoutShiftMainFrame: expect.toBeApproximately(2.268816, 6), - totalCumulativeLayoutShift: expect.toBeApproximately(4.809794, 6), - observedCumulativeLayoutShift: expect.toBeApproximately(2.268816, 6), - observedCumulativeLayoutShiftMainFrame: expect.toBeApproximately(2.268816, 6), - observedTotalCumulativeLayoutShift: expect.toBeApproximately(4.809794, 6), }); }); }); diff --git a/core/test/audits/metrics/cumulative-layout-shift-test.js b/core/test/audits/metrics/cumulative-layout-shift-test.js index 92f32ba65868..c9e6b1227b4e 100644 --- a/core/test/audits/metrics/cumulative-layout-shift-test.js +++ b/core/test/audits/metrics/cumulative-layout-shift-test.js @@ -28,13 +28,6 @@ describe('Cumulative Layout Shift', () => { score: 0, numericValue: expect.toBeApproximately(2.268816, 6), numericUnit: 'unitless', - details: { - type: 'debugdata', - items: [{ - cumulativeLayoutShiftMainFrame: expect.toBeApproximately(2.268816, 6), - totalCumulativeLayoutShift: expect.toBeApproximately(4.809794, 6), - }], - }, }); }); }); diff --git a/core/test/computed/metrics/cumulative-layout-shift-test.js b/core/test/computed/metrics/cumulative-layout-shift-test.js index 230d18cdf337..53ce4c313a2f 100644 --- a/core/test/computed/metrics/cumulative-layout-shift-test.js +++ b/core/test/computed/metrics/cumulative-layout-shift-test.js @@ -23,11 +23,7 @@ describe('Metrics: CLS', () => { describe('real traces', () => { it('calculates (all main frame) CLS for a trace', async () => { const result = await CumulativeLayoutShift.request(jumpyClsTrace, context); - expect(result).toEqual({ - cumulativeLayoutShift: expect.toBeApproximately(2.268816, 6), - cumulativeLayoutShiftMainFrame: expect.toBeApproximately(2.268816, 6), - totalCumulativeLayoutShift: expect.toBeApproximately(4.809794, 6), - }); + expect(result).toBeApproximately(2.268816, 6); }); it('throws if layout shift events are found without weighted_score_delta', async () => { @@ -37,20 +33,12 @@ describe('Metrics: CLS', () => { it('calculates CLS values for a trace with CLS events over more than one frame', async () => { const result = await CumulativeLayoutShift.request(allFramesMetricsTrace, context); - expect(result).toEqual({ - cumulativeLayoutShift: 0.026463014612806653, - cumulativeLayoutShiftMainFrame: 0.0011656245471340055, - totalCumulativeLayoutShift: 0.0011656245471340055, - }); + expect(result).toEqual(0.026463014612806653); }); it('returns 0 for a trace with no CLS events', async () => { const result = await CumulativeLayoutShift.request(preClsTrace, context); - expect(result).toEqual({ - cumulativeLayoutShift: 0, - cumulativeLayoutShiftMainFrame: 0, - totalCumulativeLayoutShift: 0, - }); + expect(result).toEqual(0); }); }); @@ -120,11 +108,7 @@ describe('Metrics: CLS', () => { {score: 1, ts: 4, had_recent_input: false}, ]); const result = await CumulativeLayoutShift.request(trace, context); - expect(result).toEqual({ - cumulativeLayoutShift: 4, - cumulativeLayoutShiftMainFrame: 4, - totalCumulativeLayoutShift: 4, - }); + expect(result).toEqual(4); }); it('should not count later shift events if input it true', async () => { @@ -137,11 +121,7 @@ describe('Metrics: CLS', () => { {score: 1, ts: 5, had_recent_input: true}, ]); const result = await CumulativeLayoutShift.request(trace, context); - expect(result).toEqual({ - cumulativeLayoutShift: 3, - cumulativeLayoutShiftMainFrame: 3, - totalCumulativeLayoutShift: 3, - }); + expect(result).toEqual(3); }); it('calculates from a uniform distribution of layout shift events', async () => { @@ -155,11 +135,7 @@ describe('Metrics: CLS', () => { const trace = makeTrace(shiftEvents); const result = await CumulativeLayoutShift.request(trace, context); - expect(result).toEqual({ - cumulativeLayoutShift: 0.75, - cumulativeLayoutShiftMainFrame: 0.75, - totalCumulativeLayoutShift: 3.75, // 30 * 0.125 - }); + expect(result).toEqual(0.75); }); it('calculates from three clusters of layout shift events', async () => { @@ -180,11 +156,7 @@ describe('Metrics: CLS', () => { const trace = makeTrace(shiftEvents); const result = await CumulativeLayoutShift.request(trace, context); - expect(result).toEqual({ - cumulativeLayoutShift: 1.0625, - cumulativeLayoutShiftMainFrame: 1.0625, - totalCumulativeLayoutShift: 1.375, - }); + expect(result).toEqual(1.0625); }); it('calculates the same LS score from a tiny extra small cluster of events', async () => { @@ -198,11 +170,8 @@ describe('Metrics: CLS', () => { const trace = makeTrace(shiftEvents); const result = await CumulativeLayoutShift.request(trace, context); - expect(result).toEqual({ - cumulativeLayoutShift: 3.75, // 30 * 0.125 - cumulativeLayoutShiftMainFrame: 3.75, - totalCumulativeLayoutShift: 3.75, - }); + // 30 * 0.125 + expect(result).toEqual(3.75); }); it('includes events with recent input at start of trace, but ignores others', async () => { @@ -222,11 +191,7 @@ describe('Metrics: CLS', () => { const trace = makeTrace(shiftEvents); const result = await CumulativeLayoutShift.request(trace, context); - expect(result).toEqual({ - cumulativeLayoutShift: 3, - cumulativeLayoutShiftMainFrame: 3, - totalCumulativeLayoutShift: 3, - }); + expect(result).toEqual(3); }); }); @@ -243,11 +208,8 @@ describe('Metrics: CLS', () => { const trace = makeTrace(shiftEvents); const result = await CumulativeLayoutShift.request(trace, context); - expect(result).toEqual({ - cumulativeLayoutShift: 0.75, // Same value as single-frame uniformly distributed. - cumulativeLayoutShiftMainFrame: 0.125, // All 1s gaps, so only one event per cluster. - totalCumulativeLayoutShift: 1.875, // 0.125 * 15 - }); + // Same value as single-frame uniformly distributed. + expect(result).toEqual(0.75); }); it('includes events with recent input at start of trace, but ignores others', async () => { @@ -272,11 +234,7 @@ describe('Metrics: CLS', () => { const trace = makeTrace(shiftEvents); const result = await CumulativeLayoutShift.request(trace, context); - expect(result).toMatchObject({ - cumulativeLayoutShift: 4, - cumulativeLayoutShiftMainFrame: 2, - totalCumulativeLayoutShift: 2, - }); + expect(result).toEqual(4); }); it('includes recent input events near first viewport event, but ignores others', async () => { @@ -327,11 +285,7 @@ describe('Metrics: CLS', () => { }); const result = await CumulativeLayoutShift.request(trace, context); - expect(result).toMatchObject({ - cumulativeLayoutShift: 5, - cumulativeLayoutShiftMainFrame: 3, - totalCumulativeLayoutShift: 3, - }); + expect(result).toEqual(5); }); it('uses layout shift score weighted by frame size', async () => { @@ -343,11 +297,7 @@ describe('Metrics: CLS', () => { const trace = makeTrace(shiftEvents); const result = await CumulativeLayoutShift.request(trace, context); - expect(result).toMatchObject({ - cumulativeLayoutShift: 4, - cumulativeLayoutShiftMainFrame: 2, - totalCumulativeLayoutShift: 2, - }); + expect(result).toEqual(4); }); it('ignores layout shift data from other tabs', async () => { @@ -372,11 +322,7 @@ describe('Metrics: CLS', () => { /* eslint-enable max-len */ ); const result = await CumulativeLayoutShift.request(trace, context); - expect(result).toMatchObject({ - cumulativeLayoutShift: 3, - cumulativeLayoutShiftMainFrame: 1, - totalCumulativeLayoutShift: 1, - }); + expect(result).toEqual(3); }); }); @@ -392,11 +338,7 @@ describe('Metrics: CLS', () => { ]; const trace = makeTrace(shiftEvents); const result = await CumulativeLayoutShift.request(trace, context); - expect(result).toMatchObject({ - cumulativeLayoutShift: 6, - cumulativeLayoutShiftMainFrame: 6, - totalCumulativeLayoutShift: 6, - }); + expect(result).toEqual(6); }); it('counts gaps > 1s and limits cluster length to <= 5s (multiple frames)', async () => { @@ -411,11 +353,7 @@ describe('Metrics: CLS', () => { ]; const trace = makeTrace(shiftEvents); const result = await CumulativeLayoutShift.request(trace, context); - expect(result).toMatchObject({ - cumulativeLayoutShift: 6, - cumulativeLayoutShiftMainFrame: 1, - totalCumulativeLayoutShift: 4, - }); + expect(result).toEqual(6); }); it('only counts gaps > 1s', async () => { @@ -425,11 +363,7 @@ describe('Metrics: CLS', () => { ]; const trace = makeTrace(shiftEvents); const result = await CumulativeLayoutShift.request(trace, context); - expect(result).toMatchObject({ - cumulativeLayoutShift: 2, - cumulativeLayoutShiftMainFrame: 2, - totalCumulativeLayoutShift: 2, - }); + expect(result).toEqual(2); }); it('only counts gaps > 1s (multiple frames)', async () => { @@ -439,11 +373,7 @@ describe('Metrics: CLS', () => { ]; const trace = makeTrace(shiftEvents); const result = await CumulativeLayoutShift.request(trace, context); - expect(result).toMatchObject({ - cumulativeLayoutShift: 2, - cumulativeLayoutShiftMainFrame: 1, - totalCumulativeLayoutShift: 1, - }); + expect(result).toEqual(2); }); }); }); diff --git a/core/test/computed/metrics/timing-summary-test.js b/core/test/computed/metrics/timing-summary-test.js index 8ceb358ed6e2..3552d4f3f43b 100644 --- a/core/test/computed/metrics/timing-summary-test.js +++ b/core/test/computed/metrics/timing-summary-test.js @@ -20,63 +20,59 @@ describe('Timing summary', () => { const result = await TimingSummary.request(artifacts, context); expect(result.metrics).toMatchInlineSnapshot(` - Object { - "cumulativeLayoutShift": 0.026463014612806653, - "cumulativeLayoutShiftMainFrame": 0.0011656245471340055, - "firstContentfulPaint": 5668.275, - "firstContentfulPaintAllFrames": 697.751, - "firstContentfulPaintAllFramesTs": 10327885660, - "firstContentfulPaintTs": 10332856184, - "firstMeaningfulPaint": 669.212, - "firstMeaningfulPaintTs": 10327857121, - "interactive": 8654.264, - "interactiveTs": 10335842173, - "largestContentfulPaint": 5668.275, - "largestContentfulPaintAllFrames": 697.751, - "largestContentfulPaintAllFramesTs": 10327885660, - "largestContentfulPaintTs": 10332856184, - "lcpLoadEnd": undefined, - "lcpLoadStart": undefined, - "maxPotentialFID": 51.056, - "observedCumulativeLayoutShift": 0.026463014612806653, - "observedCumulativeLayoutShiftMainFrame": 0.0011656245471340055, - "observedDomContentLoaded": 604.135, - "observedDomContentLoadedTs": 10327792044, - "observedFirstContentfulPaint": 5668.275, - "observedFirstContentfulPaintAllFrames": 697.751, - "observedFirstContentfulPaintAllFramesTs": 10327885660, - "observedFirstContentfulPaintTs": 10332856184, - "observedFirstMeaningfulPaint": 669.212, - "observedFirstMeaningfulPaintTs": 10327857121, - "observedFirstPaint": 669.212, - "observedFirstPaintTs": 10327857121, - "observedFirstVisualChange": 673, - "observedFirstVisualChangeTs": 10327860909, - "observedLargestContentfulPaint": 5668.275, - "observedLargestContentfulPaintAllFrames": 697.751, - "observedLargestContentfulPaintAllFramesTs": 10327885660, - "observedLargestContentfulPaintTs": 10332856184, - "observedLastVisualChange": 5711, - "observedLastVisualChangeTs": 10332898909, - "observedLoad": 688.184, - "observedLoadTs": 10327876093, - "observedNavigationStart": 0, - "observedNavigationStartTs": 10327187909, - "observedSpeedIndex": 1334.5801200005412, - "observedSpeedIndexTs": 10328522489.12, - "observedTimeOrigin": 0, - "observedTimeOriginTs": 10327187909, - "observedTotalCumulativeLayoutShift": 0.0011656245471340055, - "observedTraceEnd": 14214.313, - "observedTraceEndTs": 10341402222, - "speedIndex": 1335, - "speedIndexTs": 10328522909, - "timeToFirstByte": 570.329, - "timeToFirstByteTs": 10327758238, - "totalBlockingTime": 2.7429999999994834, - "totalCumulativeLayoutShift": 0.0011656245471340055, - } - `); +Object { + "cumulativeLayoutShift": 0.026463014612806653, + "firstContentfulPaint": 5668.275, + "firstContentfulPaintAllFrames": 697.751, + "firstContentfulPaintAllFramesTs": 10327885660, + "firstContentfulPaintTs": 10332856184, + "firstMeaningfulPaint": 669.212, + "firstMeaningfulPaintTs": 10327857121, + "interactive": 8654.264, + "interactiveTs": 10335842173, + "largestContentfulPaint": 5668.275, + "largestContentfulPaintAllFrames": 697.751, + "largestContentfulPaintAllFramesTs": 10327885660, + "largestContentfulPaintTs": 10332856184, + "lcpLoadEnd": undefined, + "lcpLoadStart": undefined, + "maxPotentialFID": 51.056, + "observedCumulativeLayoutShift": 0.026463014612806653, + "observedDomContentLoaded": 604.135, + "observedDomContentLoadedTs": 10327792044, + "observedFirstContentfulPaint": 5668.275, + "observedFirstContentfulPaintAllFrames": 697.751, + "observedFirstContentfulPaintAllFramesTs": 10327885660, + "observedFirstContentfulPaintTs": 10332856184, + "observedFirstMeaningfulPaint": 669.212, + "observedFirstMeaningfulPaintTs": 10327857121, + "observedFirstPaint": 669.212, + "observedFirstPaintTs": 10327857121, + "observedFirstVisualChange": 673, + "observedFirstVisualChangeTs": 10327860909, + "observedLargestContentfulPaint": 5668.275, + "observedLargestContentfulPaintAllFrames": 697.751, + "observedLargestContentfulPaintAllFramesTs": 10327885660, + "observedLargestContentfulPaintTs": 10332856184, + "observedLastVisualChange": 5711, + "observedLastVisualChangeTs": 10332898909, + "observedLoad": 688.184, + "observedLoadTs": 10327876093, + "observedNavigationStart": 0, + "observedNavigationStartTs": 10327187909, + "observedSpeedIndex": 1334.5801200005412, + "observedSpeedIndexTs": 10328522489.12, + "observedTimeOrigin": 0, + "observedTimeOriginTs": 10327187909, + "observedTraceEnd": 14214.313, + "observedTraceEndTs": 10341402222, + "speedIndex": 1335, + "speedIndexTs": 10328522909, + "timeToFirstByte": 570.329, + "timeToFirstByteTs": 10327758238, + "totalBlockingTime": 2.7429999999994834, +} +`); // Includes performance metrics expect(result.metrics.firstContentfulPaint).toBeDefined(); // Includes timestamps from the processed trace diff --git a/core/test/fixtures/fraggle-rock/reports/sample-flow-result.json b/core/test/fixtures/fraggle-rock/reports/sample-flow-result.json index 9d0b627138bb..caee9a7c23fc 100644 --- a/core/test/fixtures/fraggle-rock/reports/sample-flow-result.json +++ b/core/test/fixtures/fraggle-rock/reports/sample-flow-result.json @@ -181,16 +181,7 @@ "scoreDisplayMode": "numeric", "numericValue": 0.002631263732910156, "numericUnit": "unitless", - "displayValue": "0.003", - "details": { - "type": "debugdata", - "items": [ - { - "cumulativeLayoutShiftMainFrame": 0.002631263732910156, - "totalCumulativeLayoutShift": 0.002631263732910156 - } - ] - } + "displayValue": "0.003" }, "errors-in-console": { "id": "errors-in-console", @@ -1294,8 +1285,6 @@ "totalBlockingTime": 143, "maxPotentialFID": 193, "cumulativeLayoutShift": 0.002631263732910156, - "cumulativeLayoutShiftMainFrame": 0.002631263732910156, - "totalCumulativeLayoutShift": 0.002631263732910156, "lcpLoadStart": 985, "lcpLoadEnd": 1062, "timeToFirstByte": 615, @@ -1322,8 +1311,6 @@ "observedDomContentLoaded": 255, "observedDomContentLoadedTs": 183713237485, "observedCumulativeLayoutShift": 0.002631263732910156, - "observedCumulativeLayoutShiftMainFrame": 0.002631263732910156, - "observedTotalCumulativeLayoutShift": 0.002631263732910156, "observedFirstVisualChange": 341, "observedFirstVisualChangeTs": 183713323617, "observedLastVisualChange": 575, @@ -8043,16 +8030,7 @@ "scoreDisplayMode": "numeric", "numericValue": 0.13125, "numericUnit": "unitless", - "displayValue": "0.131", - "details": { - "type": "debugdata", - "items": [ - { - "cumulativeLayoutShiftMainFrame": 0.13125, - "totalCumulativeLayoutShift": 0.13125 - } - ] - } + "displayValue": "0.131" }, "experimental-interaction-to-next-paint": { "id": "experimental-interaction-to-next-paint", @@ -16865,16 +16843,7 @@ "scoreDisplayMode": "numeric", "numericValue": 0, "numericUnit": "unitless", - "displayValue": "0", - "details": { - "type": "debugdata", - "items": [ - { - "cumulativeLayoutShiftMainFrame": 0, - "totalCumulativeLayoutShift": 0 - } - ] - } + "displayValue": "0" }, "errors-in-console": { "id": "errors-in-console", @@ -17944,8 +17913,6 @@ "totalBlockingTime": 13, "maxPotentialFID": 76, "cumulativeLayoutShift": 0, - "cumulativeLayoutShiftMainFrame": 0, - "totalCumulativeLayoutShift": 0, "lcpLoadStart": 603, "lcpLoadEnd": 837, "timeToFirstByte": 603, @@ -17972,8 +17939,6 @@ "observedDomContentLoaded": 45, "observedDomContentLoadedTs": 183729805229, "observedCumulativeLayoutShift": 0, - "observedCumulativeLayoutShiftMainFrame": 0, - "observedTotalCumulativeLayoutShift": 0, "observedFirstVisualChange": 89, "observedFirstVisualChangeTs": 183729849318, "observedLastVisualChange": 505, diff --git a/core/test/results/sample_v2.json b/core/test/results/sample_v2.json index 73efc6dccfd6..f55389c3b8ad 100644 --- a/core/test/results/sample_v2.json +++ b/core/test/results/sample_v2.json @@ -195,16 +195,7 @@ "scoreDisplayMode": "numeric", "numericValue": 0.13570762803819444, "numericUnit": "unitless", - "displayValue": "0.136", - "details": { - "type": "debugdata", - "items": [ - { - "cumulativeLayoutShiftMainFrame": 0.13570762803819444, - "totalCumulativeLayoutShift": 0.13570762803819444 - } - ] - } + "displayValue": "0.136" }, "errors-in-console": { "id": "errors-in-console", @@ -1915,8 +1906,6 @@ "totalBlockingTime": 1221, "maxPotentialFID": 1175, "cumulativeLayoutShift": 0.13570762803819444, - "cumulativeLayoutShiftMainFrame": 0.13570762803819444, - "totalCumulativeLayoutShift": 0.13570762803819444, "lcpLoadStart": 10849, "lcpLoadEnd": 12900, "timeToFirstByte": 572, @@ -1944,8 +1933,6 @@ "observedDomContentLoaded": 8186, "observedDomContentLoadedTs": 8704886496, "observedCumulativeLayoutShift": 0.13570762803819444, - "observedCumulativeLayoutShiftMainFrame": 0.13570762803819444, - "observedTotalCumulativeLayoutShift": 0.13570762803819444, "observedFirstVisualChange": 8058, "observedFirstVisualChangeTs": 8704758822, "observedLastVisualChange": 8893, diff --git a/types/artifacts.d.ts b/types/artifacts.d.ts index c293c9b48f52..7ceede6bd538 100644 --- a/types/artifacts.d.ts +++ b/types/artifacts.d.ts @@ -799,8 +799,6 @@ declare module Artifacts { speedIndexTs: number | undefined; maxPotentialFID: number | undefined; cumulativeLayoutShift: number | undefined; - cumulativeLayoutShiftMainFrame: number | undefined; - totalCumulativeLayoutShift: number | undefined; totalBlockingTime: number | undefined; observedTimeOrigin: number; observedTimeOriginTs: number; From 417f613806a770f02ba97f1fc3f7de3e85f00889 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Thu, 6 Jul 2023 11:06:58 -0700 Subject: [PATCH 3/6] fix types --- build/build-cdt-lib.js | 24 ++++++++++--------- .../metrics/cumulative-layout-shift.js | 4 +++- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/build/build-cdt-lib.js b/build/build-cdt-lib.js index 3cd4fc4497a5..9248e2970814 100644 --- a/build/build-cdt-lib.js +++ b/build/build-cdt-lib.js @@ -25,10 +25,12 @@ import {LH_ROOT} from '../root.js'; const outDir = `${LH_ROOT}/core/lib/cdt/generated`; +const frontEndPath = 'node_modules/chrome-devtools-frontend/front_end'; + /** @type {Modification[]} */ const modifications = [ { - input: 'node_modules/chrome-devtools-frontend/front_end/core/sdk/SourceMap.ts', + input: `${frontEndPath}/core/sdk/SourceMap.ts`, output: `${outDir}/SourceMap.js`, template: [ 'const Common = require(\'../Common.js\');', @@ -62,7 +64,7 @@ const modifications = [ ], }, { - input: 'node_modules/chrome-devtools-frontend/front_end/core/common/ParsedURL.ts', + input: `${frontEndPath}/core/common/ParsedURL.ts`, output: `${outDir}/ParsedURL.js`, template: '%sourceFilePrinted%', rawCodeToReplace: {}, @@ -114,7 +116,7 @@ const modifications = [ ], }, { - input: 'node_modules/chrome-devtools-frontend/front_end/models/trace/types/types.ts', + input: `${frontEndPath}/models/trace/types/types.ts`, output: `${outDir}/models/trace/types/types.js`, template: '%sourceFilePrinted%', rawCodeToReplace: {}, @@ -123,7 +125,7 @@ const modifications = [ variablesToRemove: [], }, { - input: 'node_modules/chrome-devtools-frontend/front_end/models/trace/types/TraceEvents.ts', + input: `${frontEndPath}/models/trace/types/TraceEvents.ts`, output: `${outDir}/models/trace/types/TraceEvents.js`, template: '%sourceFilePrinted%', rawCodeToReplace: {}, @@ -132,7 +134,7 @@ const modifications = [ variablesToRemove: [], }, { - input: 'node_modules/chrome-devtools-frontend/front_end/models/trace/types/Timing.ts', + input: `${frontEndPath}/models/trace/types/Timing.ts`, output: `${outDir}/models/trace/types/Timing.js`, template: '%sourceFilePrinted%', rawCodeToReplace: {}, @@ -141,7 +143,7 @@ const modifications = [ variablesToRemove: [], }, { - input: 'node_modules/chrome-devtools-frontend/front_end/models/trace/helpers/helpers.ts', + input: `${frontEndPath}/models/trace/helpers/helpers.ts`, output: `${outDir}/models/trace/helpers/helpers.js`, template: '%sourceFilePrinted%', rawCodeToReplace: {}, @@ -150,7 +152,7 @@ const modifications = [ variablesToRemove: [], }, { - input: 'node_modules/chrome-devtools-frontend/front_end/models/trace/helpers/Timing.ts', + input: `${frontEndPath}/models/trace/helpers/Timing.ts`, output: `${outDir}/models/trace/helpers/Timing.js`, template: [ 'const Platform = require(\'../../../../Platform.js\');', @@ -166,7 +168,7 @@ const modifications = [ ], }, { - input: 'node_modules/chrome-devtools-frontend/front_end/models/trace/helpers/Trace.ts', + input: `${frontEndPath}/models/trace/helpers/Trace.ts`, output: `${outDir}/models/trace/helpers/Trace.js`, template: [ 'const Platform = require(\'../../../../Platform.js\');', @@ -182,7 +184,7 @@ const modifications = [ ], }, { - input: 'node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/types.ts', + input: `${frontEndPath}/models/trace/handlers/types.ts`, output: `${outDir}/models/trace/handlers/types.js`, template: '%sourceFilePrinted%', rawCodeToReplace: {}, @@ -191,7 +193,7 @@ const modifications = [ variablesToRemove: [], }, { - input: 'node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/MetaHandler.ts', + input: `${frontEndPath}/models/trace/handlers/MetaHandler.ts`, output: `${outDir}/models/trace/handlers/MetaHandler.js`, template: [ 'const Platform = require(\'../../../../Platform.js\');', @@ -209,7 +211,7 @@ const modifications = [ ], }, { - input: 'node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/LayoutShiftsHandler.ts', + input: `${frontEndPath}/models/trace/handlers/LayoutShiftsHandler.ts`, output: `${outDir}/models/trace/handlers/LayoutShiftsHandler.js`, template: [ 'const Platform = require(\'../../../../Platform.js\');', diff --git a/core/computed/metrics/cumulative-layout-shift.js b/core/computed/metrics/cumulative-layout-shift.js index b9e9bae69e05..be41de17a3bc 100644 --- a/core/computed/metrics/cumulative-layout-shift.js +++ b/core/computed/metrics/cumulative-layout-shift.js @@ -118,6 +118,7 @@ class CumulativeLayoutShift { let cumulativeLayoutShift; try { + // Experimental usage of CDT's trace processor. const processor = new SDK.TraceProcessor.TraceProcessor({ LayoutShifts: SDK.TraceHandlers.LayoutShiftsHandler, }); @@ -132,9 +133,10 @@ class CumulativeLayoutShift { return allFrameShiftEvents.some(lse => lse.event === event); }); await processor.parse(filteredTrace); - const data = processor.data; + const data = /** @type {{LayoutShifts: {sessionMaxScore: number}}} */ (processor.data); cumulativeLayoutShift = data.LayoutShifts.sessionMaxScore; } catch (e) { + // Something failed, so fallback to our own implementation. log.error('Error running SDK.TraceProcessor', e); cumulativeLayoutShift = CumulativeLayoutShift.calculate(allFrameShiftEvents); } From e52106a51bc7da95a62879939a94e1dc632e29e0 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Thu, 6 Jul 2023 11:14:21 -0700 Subject: [PATCH 4/6] refactor typing --- core/computed/metrics/cumulative-layout-shift.js | 3 ++- core/lib/cdt/SDK.js | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/core/computed/metrics/cumulative-layout-shift.js b/core/computed/metrics/cumulative-layout-shift.js index be41de17a3bc..4b1e144a78ed 100644 --- a/core/computed/metrics/cumulative-layout-shift.js +++ b/core/computed/metrics/cumulative-layout-shift.js @@ -133,7 +133,8 @@ class CumulativeLayoutShift { return allFrameShiftEvents.some(lse => lse.event === event); }); await processor.parse(filteredTrace); - const data = /** @type {{LayoutShifts: {sessionMaxScore: number}}} */ (processor.data); + const data = /** @type {import('../../lib/cdt/SDK.js').TraceProcessorResult} */ ( + processor.data); cumulativeLayoutShift = data.LayoutShifts.sessionMaxScore; } catch (e) { // Something failed, so fallback to our own implementation. diff --git a/core/lib/cdt/SDK.js b/core/lib/cdt/SDK.js index 4a277c364d04..3215fa3527fd 100644 --- a/core/lib/cdt/SDK.js +++ b/core/lib/cdt/SDK.js @@ -10,6 +10,9 @@ const SDK = { TraceHandlers: require('./generated/models/trace/handlers/handlers.js').ModelHandlers, }; +/** @typedef {{sessionMaxScore: number}} LayoutShiftsHandler */ +/** @typedef {{LayoutShifts: LayoutShiftsHandler}} TraceProcessorResult */ + // Add `lastColumnNumber` to mappings. This will eventually be added to CDT. // @ts-expect-error SDK.SourceMap.prototype.computeLastGeneratedColumns = function() { From 4ee6efb1c1604346854768f15b2d8ba2e3646b7c Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Thu, 6 Jul 2023 11:15:24 -0700 Subject: [PATCH 5/6] edit --- build/build-cdt-lib.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/build/build-cdt-lib.js b/build/build-cdt-lib.js index 9248e2970814..294d22775ed6 100644 --- a/build/build-cdt-lib.js +++ b/build/build-cdt-lib.js @@ -203,9 +203,7 @@ const modifications = [ 'new DOMRect(viewportX, viewportY, viewportWidth, viewportHeight)': 'null', }, classesToRemove: [], - methodsToRemove: [ - // 'findNextScreenshotSource', - ], + methodsToRemove: [], variablesToRemove: [ 'Platform', ], From 678c5ac24e63921c6a606e6dfa4b8bf2fb990bca Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Thu, 6 Jul 2023 11:58:16 -0700 Subject: [PATCH 6/6] fix clean types --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9ac33e55cd5a..ac2e2e41c4ca 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "reset-link": "(yarn unlink || true) && yarn link && yarn link lighthouse", "c8": "bash core/scripts/c8.sh", "clean": "rm -r dist proto/scripts/*.json proto/scripts/*_pb2.* proto/scripts/*_pb.* proto/scripts/__pycache__ proto/scripts/*.pyc *.report.html *.report.dom.html *.report.json *.devtoolslog.json *.trace.json shared/localization/locales/*.ctc.json || true", - "clean-types": "git clean -xfq '*.d.ts' '*.d.cts' -e 'node_modules/' -e 'dist/' -e '.tmp/' -e '**/types/'", + "clean-types": "git clean -xfq '*.d.ts' '*.d.cts' -e 'node_modules/' -e 'dist/' -e '.tmp/' -e '*/types/'", "lint": "[ \"$CI\" = true ] && eslint --quiet -f codeframe . || eslint .", "smoke": "node cli/test/smokehouse/frontends/smokehouse-bin.js", "debug": "node --inspect-brk ./cli/index.js",