lighthouse 9.5.0-dev.20230109 → 9.5.0-dev.20230111

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -28,4 +28,9 @@ const exclusions = {
28
28
  ],
29
29
  };
30
30
 
31
+ // https://github.com/GoogleChrome/lighthouse/issues/14271
32
+ for (const array of Object.values(exclusions)) {
33
+ array.push('lantern-idle-callback-short');
34
+ }
35
+
31
36
  export default exclusions;
@@ -14,6 +14,8 @@ import errorsExpiredSsl from './test-definitions/errors-expired-ssl.js';
14
14
  import errorsIframeExpiredSsl from './test-definitions/errors-iframe-expired-ssl.js';
15
15
  import errorsInfiniteLoop from './test-definitions/errors-infinite-loop.js';
16
16
  import formsAutoComplete from './test-definitions/forms-autocomplete.js';
17
+ import fpsMax from './test-definitions/fps-max.js';
18
+ import fpsScaled from './test-definitions/fps-scaled.js';
17
19
  import issuesMixedContent from './test-definitions/issues-mixed-content.js';
18
20
  import lanternFetch from './test-definitions/lantern-fetch.js';
19
21
  import lanternIdleCallbackLong from './test-definitions/lantern-idle-callback-long.js';
@@ -51,10 +53,10 @@ import pwaSvgomg from './test-definitions/pwa-svgomg.js';
51
53
  import redirectsClientPaintServer from './test-definitions/redirects-client-paint-server.js';
52
54
  import redirectsHistoryPushState from './test-definitions/redirects-history-push-state.js';
53
55
  import redirectsMultipleServer from './test-definitions/redirects-multiple-server.js';
54
- import redirectScripts from './test-definitions/redirects-scripts.js';
56
+ import redirectsScripts from './test-definitions/redirects-scripts.js';
57
+ import redirectsSelf from './test-definitions/redirects-self.js';
55
58
  import redirectsSingleClient from './test-definitions/redirects-single-client.js';
56
59
  import redirectsSingleServer from './test-definitions/redirects-single-server.js';
57
- import redirectsSelf from './test-definitions/redirects-self.js';
58
60
  import screenshot from './test-definitions/screenshot.js';
59
61
  import seoFailing from './test-definitions/seo-failing.js';
60
62
  import seoPassing from './test-definitions/seo-passing.js';
@@ -62,8 +64,6 @@ import seoStatus403 from './test-definitions/seo-status-403.js';
62
64
  import seoTapTargets from './test-definitions/seo-tap-targets.js';
63
65
  import sourceMaps from './test-definitions/source-maps.js';
64
66
  import timing from './test-definitions/timing.js';
65
- import fpsScaled from './test-definitions/fps-scaled.js';
66
- import fpsMax from './test-definitions/fps-max.js';
67
67
 
68
68
  /** @type {ReadonlyArray<Smokehouse.TestDfn>} */
