From 48be37a05934ec8bfe47cd3be702e69d84a3961d Mon Sep 17 00:00:00 2001 From: Patrick Hulce Date: Fri, 16 Aug 2019 10:20:39 -0500 Subject: [PATCH] core(third-party-summary): add blocking time impact (#9486) --- lighthouse-core/audits/third-party-summary.js | 62 +++++++++++-------- lighthouse-core/lib/i18n/locales/en-US.json | 11 ++-- lighthouse-core/lib/i18n/locales/en-XL.json | 11 ++-- .../test/audits/third-party-summary-test.js | 10 ++- lighthouse-core/test/results/sample_v2.json | 22 ++++--- proto/sample_v2_round_trip.json | 15 ++--- 6 files changed, 79 insertions(+), 52 deletions(-) diff --git a/lighthouse-core/audits/third-party-summary.js b/lighthouse-core/audits/third-party-summary.js index e63ed31124b4..9aac6184e12c 100644 --- a/lighthouse-core/audits/third-party-summary.js +++ b/lighthouse-core/audits/third-party-summary.js @@ -14,25 +14,28 @@ const NetworkRecords = require('../computed/network-records.js'); const MainThreadTasks = require('../computed/main-thread-tasks.js'); const UIStrings = { - /** Title of a Lighthouse audit that identifies the code on the page that the user doesn't control. This is shown in a list of audits that Lighthouse generates. */ - title: 'Third-Party Usage', + /** Title of a diagnostic audit that provides details about the code on a web page that the user doesn't control (referred to as "third-party code"). This descriptive title is shown to users when the amount is acceptable and no user action is required. */ + title: 'Third-Party usage', + /** Title of a diagnostic audit that provides details about the code on a web page that the user doesn't control (referred to as "third-party code"). This imperative title is shown to users when there is a significant amount of page execution time caused by third-party code that should be reduced. */ + failureTitle: 'Reduce the impact of third-party code', /** Description of a Lighthouse audit that identifies the code on the page that the user doesn't control. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation. */ description: 'Third-party code can significantly impact load performance. ' + 'Limit the number of redundant third-party providers and try to load third-party code after ' + 'your page has primarily finished loading. [Learn more](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/loading-third-party-javascript/).', /** Label for a table column that displays the name of a third-party provider that potentially links to their website. */ columnThirdParty: 'Third-Party', - /** Label for a table column that displays how much time each row spent executing on the main thread, entries will be the number of milliseconds spent. */ - columnMainThreadTime: 'Main Thread Time', - /** Summary text for the result of a Lighthouse audit that identifies the code on the page that the user doesn't control. This text summarizes the number of distinct entities that were found on the page. */ - displayValue: `{itemCount, plural, - =1 {1 Third-Party Found} - other {# Third-Parties Found} - }`, + /** Label for a table column that displays how much time each row spent blocking other work on the main thread, entries will be the number of milliseconds spent. */ + columnBlockingTime: 'Main-Thread Blocking Time', + /** Summary text for the result of a Lighthouse audit that identifies the code on a web page that the user doesn't control (referred to as "third-party code"). This text summarizes the number of distinct entities that were found on the page. */ + displayValue: 'Third-party code blocked the main thread for ' + + `{timeInMs, number, milliseconds}\xa0ms`, }; const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings); +// A page passes when all third-party code blocks for less than 250 ms. +const PASS_THRESHOLD_IN_MS = 250; + /** @typedef {import("third-party-web").IEntity} ThirdPartyEntity */ class ThirdPartySummary extends Audit { @@ -43,8 +46,8 @@ class ThirdPartySummary extends Audit { return { id: 'third-party-summary', title: str_(UIStrings.title), + failureTitle: str_(UIStrings.failureTitle), description: str_(UIStrings.description), - scoreDisplayMode: Audit.SCORING_MODES.INFORMATIVE, requiredArtifacts: ['traces', 'devtoolsLogs'], }; } @@ -69,17 +72,18 @@ class ThirdPartySummary extends Audit { * @param {Array} networkRecords * @param {Array} mainThreadTasks * @param {number} cpuMultiplier - * @return {Map} + * @return {Map} */ static getSummaryByEntity(networkRecords, mainThreadTasks, cpuMultiplier) { - /** @type {Map} */ + /** @type {Map} */ const entities = new Map(); + const defaultEntityStat = {mainThreadTime: 0, blockingTime: 0, transferSize: 0}; for (const request of networkRecords) { const entity = ThirdPartySummary.getEntitySafe(request.url); if (!entity) continue; - const entityStats = entities.get(entity) || {mainThreadTime: 0, transferSize: 0}; + const entityStats = entities.get(entity) || {...defaultEntityStat}; entityStats.transferSize += request.transferSize; entities.set(entity, entityStats); } @@ -91,8 +95,14 @@ class ThirdPartySummary extends Audit { const entity = ThirdPartySummary.getEntitySafe(attributeableURL); if (!entity) continue; - const entityStats = entities.get(entity) || {mainThreadTime: 0, transferSize: 0}; - entityStats.mainThreadTime += task.selfTime * cpuMultiplier; + const entityStats = entities.get(entity) || {...defaultEntityStat}; + const taskDuration = task.selfTime * cpuMultiplier; + // The amount of time spent on main thread is the sum of all durations. + entityStats.mainThreadTime += taskDuration; + // The amount of time spent *blocking* on main thread is the sum of all time longer than 50ms. + // Note that this is not totally equivalent to the TBT definition since it fails to account for FCP, + // but a majority of third-party work occurs after FCP and should yield largely similar numbers. + entityStats.blockingTime += Math.max(taskDuration - 50, 0); entities.set(entity, entityStats); } @@ -117,15 +127,10 @@ class ThirdPartySummary extends Audit { const summary = {wastedBytes: 0, wastedMs: 0}; - // Sort by a combined measure of bytes + main thread time. - // 1KB ~= 1 ms - /** @param {{transferSize: number, mainThreadTime: number}} stats */ - const computeSortValue = stats => stats.transferSize / 1024 + stats.mainThreadTime; - const results = Array.from(summaryByEntity.entries()) .map(([entity, stats]) => { summary.wastedBytes += stats.transferSize; - summary.wastedMs += stats.mainThreadTime; + summary.wastedMs += stats.blockingTime; return { entity: /** @type {LH.Audit.Details.LinkValue} */ ({ @@ -135,17 +140,19 @@ class ThirdPartySummary extends Audit { }), transferSize: stats.transferSize, mainThreadTime: stats.mainThreadTime, + blockingTime: stats.blockingTime, }; }) - .sort((a, b) => computeSortValue(b) - computeSortValue(a)); + // Sort by blocking time first, then transfer size to break ties. + .sort((a, b) => (b.blockingTime - a.blockingTime) || (b.transferSize - a.transferSize)); /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ {key: 'entity', itemType: 'link', text: str_(UIStrings.columnThirdParty)}, {key: 'transferSize', granularity: 1, itemType: 'bytes', text: str_(i18n.UIStrings.columnSize)}, - {key: 'mainThreadTime', granularity: 1, itemType: 'ms', - text: str_(UIStrings.columnMainThreadTime)}, + {key: 'blockingTime', granularity: 1, itemType: 'ms', + text: str_(UIStrings.columnBlockingTime)}, ]; if (!results.length) { @@ -156,8 +163,11 @@ class ThirdPartySummary extends Audit { } return { - score: Number(results.length === 0), - displayValue: str_(UIStrings.displayValue, {itemCount: results.length}), + score: Number(summary.wastedMs <= PASS_THRESHOLD_IN_MS), + displayValue: str_(UIStrings.displayValue, { + itemCount: results.length, + timeInMs: summary.wastedMs, + }), details: Audit.makeTableDetails(headings, results, summary), }; } diff --git a/lighthouse-core/lib/i18n/locales/en-US.json b/lighthouse-core/lib/i18n/locales/en-US.json index 096c96bc7b56..6727113c8ed9 100644 --- a/lighthouse-core/lib/i18n/locales/en-US.json +++ b/lighthouse-core/lib/i18n/locales/en-US.json @@ -1073,8 +1073,8 @@ "lighthouse-core/audits/themed-omnibox.js | title": { "message": "Sets a theme color for the address bar." }, - "lighthouse-core/audits/third-party-summary.js | columnMainThreadTime": { - "message": "Main Thread Time" + "lighthouse-core/audits/third-party-summary.js | columnBlockingTime": { + "message": "Main-Thread Blocking Time" }, "lighthouse-core/audits/third-party-summary.js | columnThirdParty": { "message": "Third-Party" @@ -1083,10 +1083,13 @@ "message": "Third-party code can significantly impact load performance. Limit the number of redundant third-party providers and try to load third-party code after your page has primarily finished loading. [Learn more](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/loading-third-party-javascript/)." }, "lighthouse-core/audits/third-party-summary.js | displayValue": { - "message": "{itemCount, plural,\n =1 {1 Third-Party Found}\n other {# Third-Parties Found}\n }" + "message": "Third-party code blocked the main thread for {timeInMs, number, milliseconds} ms" + }, + "lighthouse-core/audits/third-party-summary.js | failureTitle": { + "message": "Reduce the impact of third-party code" }, "lighthouse-core/audits/third-party-summary.js | title": { - "message": "Third-Party Usage" + "message": "Third-Party usage" }, "lighthouse-core/audits/time-to-first-byte.js | description": { "message": "Time To First Byte identifies the time at which your server sends a response. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/ttfb)." diff --git a/lighthouse-core/lib/i18n/locales/en-XL.json b/lighthouse-core/lib/i18n/locales/en-XL.json index 278731990423..220fbd3b8e03 100644 --- a/lighthouse-core/lib/i18n/locales/en-XL.json +++ b/lighthouse-core/lib/i18n/locales/en-XL.json @@ -1073,8 +1073,8 @@ "lighthouse-core/audits/themed-omnibox.js | title": { "message": "Ŝét̂ś â t́ĥém̂é ĉól̂ór̂ f́ôŕ t̂h́ê ád̂d́r̂éŝś b̂ár̂." }, - "lighthouse-core/audits/third-party-summary.js | columnMainThreadTime": { - "message": "M̂áîń T̂h́r̂éâd́ T̂ím̂é" + "lighthouse-core/audits/third-party-summary.js | columnBlockingTime": { + "message": "M̂áîń-T̂h́r̂éâd́ B̂ĺôćk̂ín̂ǵ T̂ím̂é" }, "lighthouse-core/audits/third-party-summary.js | columnThirdParty": { "message": "T̂h́îŕd̂-Ṕâŕt̂ý" @@ -1083,10 +1083,13 @@ "message": "T̂h́îŕd̂-ṕâŕt̂ý ĉód̂é ĉán̂ śîǵn̂íf̂íĉán̂t́l̂ý îḿp̂áĉt́ l̂óâd́ p̂ér̂f́ôŕm̂án̂ćê. Ĺîḿît́ t̂h́ê ńûḿb̂ér̂ óf̂ ŕêd́ûńd̂án̂t́ t̂h́îŕd̂-ṕâŕt̂ý p̂ŕôv́îd́êŕŝ án̂d́ t̂ŕŷ t́ô ĺôád̂ t́ĥír̂d́-p̂ár̂t́ŷ ćôd́ê áf̂t́êŕ ŷóûŕ p̂áĝé ĥáŝ ṕr̂ím̂ár̂íl̂ý f̂ín̂íŝh́êd́ l̂óâd́îńĝ. [Ĺêár̂ń m̂ór̂é](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/loading-third-party-javascript/)." }, "lighthouse-core/audits/third-party-summary.js | displayValue": { - "message": "{itemCount, plural,\n =1 {1 T̂h́îŕd̂-Ṕâŕt̂ý F̂óûńd̂}\n other {# T́ĥír̂d́-P̂ár̂t́îéŝ F́ôún̂d́}\n }" + "message": "T̂h́îŕd̂-ṕâŕt̂ý ĉód̂é b̂ĺôćk̂éd̂ t́ĥé m̂áîń t̂h́r̂éâd́ f̂ór̂ {timeInMs, number, milliseconds} ḿŝ" + }, + "lighthouse-core/audits/third-party-summary.js | failureTitle": { + "message": "R̂éd̂úĉé t̂h́ê ím̂ṕâćt̂ óf̂ t́ĥír̂d́-p̂ár̂t́ŷ ćôd́ê" }, "lighthouse-core/audits/third-party-summary.js | title": { - "message": "T̂h́îŕd̂-Ṕâŕt̂ý Ûśâǵê" + "message": "T̂h́îŕd̂-Ṕâŕt̂ý ûśâǵê" }, "lighthouse-core/audits/time-to-first-byte.js | description": { "message": "T̂ím̂é T̂ó F̂ír̂śt̂ B́ŷt́ê íd̂én̂t́îf́îéŝ t́ĥé t̂ím̂é ât́ ŵh́îćĥ ýôúr̂ śêŕv̂ér̂ śêńd̂ś â ŕêśp̂ón̂śê. [Ĺêár̂ń m̂ór̂é](https://developers.google.com/web/tools/lighthouse/audits/ttfb)." diff --git a/lighthouse-core/test/audits/third-party-summary-test.js b/lighthouse-core/test/audits/third-party-summary-test.js index 7bded94240bb..da4c5cacc05d 100644 --- a/lighthouse-core/test/audits/third-party-summary-test.js +++ b/lighthouse-core/test/audits/third-party-summary-test.js @@ -22,7 +22,10 @@ describe('Third party summary', () => { const results = await ThirdPartySummary.audit(artifacts, {computedCache: new Map()}); - expect(results.displayValue).toBeDisplayString('2 Third-Parties Found'); + expect(results.score).toBe(1); + expect(results.displayValue).toBeDisplayString( + 'Third-party code blocked the main thread for 20 ms' + ); expect(results.details.items).toEqual([ { entity: { @@ -31,6 +34,7 @@ describe('Third party summary', () => { url: 'https://marketingplatform.google.com/about/tag-manager/', }, mainThreadTime: 104.70300000000002, + blockingTime: 18.186999999999998, transferSize: 30827, }, { @@ -40,6 +44,7 @@ describe('Third party summary', () => { url: 'https://www.google.com/analytics/analytics/', }, mainThreadTime: 87.576, + blockingTime: 0, transferSize: 20913, }, ]); @@ -54,9 +59,12 @@ describe('Third party summary', () => { const settings = {throttlingMethod: 'simulate', throttling: {cpuSlowdownMultiplier: 4}}; const results = await ThirdPartySummary.audit(artifacts, {computedCache: new Map(), settings}); + expect(results.score).toBe(0); expect(results.details.items).toHaveLength(2); expect(Math.round(results.details.items[0].mainThreadTime)).toEqual(419); + expect(Math.round(results.details.items[0].blockingTime)).toEqual(250); expect(Math.round(results.details.items[1].mainThreadTime)).toEqual(350); + expect(Math.round(results.details.items[1].blockingTime)).toEqual(157); }); it('be not applicable when no third parties are present', async () => { diff --git a/lighthouse-core/test/results/sample_v2.json b/lighthouse-core/test/results/sample_v2.json index afa314985761..c5bba298b4d6 100644 --- a/lighthouse-core/test/results/sample_v2.json +++ b/lighthouse-core/test/results/sample_v2.json @@ -1481,11 +1481,11 @@ }, "third-party-summary": { "id": "third-party-summary", - "title": "Third-Party Usage", + "title": "Third-Party usage", "description": "Third-party code can significantly impact load performance. Limit the number of redundant third-party providers and try to load third-party code after your page has primarily finished loading. [Learn more](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/loading-third-party-javascript/).", - "score": null, - "scoreDisplayMode": "informative", - "displayValue": "1 Third-Party Found", + "score": 1, + "scoreDisplayMode": "binary", + "displayValue": "Third-party code blocked the main thread for 20 ms", "details": { "type": "table", "headings": [ @@ -1501,10 +1501,10 @@ "text": "Size" }, { - "key": "mainThreadTime", + "key": "blockingTime", "granularity": 1, "itemType": "ms", - "text": "Main Thread Time" + "text": "Main-Thread Blocking Time" } ], "items": [ @@ -1515,12 +1515,13 @@ "url": "https://developers.google.com/speed/libraries/" }, "transferSize": 30174, - "mainThreadTime": 82.74100000000001 + "mainThreadTime": 82.74100000000001, + "blockingTime": 22.918000000000006 } ], "summary": { "wastedBytes": 30174, - "wastedMs": 82.74100000000001 + "wastedMs": 22.918000000000006 } } }, @@ -5603,7 +5604,8 @@ "lighthouse-core/audits/third-party-summary.js | displayValue": [ { "values": { - "itemCount": 1 + "itemCount": 1, + "timeInMs": 22.918000000000006 }, "path": "audits[third-party-summary].displayValue" } @@ -5621,7 +5623,7 @@ "audits[uses-text-compression].details.headings[1].label", "audits[tap-targets].details.headings[1].text" ], - "lighthouse-core/audits/third-party-summary.js | columnMainThreadTime": [ + "lighthouse-core/audits/third-party-summary.js | columnBlockingTime": [ "audits[third-party-summary].details.headings[2].text" ], "lighthouse-core/audits/manual/pwa-cross-browser.js | title": [ diff --git a/proto/sample_v2_round_trip.json b/proto/sample_v2_round_trip.json index a75d9660551c..8507ea2a3057 100644 --- a/proto/sample_v2_round_trip.json +++ b/proto/sample_v2_round_trip.json @@ -2578,12 +2578,13 @@ { "granularity": 1.0, "itemType": "ms", - "key": "mainThreadTime", - "text": "Main Thread Time" + "key": "blockingTime", + "text": "Main-Thread Blocking Time" } ], "items": [ { + "blockingTime": 22.918000000000006, "entity": { "text": "Google CDN", "type": "link", @@ -2595,15 +2596,15 @@ ], "summary": { "wastedBytes": 30174.0, - "wastedMs": 82.74100000000001 + "wastedMs": 22.918000000000006 }, "type": "table" }, - "displayValue": "1 Third-Party Found", + "displayValue": "Third-party code blocked the main thread for 20\u00a0ms", "id": "third-party-summary", - "score": null, - "scoreDisplayMode": "informative", - "title": "Third-Party Usage" + "score": 1.0, + "scoreDisplayMode": "binary", + "title": "Third-Party usage" }, "time-to-first-byte": { "description": "Time To First Byte identifies the time at which your server sends a response. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/ttfb).",