69
69
  const smokeTests = [
@@ -77,19 +77,21 @@ const smokeTests = [
77
77
  errorsIframeExpiredSsl,
78
78
  errorsInfiniteLoop,
79
79
  formsAutoComplete,
80
+ fpsMax,
81
+ fpsScaled,
80
82
  issuesMixedContent,
83
+ lanternFetch,
84
+ lanternIdleCallbackLong,
85
+ lanternIdleCallbackShort,
81
86
  lanternOnline,
82
87
  lanternSetTimeout,
83
- lanternFetch,
84
88
  lanternXhr,
85
- lanternIdleCallbackShort,
86
- lanternIdleCallbackLong,
87
89
  legacyJavascript,
88
90
  metricsDebugger,
89
91
  metricsDelayedFcp,
90
92
  metricsDelayedLcp,
91
- metricsTrickyTtiLateFcp,
92
93
  metricsTrickyTti,
94
+ metricsTrickyTtiLateFcp,
93
95
  offlineOnlineOnly,
94
96
  offlineReady,
95
97
  offlineSwBroken,
@@ -107,17 +109,17 @@ const smokeTests = [
107
109
  perfTraceElements,
108
110
  pubads,
109
111
  pwaAirhorner,
110
- pwaChromestatus,
111
- pwaSvgomg,
112
112
  pwaCaltrain,
113
+ pwaChromestatus,
113
114
  pwaRocks,
115
+ pwaSvgomg,
114
116
  redirectsClientPaintServer,
115
117
  redirectsHistoryPushState,
116
118
  redirectsMultipleServer,
117
- redirectScripts,
119
+ redirectsScripts,
120
+ redirectsSelf,
118
121
  redirectsSingleClient,
119
122
  redirectsSingleServer,
120
- redirectsSelf,
121
123
  screenshot,
122
124
  seoFailing,
123
125
  seoPassing,
@@ -125,8 +127,6 @@ const smokeTests = [
125
127
  seoTapTargets,
126
128
  sourceMaps,
127
129
  timing,
128
- fpsScaled,
129
- fpsMax,
130
130
  ];
131
131
 
132
132
  export default smokeTests;
@@ -0,0 +1,120 @@
1
+ /**
2
+ * @license Copyright 2022 The Lighthouse Authors. All Rights Reserved.
3
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
4
+ * 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.
5
+ */
6
+
7
+ import {Audit} from './audit.js';
8
+ import * as i18n from '../lib/i18n/i18n.js';
9
+ import {NotRestoredReasonDescription} from '../lib/bf-cache-strings.js';
10
+
11
+ /* eslint-disable max-len */
12
+ const UIStrings = {
13
+ /** Title of a diagnostic Lighthouse audit that identifies when the back/forward cache is being used. "back/forward" refers to the back and forward buttons found in modern browsers. This title is shown to users if the back/forward cache was used, or if the there was no attempt to restore the page from the back/forward cache. */
14
+ title: `Page didn't prevent back/forward cache restoration`,
15
+ /** Title of a diagnostic Lighthouse audit that identifies when the back/forward cache is being used. "back/forward" refers to the back and forward buttons found in modern browsers. This title is shown to users if the page attempted to restore from the back/forward cache but the back/forward cache was not used. */
16
+ failureTitle: 'Page prevented back/forward cache restoration',
17
+ /** Description of a diagnostic Lighthouse audit that identifies when the back/forward cache is being used. "back/forward" refers to the back and forward buttons found in modern browsers. */
18
+ description: 'Many navigations are performed by going back to a previous page, or forwards again. The back/forward cache (bfcache) can speed up these return navigations. [Learn more about the bfcache](https://web.dev/bfcache/)',
19
+ /** Failure type for an error that the user should be able to address themselves. Shown in a table column with other failure types. */
20
+ actionableFailureType: 'Actionable',
21
+ /** Failure type for an error that the user cannot address in the page's code. Shown in a table column with other failure types. */
22
+ notActionableFailureType: 'Not actionable',
23
+ /** Failure type for an error caused by missing browser support. Shown in a table column with other failure types. */
24
+ supportPendingFailureType: 'Pending browser support',
25
+ /** Label for a column in a data table; entries in the column will be a string explaining why a failure occurred. */
26
+ failureReasonColumn: 'Failure reason',
27
+ /** Label for a column in a data table; entries in the column will be a string representing the type of failure preventing the back/forward cache from being used. */
28
+ failureTypeColumn: 'Failure type',
29
+ /**
30
+ * @description [ICU Syntax] Label for an audit identifying the number of back/forward cache failure reasons found in the page.
31
+ */
32
+ displayValue: `{itemCount, plural,
33
+ =1 {1 failure reason}
34
+ other {# failure reasons}
35
+ }`,
36
+ };
37
+ /* eslint-enable max-len */
38
+
39
+ const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
40
+
41
+ /** @type {LH.Crdp.Page.BackForwardCacheNotRestoredReasonType[]} */
42
+ const ORDERED_FAILURE_TYPES = ['PageSupportNeeded', 'SupportPending', 'Circumstantial'];
43
+
44
+ /** @type {Record<LH.Crdp.Page.BackForwardCacheNotRestoredReasonType, string | LH.IcuMessage>} */
45
+ const FAILURE_TYPE_TO_STRING = {
46
+ PageSupportNeeded: str_(UIStrings.actionableFailureType),
47
+ Circumstantial: str_(UIStrings.notActionableFailureType),
48
+ SupportPending: str_(UIStrings.supportPendingFailureType),
49
+ };
50
+
51
+ class BFCache extends Audit {
52
+ /**
53
+ * @return {LH.Audit.Meta}
54
+ */
55
+ static get meta() {
56
+ return {
57
+ id: 'bf-cache',
58
+ title: str_(UIStrings.title),
59
+ failureTitle: str_(UIStrings.failureTitle),
60
+ description: str_(UIStrings.description),
61
+ supportedModes: ['navigation', 'timespan'],
62
+ requiredArtifacts: ['BFCacheFailures'],
63
+ };
64
+ }
65
+
66
+ /**
67
+ * @param {LH.Artifacts} artifacts
68
+ * @return {Promise<LH.Audit.Product>}
69
+ */
70
+ static async audit(artifacts) {
71
+ const failures = artifacts.BFCacheFailures;
72
+ if (!failures.length) return {score: 1};
73
+
74
+ // TODO: Analyze more than one bf cache failure.
75
+ const {notRestoredReasonsTree} = failures[0];
76
+
77
+ /** @type {LH.Audit.Details.TableItem[]} */
78
+ const results = [];
79
+
80
+ for (const failureType of ORDERED_FAILURE_TYPES) {
81
+ const reasonsMap = notRestoredReasonsTree[failureType];
82
+
83
+ for (const [reason, frameUrls] of Object.entries(reasonsMap)) {
84
+ results.push({
85
+ reason: NotRestoredReasonDescription[reason]?.name ?? reason,
86
+ failureType: FAILURE_TYPE_TO_STRING[failureType],
87
+ subItems: {
88
+ type: 'subitems',
89
+ items: frameUrls.map(frameUrl => ({frameUrl})),
90
+ },
91
+ // Include hidden protocol reason code for debugging.
92
+ protocolReason: reason,
93
+ });
94
+ }
95
+ }
96
+
97
+ /** @type {LH.Audit.Details.Table['headings']} */
98
+ const headings = [
99
+ /* eslint-disable max-len */
100
+ {key: 'reason', valueType: 'text', subItemsHeading: {key: 'frameUrl', valueType: 'url'}, label: str_(UIStrings.failureReasonColumn)},
101
+ {key: 'failureType', valueType: 'text', label: str_(UIStrings.failureTypeColumn)},
102
+ /* eslint-enable max-len */
103
+ ];
104
+
105
+ const details = Audit.makeTableDetails(headings, results);
106
+
107
+ const displayValue = results.length ?
108
+ str_(UIStrings.displayValue, {itemCount: results.length}) :
109
+ undefined;
110
+
111
+ return {
112
+ score: results.length ? 0 : 1,
113
+ displayValue,
114
+ details,
115
+ };
116
+ }
117
+ }
118
+
119
+ export default BFCache;
120
+ export {UIStrings};
@@ -16,7 +16,7 @@ const UIStrings = {
16
16
  'failureTitle': 'Web app manifest or service worker do not meet the installability requirements',
17
17
  /** Description of a Lighthouse audit that tells the user why installability is important for webapps. This is displayed after a user expands the section to see more. No character length limits. The last sentence starting with 'Learn' becomes link text to additional documentation. */
18
18
  'description': `Service worker is the technology that enables your app to use many Progressive Web App features, such as offline, add to homescreen, and push notifications. With proper service worker and manifest implementations, browsers can proactively prompt users to add your app to their homescreen, which can lead to higher engagement. [Learn more about manifest installability requirements](https://developer.chrome.com/docs/lighthouse/pwa/installable-manifest/).`,
19
- /** Description Table column header for the observed value of the Installability failure reason statistic. */
19
+ /** Label for a column in a data table; entries in the column will be a string explaining why a failure occurred. */
20
20
  'columnValue': 'Failure reason',
21
21
  /**
22
22
  * @description [ICU Syntax] Label for an audit identifying the number of installability errors found in the page.
@@ -217,8 +217,11 @@ const defaultConfig = {
217
217
  {id: artifacts.devtoolsLogs, gatherer: 'devtools-log-compat'},
218
218
  {id: artifacts.traces, gatherer: 'trace-compat'},
219
219
 
220
- // FullPageScreenshot comes at the very end so all other node analysis is captured.
220
+ // FullPageScreenshot comes at the end so all other node analysis is captured.
221
221
  {id: artifacts.FullPageScreenshot, gatherer: 'full-page-screenshot'},
222
+
223
+ // BFCacheErrors comes at the very end because it can perform a page navigation.
224
+ {id: artifacts.BFCacheFailures, gatherer: 'bf-cache-failures'},
222
225
  ],
223
226
  audits: [
224
227
  'is-on-https',
@@ -374,6 +377,7 @@ const defaultConfig = {
374
377
  'seo/canonical',
375
378
  'seo/manual/structured-data',
376
379
  'work-during-interaction',
380
+ 'bf-cache',
377
381
  ],
378
382
  groups: {
379
383
  'metrics': {
@@ -516,6 +520,7 @@ const defaultConfig = {
516
520
  {id: 'no-unload-listeners', weight: 0},
517
521
  {id: 'uses-responsive-images-snapshot', weight: 0},
518
522
  {id: 'work-during-interaction', weight: 0},
523
+ {id: 'bf-cache', weight: 0},
519
524
 
520
525
  // Budget audits.
521
526
  {id: 'performance-budget', weight: 0, group: 'budgets'},
@@ -61,8 +61,8 @@ class ExecutionContext {
61
61
  await this._session.sendCommand('Page.enable');
62
62
  await this._session.sendCommand('Runtime.enable');
63
63
 
64
- const resourceTreeResponse = await this._session.sendCommand('Page.getResourceTree');
65
- const mainFrameId = resourceTreeResponse.frameTree.frame.id;
64
+ const frameTreeResponse = await this._session.sendCommand('Page.getFrameTree');
65
+ const mainFrameId = frameTreeResponse.frameTree.frame.id;
66
66
 
67
67
  const isolatedWorldResponse = await this._session.sendCommand('Page.createIsolatedWorld', {
68
68
  frameId: mainFrameId,
@@ -95,8 +95,8 @@ class NetworkMonitor extends NetworkMonitorEventEmitter {
95
95
  const frameNavigations = this._frameNavigations;
96
96
  if (!frameNavigations.length) return {};
97
97
 
98
- const resourceTreeResponse = await this._session.sendCommand('Page.getResourceTree');
99
- const mainFrameId = resourceTreeResponse.frameTree.frame.id;
98
+ const frameTreeResponse = await this._session.sendCommand('Page.getFrameTree');
99
+ const mainFrameId = frameTreeResponse.frameTree.frame.id;
100
100
  const mainFrameNavigations = frameNavigations.filter(frame => frame.id === mainFrameId);
101
101
  if (!mainFrameNavigations.length) log.warn('NetworkMonitor', 'No detected navigations');
102
102
 
@@ -13,7 +13,13 @@ import {ExecutionContext} from './execution-context.js';
13
13
 
14
14
  /** @typedef {InstanceType<import('./network-monitor.js')['NetworkMonitor']>} NetworkMonitor */
15
15
  /** @typedef {import('./network-monitor.js').NetworkMonitorEvent} NetworkMonitorEvent */
16
- /** @typedef {{promise: Promise<void>, cancel: function(): void}} CancellableWait */
16
+
17
+ /**
18
+ * @template [T=void]
19
+ * @typedef CancellableWait
20
+ * @prop {Promise<T>} promise
21
+ * @prop {() => void} cancel
22
+ */
17
23
 
18
24
  /**
19
25
  * @typedef WaitOptions
@@ -40,7 +46,7 @@ function waitForNothing() {
40
46
  * Returns a promise that resolve when a frame has been navigated.
41
47
  * Used for detecting that our about:blank reset has been completed.
42
48
  * @param {LH.Gatherer.FRProtocolSession} session
43
- * @return {CancellableWait}
49
+ * @return {CancellableWait<LH.Crdp.Page.FrameNavigatedEvent>}
44
50
  */
45
51
  function waitForFrameNavigated(session) {
46
52
  /** @type {(() => void)} */
@@ -48,6 +54,7 @@ function waitForFrameNavigated(session) {
48
54
  throw new Error('waitForFrameNavigated.cancel() called before it was defined');
49
55
  };
50
56
 
57
+ /** @type {Promise<LH.Crdp.Page.FrameNavigatedEvent>} */
51
58
  const promise = new Promise((resolve, reject) => {
52
59
  session.once('Page.frameNavigated', resolve);
53
60
  cancel = () => {
@@ -8,6 +8,8 @@ import FRGatherer from '../base-gatherer.js';
8
8
  import {waitForFrameNavigated, waitForLoadEvent} from '../driver/wait-for-condition.js';
9
9
  import DevtoolsLog from './devtools-log.js';
10
10
 
11
+ const FAILURE_EVENT_TIMEOUT = 100;
12
+
11
13
  class BFCacheFailures extends FRGatherer {
12
14
  /** @type {LH.Gatherer.GathererMeta<'DevtoolsLog'>} */
13
15
  meta = {
@@ -101,16 +103,32 @@ class BFCacheFailures extends FRGatherer {
101
103
  const history = await session.sendCommand('Page.getNavigationHistory');
102
104
  const entry = history.entries[history.currentIndex];
103
105
 
106
+ // In theory, we should be able to use about:blank here
107
+ // but that sometimes produces BrowsingInstanceNotSwapped failures.
108
+ // DevTools uses chrome://terms as it's temporary page so we should stick with that.
109
+ // https://github.com/GoogleChrome/lighthouse/issues/14665
104
110
  await Promise.all([
105
- session.sendCommand('Page.navigate', {url: 'about:blank'}),
111
+ session.sendCommand('Page.navigate', {url: 'chrome://terms'}),
106
112
  waitForLoadEvent(session, 0).promise,
107
113
  ]);
108
114
 
109
- await Promise.all([
115
+ const [, frameNavigatedEvent] = await Promise.all([
110
116
  session.sendCommand('Page.navigateToHistoryEntry', {entryId: entry.id}),
111
117
  waitForFrameNavigated(session).promise,
112
118
  ]);
113
119
 
120
+ // The bfcache failure event is not necessarily emitted by this point.
121
+ // If we are expecting a bfcache failure event but haven't seen one, we should wait for it.
122
+ if (frameNavigatedEvent.type !== 'BackForwardCacheRestore' && !bfCacheEvent) {
123
+ await new Promise(resolve => setTimeout(resolve, FAILURE_EVENT_TIMEOUT));
124
+
125
+ // If we still can't get the failure reasons after the timeout we should fail loudly,
126
+ // otherwise this gatherer will return no failures when there should be failures.
127
+ if (!bfCacheEvent) {
128
+ throw new Error('bfcache failed but the failure reasons were not emitted in time');
129
+ }
130
+ }
131
+
114
132
  session.off('Page.backForwardCacheNotUsed', onBfCacheNotUsed);
115
133
 
116
134
  return bfCacheEvent;
@@ -136,7 +154,7 @@ class BFCacheFailures extends FRGatherer {
136
154
  */
137
155
  async getArtifact(context) {
138
156
  const events = this.passivelyCollectBFCacheEvents(context);
139
- if (context.gatherMode === 'navigation') {
157
+ if (context.gatherMode === 'navigation' && !context.settings.usePassiveGathering) {
140
158
  const activelyCollectedEvent = await this.activelyCollectBFCacheEvent(context);
141
159
  if (activelyCollectedEvent) events.push(activelyCollectedEvent);
142
160
  }
@@ -518,6 +518,7 @@ const UIStrings = {
518
518
  const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
519
519
 
520
520
 
521
+ /** @type {Record<string, {name: LH.IcuMessage} | undefined>} */
521
522
  const NotRestoredReasonDescription = {
522
523
  'NotPrimaryMainFrame': {name: str_(UIStrings.notMainFrame)},
523
524
  'BackForwardCacheDisabled': {name: str_(UIStrings.backForwardCacheDisabled)},
package/core/user-flow.js CHANGED
@@ -87,22 +87,22 @@ class UserFlow {
87
87
  * @return {LH.UserFlow.StepFlags}
88
88
  */
89
89
  _getNextNavigationFlags(flags) {
90
- const nextFlags = this._getNextFlags(flags) || {};
90
+ const newStepFlags = this._getNextFlags(flags) || {};
91
91
 
92
- if (nextFlags.skipAboutBlank === undefined) {
93
- nextFlags.skipAboutBlank = true;
92
+ if (newStepFlags.skipAboutBlank === undefined) {
93
+ newStepFlags.skipAboutBlank = true;
94
94
  }
95
95
 
96
96
  // On repeat navigations, we want to disable storage reset by default (i.e. it's not a cold load).
97
97
  const isSubsequentNavigation = this._gatherSteps
98
98
  .some(step => step.artifacts.GatherContext.gatherMode === 'navigation');
99
99
  if (isSubsequentNavigation) {
100
- if (nextFlags.disableStorageReset === undefined) {
101
- nextFlags.disableStorageReset = true;
100
+ if (newStepFlags.disableStorageReset === undefined) {
101
+ newStepFlags.disableStorageReset = true;
102
102
  }
103
103
  }
104
104
 
105
- return nextFlags;
105
+ return newStepFlags;
106
106
  }
107
107
 
108
108
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "lighthouse",
3
3
  "type": "module",
4
- "version": "9.5.0-dev.20230109",
4
+ "version": "9.5.0-dev.20230111",
5
5
  "description": "Automated auditing, performance metrics, and best practices for the web.",
6
6
  "main": "./core/index.js",
7
7
  "bin": {