lighthouse 8.6.0 → 9.0.0

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.
Files changed (209) hide show
  1. package/dist/report/bundle.esm.js +5994 -0
  2. package/dist/report/flow.js +30 -13
  3. package/dist/report/standalone.js +41 -35
  4. package/flow-report/.eslintrc.cjs +2 -0
  5. package/flow-report/assets/standalone-flow-template.html +3 -2
  6. package/flow-report/assets/styles.css +108 -31
  7. package/flow-report/src/app.tsx +18 -18
  8. package/flow-report/src/common.tsx +28 -15
  9. package/flow-report/src/header.tsx +12 -13
  10. package/flow-report/src/help-dialog.tsx +3 -4
  11. package/flow-report/src/i18n/i18n.tsx +57 -16
  12. package/flow-report/src/i18n/ui-strings.js +50 -0
  13. package/flow-report/src/icons.tsx +36 -8
  14. package/flow-report/src/sidebar/flow.tsx +3 -3
  15. package/flow-report/src/sidebar/sidebar.tsx +35 -14
  16. package/flow-report/src/summary/category.tsx +97 -24
  17. package/flow-report/src/summary/summary.tsx +21 -14
  18. package/flow-report/src/topbar.tsx +9 -5
  19. package/flow-report/src/util.ts +33 -26
  20. package/flow-report/src/wrappers/category-score.tsx +0 -3
  21. package/flow-report/src/wrappers/markdown.tsx +27 -0
  22. package/flow-report/src/wrappers/report-renderer.tsx +10 -3
  23. package/flow-report/src/wrappers/report.tsx +27 -6
  24. package/flow-report/standalone-flow.tsx +4 -3
  25. package/flow-report/test/common-test.tsx +32 -17
  26. package/flow-report/test/flow-report-pptr-test.ts +46 -0
  27. package/flow-report/test/header-test.tsx +6 -6
  28. package/flow-report/test/setup/env-setup.ts +1 -1
  29. package/flow-report/test/sidebar/sidebar-test.tsx +43 -1
  30. package/flow-report/test/summary/category-test.tsx +120 -24
  31. package/flow-report/test/summary/summary-test.tsx +1 -1
  32. package/flow-report/test/topbar-test.tsx +1 -0
  33. package/flow-report/test/util-test.tsx +32 -11
  34. package/flow-report/test/wrappers/category-score-test.tsx +87 -0
  35. package/flow-report/test/wrappers/markdown-test.tsx +29 -0
  36. package/flow-report/tsconfig.json +1 -0
  37. package/jest.config.js +4 -2
  38. package/lighthouse-cli/.eslintrc.cjs +2 -0
  39. package/lighthouse-cli/cli-flags.js +1 -1
  40. package/lighthouse-cli/run.js +1 -1
  41. package/lighthouse-cli/test/smokehouse/frontends/smokehouse-bin.js +0 -0
  42. package/lighthouse-cli/test/smokehouse/lighthouse-runners/bundle.js +4 -2
  43. package/lighthouse-cli/test/smokehouse/lighthouse-runners/cli.js +1 -1
  44. package/lighthouse-cli/test/smokehouse/smokehouse.js +1 -1
  45. package/lighthouse-core/audits/accessibility/axe-audit.js +7 -1
  46. package/lighthouse-core/audits/audit.js +1 -1
  47. package/lighthouse-core/audits/byte-efficiency/byte-efficiency-audit.js +2 -0
  48. package/lighthouse-core/audits/byte-efficiency/modern-image-formats.js +4 -2
  49. package/lighthouse-core/audits/byte-efficiency/offscreen-images.js +11 -10
  50. package/lighthouse-core/audits/byte-efficiency/uses-optimized-images.js +5 -3
  51. package/lighthouse-core/audits/byte-efficiency/uses-responsive-images-snapshot.js +2 -1
  52. package/lighthouse-core/audits/byte-efficiency/uses-responsive-images.js +2 -1
  53. package/lighthouse-core/audits/deprecations.js +33 -11
  54. package/lighthouse-core/audits/dobetterweb/uses-http2.js +27 -11
  55. package/lighthouse-core/audits/errors-in-console.js +0 -2
  56. package/lighthouse-core/audits/image-aspect-ratio.js +5 -5
  57. package/lighthouse-core/audits/image-size-responsive.js +6 -6
  58. package/lighthouse-core/audits/installable-manifest.js +5 -3
  59. package/lighthouse-core/audits/preload-lcp-image.js +7 -5
  60. package/lighthouse-core/audits/script-treemap-data.js +1 -1
  61. package/lighthouse-core/audits/unsized-images.js +2 -3
  62. package/lighthouse-core/computed/image-records.js +4 -3
  63. package/lighthouse-core/computed/metrics/lantern-total-blocking-time.js +3 -4
  64. package/lighthouse-core/computed/metrics/tbt-utils.js +57 -0
  65. package/lighthouse-core/computed/metrics/total-blocking-time.js +3 -53
  66. package/lighthouse-core/computed/resource-summary.js +1 -1
  67. package/lighthouse-core/config/config-helpers.js +21 -10
  68. package/lighthouse-core/config/constants.js +3 -4
  69. package/lighthouse-core/config/default-config.js +61 -71
  70. package/lighthouse-core/fraggle-rock/config/config.js +1 -1
  71. package/lighthouse-core/fraggle-rock/config/default-config.js +4 -5
  72. package/lighthouse-core/fraggle-rock/gather/base-gatherer.js +6 -1
  73. package/lighthouse-core/fraggle-rock/gather/session.js +3 -4
  74. package/lighthouse-core/gather/connections/cri.js +1 -1
  75. package/lighthouse-core/gather/driver/storage.js +0 -1
  76. package/lighthouse-core/gather/driver.js +3 -7
  77. package/lighthouse-core/gather/fetcher.js +4 -7
  78. package/lighthouse-core/gather/gather-runner.js +29 -6
  79. package/lighthouse-core/gather/gatherers/accessibility.js +27 -0
  80. package/lighthouse-core/gather/gatherers/dobetterweb/response-compression.js +1 -0
  81. package/lighthouse-core/gather/gatherers/image-elements.js +0 -3
  82. package/lighthouse-core/gather/gatherers/inspector-issues.js +5 -0
  83. package/lighthouse-core/lib/asset-saver.js +7 -4
  84. package/lighthouse-core/lib/emulation.js +45 -1
  85. package/lighthouse-core/lib/i18n/i18n.js +19 -8
  86. package/lighthouse-core/lib/lh-env.js +2 -0
  87. package/lighthouse-core/lib/proto-preprocessor.js +14 -2
  88. package/lighthouse-core/lib/tappable-rects.js +8 -15
  89. package/lighthouse-core/lib/url-shim.js +1 -1
  90. package/lighthouse-core/runner.js +1 -1
  91. package/lighthouse-core/util-commonjs.js +65 -46
  92. package/package.json +27 -17
  93. package/readme.md +3 -3
  94. package/report/.eslintrc.cjs +2 -0
  95. package/report/README.md +1 -1
  96. package/report/assets/standalone-template.html +2 -3
  97. package/report/assets/styles.css +360 -254
  98. package/report/assets/templates.html +21 -76
  99. package/report/clients/bundle.js +1 -0
  100. package/report/clients/standalone.js +6 -15
  101. package/report/generator/README.md +1 -1
  102. package/report/renderer/api.js +32 -0
  103. package/report/renderer/category-renderer.js +59 -19
  104. package/report/renderer/components.js +59 -110
  105. package/report/renderer/crc-details-renderer.js +15 -12
  106. package/report/renderer/dom.js +16 -1
  107. package/report/renderer/drop-down-menu.js +2 -2
  108. package/report/renderer/element-screenshot-renderer.js +8 -8
  109. package/report/renderer/features-util.js +1 -1
  110. package/report/renderer/open-tab.js +9 -3
  111. package/report/renderer/performance-category-renderer.js +55 -34
  112. package/report/renderer/pwa-category-renderer.js +0 -10
  113. package/report/renderer/report-renderer.js +91 -44
  114. package/report/renderer/report-ui-features.js +36 -33
  115. package/report/renderer/swap-locale-feature.js +3 -3
  116. package/report/renderer/topbar-features.js +53 -48
  117. package/report/renderer/util.js +66 -46
  118. package/report/test/clients/bundle-test.js +70 -0
  119. package/report/test/renderer/category-renderer-test.js +48 -3
  120. package/report/test/renderer/i18n-test.js +0 -7
  121. package/report/test/renderer/performance-category-renderer-test.js +51 -14
  122. package/report/test/renderer/pwa-category-renderer-test.js +6 -6
  123. package/report/test/renderer/report-renderer-axe-test.js +10 -12
  124. package/report/test/renderer/report-renderer-test.js +23 -22
  125. package/report/test/renderer/report-ui-features-test.js +20 -10
  126. package/report/test/renderer/text-encoding-test.js +1 -1
  127. package/report/test/renderer/util-test.js +35 -31
  128. package/report/test-assets/faux-psi-template.html +3 -11
  129. package/report/test-assets/faux-psi.js +26 -22
  130. package/report/types/html-renderer.d.ts +2 -0
  131. package/report/types/report-renderer.d.ts +23 -0
  132. package/shared/localization/format.js +64 -30
  133. package/shared/localization/locales/ar-XB.json +72 -75
  134. package/shared/localization/locales/ar.json +72 -75
  135. package/shared/localization/locales/bg.json +72 -75
  136. package/shared/localization/locales/ca.json +72 -75
  137. package/shared/localization/locales/cs.json +89 -92
  138. package/shared/localization/locales/da.json +72 -75
  139. package/shared/localization/locales/de.json +89 -92
  140. package/shared/localization/locales/el.json +72 -75
  141. package/shared/localization/locales/en-GB.json +72 -75
  142. package/shared/localization/locales/en-US.json +75 -75
  143. package/shared/localization/locales/en-XA.json +72 -75
  144. package/shared/localization/locales/en-XL.json +75 -75
  145. package/shared/localization/locales/es-419.json +90 -93
  146. package/shared/localization/locales/es.json +90 -93
  147. package/shared/localization/locales/fi.json +72 -75
  148. package/shared/localization/locales/fil.json +72 -75
  149. package/shared/localization/locales/fr.json +89 -92
  150. package/shared/localization/locales/he.json +73 -76
  151. package/shared/localization/locales/hi.json +72 -75
  152. package/shared/localization/locales/hr.json +72 -75
  153. package/shared/localization/locales/hu.json +89 -92
  154. package/shared/localization/locales/id.json +89 -92
  155. package/shared/localization/locales/it.json +89 -92
  156. package/shared/localization/locales/ja.json +89 -92
  157. package/shared/localization/locales/ko.json +89 -92
  158. package/shared/localization/locales/lt.json +89 -92
  159. package/shared/localization/locales/lv.json +95 -98
  160. package/shared/localization/locales/nl.json +89 -92
  161. package/shared/localization/locales/no.json +72 -75
  162. package/shared/localization/locales/pl.json +72 -75
  163. package/shared/localization/locales/pt-PT.json +72 -75
  164. package/shared/localization/locales/pt.json +89 -92
  165. package/shared/localization/locales/ro.json +90 -93
  166. package/shared/localization/locales/ru.json +89 -92
  167. package/shared/localization/locales/sk.json +89 -92
  168. package/shared/localization/locales/sl.json +89 -92
  169. package/shared/localization/locales/sr-Latn.json +89 -92
  170. package/shared/localization/locales/sr.json +89 -92
  171. package/shared/localization/locales/sv.json +72 -75
  172. package/shared/localization/locales/ta.json +89 -92
  173. package/shared/localization/locales/te.json +89 -92
  174. package/shared/localization/locales/th.json +89 -92
  175. package/shared/localization/locales/tr.json +89 -92
  176. package/shared/localization/locales/uk.json +92 -95
  177. package/shared/localization/locales/vi.json +89 -92
  178. package/shared/localization/locales/zh-HK.json +89 -92
  179. package/shared/localization/locales/zh-TW.json +89 -92
  180. package/shared/localization/locales/zh.json +89 -92
  181. package/shared/localization/locales.js +6 -0
  182. package/shared/test/localization/format-test.js +45 -9
  183. package/shared/test/localization/swap-locale-test.js +0 -12
  184. package/third-party/chromium-synchronization/inspector-issueAdded-types-test.js +19 -11
  185. package/third-party/chromium-synchronization/installability-errors-test.js +2 -0
  186. package/third-party/download-content-shell/download-content-shell.js +2 -2
  187. package/third-party/download-content-shell/utils.js +0 -24
  188. package/tsconfig-all.json +2 -2
  189. package/tsconfig.json +0 -1
  190. package/types/artifacts.d.ts +7 -7
  191. package/types/enquirer.d.ts +3 -1
  192. package/types/lhr/audit-details.d.ts +6 -5
  193. package/types/lhr/flow.d.ts +3 -2
  194. package/types/lhr/lhr.d.ts +1 -1
  195. package/types/node.d.ts +5 -5
  196. package/types/rollup-plugin-postprocess.d.ts +10 -0
  197. package/changelog.md +0 -5490
  198. package/lighthouse-core/audits/dobetterweb/appcache-manifest.js +0 -67
  199. package/lighthouse-core/audits/dobetterweb/external-anchors-use-rel-noopener.js +0 -96
  200. package/lighthouse-core/audits/redirects-http.js +0 -59
  201. package/lighthouse-core/gather/gatherers/dobetterweb/appcache.js +0 -34
  202. package/lighthouse-core/gather/gatherers/http-redirect.js +0 -46
  203. package/lighthouse-core/lib/i18n/locales/en-US.ctc.json +0 -6699
  204. package/lighthouse-core/lib/i18n/locales/en-XL.ctc.json +0 -6672
  205. package/lighthouse-core/scripts/package.json +0 -4
  206. package/report/clients/psi.js +0 -158
  207. package/report/test/clients/psi-test.js +0 -143
  208. package/shared/localization/locales/en-US.ctc.json +0 -6877
  209. package/shared/localization/locales/en-XL.ctc.json +0 -6852
@@ -7,32 +7,68 @@
7
7
  import {createContext, FunctionComponent} from 'preact';
8
8
  import {useContext, useMemo} from 'preact/hooks';
9
9
 
10
+ import {formatMessage} from '../../../shared/localization/format';
10
11
  import {I18n} from '../../../report/renderer/i18n';
11
12
  import {UIStrings} from './ui-strings';
12
- import {useLocale} from '../util';
13
+ import {useFlowResult} from '../util';
13
14
  import strings from './localized-strings';
15
+ import {Util} from '../../../report/renderer/util';
14
16
 
15
- const I18nContext = createContext<I18n<typeof UIStrings>|undefined>(undefined);
17
+ const I18nContext = createContext(new I18n('en-US', {...Util.UIStrings, ...UIStrings}));
16
18
 
17
- export function useI18n() {
18
- const i18n = useContext(I18nContext);
19
- if (!i18n) throw Error('i18n was not initialized');
20
- return i18n;
19
+ function useLhrLocale() {
20
+ const flowResult = useFlowResult();
21
+ const firstLhr = flowResult.steps[0].lhr;
22
+ const locale = firstLhr.configSettings.locale;
23
+
24
+ if (flowResult.steps.some(step => step.lhr.configSettings.locale !== locale)) {
25
+ console.warn('LHRs have inconsistent locales');
26
+ }
27
+
28
+ return {
29
+ locale,
30
+ lhrStrings: firstLhr.i18n.rendererFormattedStrings,
31
+ };
32
+ }
33
+
34
+ function useI18n() {
35
+ return useContext(I18nContext);
21
36
  }
22
37
 
23
- export function useUIStrings() {
38
+ function useLocalizedStrings() {
24
39
  const i18n = useI18n();
25
40
  return i18n.strings;
26
41
  }
27
42
 
28
- export const I18nProvider: FunctionComponent = ({children}) => {
29
- const locale = useLocale();
30
- const i18n = useMemo(() => new I18n(locale, {
31
- // Set missing renderer strings to default (english) values.
32
- ...UIStrings,
33
- // `strings` is generated in build/build-report.js
34
- ...strings[locale],
35
- }), [locale]);
43
+ function useStringFormatter() {
44
+ const {locale} = useLhrLocale();
45
+ return (str: string, values?: Record<string, string|number>) => {
46
+ return formatMessage(str, values, locale);
47
+ };
48
+ }
49
+
50
+ const I18nProvider: FunctionComponent = ({children}) => {
51
+ const {locale, lhrStrings} = useLhrLocale();
52
+
53
+ const i18n = useMemo(() => {
54
+ const i18n = new I18n(locale, {
55
+ // Set any missing lhr strings to default (english) values.
56
+ ...Util.UIStrings,
57
+ // Preload with strings from the first lhr.
58
+ // Used for legacy report components imported into the flow report.
59
+ ...lhrStrings,
60
+ // Set any missing flow strings to default (english) values.
61
+ ...UIStrings,
62
+ // `strings` is generated in build/build-report.js
63
+ ...strings[locale],
64
+ });
65
+
66
+ // Initialize renderer util i18n for strings rendered in wrapped components.
67
+ // TODO: Don't attach global i18n to `Util`.
68
+ Util.i18n = i18n;
69
+
70
+ return i18n;
71
+ }, [locale, lhrStrings]);
36
72
 
37
73
  return (
38
74
  <I18nContext.Provider value={i18n}>
@@ -41,4 +77,9 @@ export const I18nProvider: FunctionComponent = ({children}) => {
41
77
  );
42
78
  };
43
79
 
44
-
80
+ export {
81
+ useI18n,
82
+ useLocalizedStrings,
83
+ useStringFormatter,
84
+ I18nProvider,
85
+ };
@@ -57,6 +57,30 @@ export const UIStrings = {
57
57
  ratingFail: 'Poor',
58
58
  /** Rating indicating that a report category rating could not be calculated because of an error. */
59
59
  ratingError: 'Error',
60
+ /**
61
+ * @description Label indicating the number of Lighthouse reports that evaluate a web page as it loads.
62
+ * @example {2} numNavigation
63
+ */
64
+ navigationReportCount: `{numNavigation, plural,
65
+ =1 {{numNavigation} navigation report}
66
+ other {{numNavigation} navigation reports}
67
+ }`,
68
+ /**
69
+ * @description Label indicating the number of Lighthouse reports that evaluate a web page over a period of time.
70
+ * @example {2} numTimespan
71
+ */
72
+ timespanReportCount: `{numTimespan, plural,
73
+ =1 {{numTimespan} timespan report}
74
+ other {{numTimespan} timespan reports}
75
+ }`,
76
+ /**
77
+ * @description Label indicating the number of Lighthouse reports that evaluate a web page at a single point in time.
78
+ * @example {2} numSnapshot
79
+ */
80
+ snapshotReportCount: `{numSnapshot, plural,
81
+ =1 {{numSnapshot} snapshot report}
82
+ other {{numSnapshot} snapshot reports}
83
+ }`,
60
84
  /** Label for a button that saves a Lighthouse report to disk. */
61
85
  save: 'Save',
62
86
  /** Label for a button that toggles the help modal with explanations on how to interpret the Lighthouse flow report. */
@@ -83,4 +107,30 @@ export const UIStrings = {
83
107
  helpUseCaseSnapshot1: 'Find accessibility issues in single page applications or complex forms.',
84
108
  /** Example use case for how Lighthouse can be applied in practice. Appears in a list with other examples. */
85
109
  helpUseCaseSnapshot2: 'Evaluate best practices of menus and UI elements hidden behind interaction.',
110
+ /**
111
+ * @description Label indicating the number of Lighthouse audits that passed.
112
+ * @example {2} numPassed
113
+ */
114
+ passedAuditCount: `{numPassed, plural,
115
+ =1 {{numPassed} audit passed}
116
+ other {{numPassed} audits passed}
117
+ }`,
118
+ /**
119
+ * @description Label indicating the number of Lighthouse audits that are possible to pass for a page.
120
+ * @example {2} numPassableAudits
121
+ */
122
+ passableAuditCount: `{numPassableAudits, plural,
123
+ =1 {{numPassableAudits} passable audit}
124
+ other {{numPassableAudits} passable audits}
125
+ }`,
126
+ /**
127
+ * @description Label indicating the number of Lighthouse audits that are informative.
128
+ * @example {2} numInformative
129
+ */
130
+ informativeAuditCount: `{numInformative, plural,
131
+ =1 {{numInformative} informative audit}
132
+ other {{numInformative} informative audits}
133
+ }`,
134
+ /** Label for a list of Lighthouse audits that are the most impactful. */
135
+ highestImpact: 'Highest impact',
86
136
  };
@@ -8,7 +8,7 @@ import {FunctionComponent} from 'preact';
8
8
 
9
9
  /* eslint-disable max-len */
10
10
 
11
- export const SummaryIcon: FunctionComponent = () => {
11
+ const SummaryIcon: FunctionComponent = () => {
12
12
  return (
13
13
  <svg width="14" viewBox="0 0 18 16" fill="none" role="img">
14
14
  <path fill-rule="evenodd" clip-rule="evenodd" d="M0 2C0 1.17 0.67 0.5 1.5 0.5C2.33 0.5 3 1.17 3 2C3 2.83 2.33 3.5 1.5 3.5C0.67 3.5 0 2.83 0 2ZM0 8C0 7.17 0.67 6.5 1.5 6.5C2.33 6.5 3 7.17 3 8C3 8.83 2.33 9.5 1.5 9.5C0.67 9.5 0 8.83 0 8ZM1.5 12.5C0.67 12.5 0 13.18 0 14C0 14.82 0.68 15.5 1.5 15.5C2.32 15.5 3 14.82 3 14C3 13.18 2.33 12.5 1.5 12.5ZM18 15H5V13H18V15ZM5 9H18V7H5V9ZM5 3V1H18V3H5Z" fill="currentColor"/>
@@ -16,7 +16,7 @@ export const SummaryIcon: FunctionComponent = () => {
16
16
  );
17
17
  };
18
18
 
19
- export const NavigationIcon: FunctionComponent = () => {
19
+ const NavigationIcon: FunctionComponent = () => {
20
20
  return (
21
21
  <svg
22
22
  width="16"
@@ -36,7 +36,7 @@ export const NavigationIcon: FunctionComponent = () => {
36
36
  );
37
37
  };
38
38
 
39
- export const TimespanIcon: FunctionComponent = () => {
39
+ const TimespanIcon: FunctionComponent = () => {
40
40
  return (
41
41
  <svg
42
42
  width="16"
@@ -60,7 +60,7 @@ export const TimespanIcon: FunctionComponent = () => {
60
60
  );
61
61
  };
62
62
 
63
- export const SnapshotIcon: FunctionComponent = () => {
63
+ const SnapshotIcon: FunctionComponent = () => {
64
64
  return (
65
65
  <svg
66
66
  xmlns="http://www.w3.org/2000/svg"
@@ -79,7 +79,7 @@ export const SnapshotIcon: FunctionComponent = () => {
79
79
  );
80
80
  };
81
81
 
82
- export const CloseIcon: FunctionComponent = () => {
82
+ const CloseIcon: FunctionComponent = () => {
83
83
  return (
84
84
  <svg
85
85
  xmlns="http://www.w3.org/2000/svg"
@@ -96,7 +96,7 @@ export const CloseIcon: FunctionComponent = () => {
96
96
  );
97
97
  };
98
98
 
99
- export const EnvIcon: FunctionComponent = () => {
99
+ const EnvIcon: FunctionComponent = () => {
100
100
  return (
101
101
  <svg width="15" height="12" viewBox="0 0 15 12" fill="none" role="img">
102
102
  <path fill-rule="evenodd" clip-rule="evenodd" d="M3.33317 2.00008H13.9998V0.666748H3.33317C2.59984 0.666748 1.99984 1.26675 1.99984 2.00008V9.33341H0.666504V11.3334H7.99984V9.33341H3.33317V2.00008ZM13.9998 3.33341H9.99984C9.63317 3.33341 9.33317 3.63341 9.33317 4.00008V10.6667C9.33317 11.0334 9.63317 11.3334 9.99984 11.3334H13.9998C14.3665 11.3334 14.6665 11.0334 14.6665 10.6667V4.00008C14.6665 3.63341 14.3665 3.33341 13.9998 3.33341ZM10.6665 9.33341H13.3332V4.66675H10.6665V9.33341Z" fill="currentColor"/>
@@ -104,7 +104,15 @@ export const EnvIcon: FunctionComponent = () => {
104
104
  );
105
105
  };
106
106
 
107
- export const CpuIcon: FunctionComponent = () => {
107
+ const NetworkIcon: FunctionComponent = () => {
108
+ return (
109
+ <svg width="16" height="11" viewBox="0 0 16 11" fill="none" role="img">
110
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M0.666687 3.26663L2.00002 4.59997C3.92002 2.67997 6.52669 1.87997 9.02002 2.18663L9.81335 0.399966C6.59335 -0.173367 3.16002 0.779966 0.666687 3.26663ZM10.6 0.599966C10.4867 0.599966 10.3867 0.659966 10.3267 0.753299L10.28 0.853299L6.82669 8.61996C6.72002 8.8133 6.65335 9.02663 6.65335 9.25996C6.65335 9.99996 7.25335 10.6 7.99335 10.6C8.63335 10.6 9.17335 10.1466 9.30002 9.53996L9.30669 9.51997L10.9334 0.933299C10.9334 0.746633 10.7867 0.599966 10.6 0.599966ZM15.3334 3.26663L14 4.59997C13.1867 3.78663 12.2534 3.17997 11.2534 2.76663L11.6067 0.886633C12.9667 1.38663 14.24 2.1733 15.3334 3.26663ZM11.3334 7.26663L12.6667 5.9333C12.1334 5.39997 11.5334 4.98663 10.8934 4.6733L10.5267 6.61997C10.8067 6.79997 11.08 7.0133 11.3334 7.26663ZM4.66669 7.26663L3.33335 5.9333C4.67335 4.5933 6.45335 3.95997 8.20669 4.0133L7.35335 5.9333C6.37335 6.0733 5.42002 6.5133 4.66669 7.26663Z" fill="currentColor"/>
111
+ </svg>
112
+ );
113
+ };
114
+
115
+ const CpuIcon: FunctionComponent = () => {
108
116
  return (
109
117
  <svg width="16" height="16" viewBox="0 0 16 16" fill="none" role="img">
110
118
  <path fill-rule="evenodd" clip-rule="evenodd" d="M15.5 7.16667V5.5H13.8333V3.83333C13.8333 2.91667 13.0833 2.16667 12.1667 2.16667H10.5V0.5H8.83333V2.16667H7.16667V0.5H5.5V2.16667H3.83333C2.91667 2.16667 2.16667 2.91667 2.16667 3.83333V5.5H0.5V7.16667H2.16667V8.83333H0.5V10.5H2.16667V12.1667C2.16667 13.0833 2.91667 13.8333 3.83333 13.8333H5.5V15.5H7.16667V13.8333H8.83333V15.5H10.5V13.8333H12.1667C13.0833 13.8333 13.8333 13.0833 13.8333 12.1667V10.5H15.5V8.83333H13.8333V7.16667H15.5ZM10.5 5.5H5.5V10.5H10.5V5.5ZM3.83333 12.1667H12.1667V3.83333H3.83333V12.1667Z" fill="currentColor"/>
@@ -112,7 +120,7 @@ export const CpuIcon: FunctionComponent = () => {
112
120
  );
113
121
  };
114
122
 
115
- export const HamburgerIcon: FunctionComponent = () => {
123
+ const HamburgerIcon: FunctionComponent = () => {
116
124
  return (
117
125
  <svg viewBox="0 0 18 12" width="18" height="12" role="img">
118
126
  <rect width="18" height="2" fill="currentColor"></rect>
@@ -122,3 +130,23 @@ export const HamburgerIcon: FunctionComponent = () => {
122
130
  );
123
131
  };
124
132
 
133
+ const InfoIcon: FunctionComponent = () => {
134
+ return (
135
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none">
136
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M13 7C13 10.3137 10.3137 13 7 13C3.68629 13 1 10.3137 1 7C1 3.68629 3.68629 1 7 1C10.3137 1 13 3.68629 13 7ZM14 7C14 10.866 10.866 14 7 14C3.13401 14 0 10.866 0 7C0 3.13401 3.13401 0 7 0C10.866 0 14 3.13401 14 7ZM7.66658 11H6.33325V9.66667H7.66658V11ZM4.33325 5.66667C4.33325 4.19333 5.52659 3 6.99992 3C8.47325 3 9.66658 4.19333 9.66658 5.66667C9.66658 6.52194 9.1399 6.98221 8.62709 7.43036C8.1406 7.85551 7.66658 8.26975 7.66658 9H6.33325C6.33325 7.78582 6.96133 7.30439 7.51355 6.88112C7.94674 6.54907 8.33325 6.25281 8.33325 5.66667C8.33325 4.93333 7.73325 4.33333 6.99992 4.33333C6.26658 4.33333 5.66658 4.93333 5.66658 5.66667H4.33325Z" fill="currentColor"/>
137
+ </svg>
138
+ );
139
+ };
140
+
141
+ export {
142
+ SummaryIcon,
143
+ NavigationIcon,
144
+ TimespanIcon,
145
+ SnapshotIcon,
146
+ CloseIcon,
147
+ EnvIcon,
148
+ NetworkIcon,
149
+ CpuIcon,
150
+ HamburgerIcon,
151
+ InfoIcon,
152
+ };
@@ -7,7 +7,7 @@
7
7
  import {FunctionComponent} from 'preact';
8
8
 
9
9
  import {FlowSegment} from '../common';
10
- import {classNames, useCurrentLhr, useFlowResult} from '../util';
10
+ import {classNames, useHashState, useFlowResult} from '../util';
11
11
  import {Separator} from '../common';
12
12
 
13
13
  const SidebarFlowStep: FunctionComponent<{
@@ -41,7 +41,7 @@ const SidebarFlowSeparator: FunctionComponent = () => {
41
41
 
42
42
  export const SidebarFlow: FunctionComponent = () => {
43
43
  const flowResult = useFlowResult();
44
- const currentLhr = useCurrentLhr();
44
+ const hashState = useHashState();
45
45
 
46
46
  return (
47
47
  <div className="SidebarFlow">
@@ -61,7 +61,7 @@ export const SidebarFlow: FunctionComponent = () => {
61
61
  mode={lhr.gatherMode}
62
62
  href={url.href}
63
63
  label={name}
64
- isCurrent={index === (currentLhr && currentLhr.index)}
64
+ isCurrent={index === (hashState && hashState.index)}
65
65
  />
66
66
  </>;
67
67
  })
@@ -6,22 +6,23 @@
6
6
 
7
7
  import {FunctionComponent} from 'preact';
8
8
 
9
+ import {Util} from '../../../report/renderer/util';
9
10
  import {Separator} from '../common';
10
- import {useI18n, useUIStrings} from '../i18n/i18n';
11
- import {CpuIcon, EnvIcon, SummaryIcon} from '../icons';
12
- import {classNames, useCurrentLhr, useFlowResult} from '../util';
11
+ import {useI18n, useLocalizedStrings} from '../i18n/i18n';
12
+ import {CpuIcon, EnvIcon, NetworkIcon, SummaryIcon} from '../icons';
13
+ import {classNames, useHashState, useFlowResult} from '../util';
13
14
  import {SidebarFlow} from './flow';
14
15
 
15
- export const SidebarSummary: FunctionComponent = () => {
16
- const currentLhr = useCurrentLhr();
17
- const strings = useUIStrings();
16
+ const SidebarSummary: FunctionComponent = () => {
17
+ const hashState = useHashState();
18
+ const strings = useLocalizedStrings();
18
19
 
19
20
  const url = new URL(location.href);
20
21
  url.hash = '#';
21
22
  return (
22
23
  <a
23
24
  href={url.href}
24
- className={classNames('SidebarSummary', {'Sidebar--current': currentLhr === null})}
25
+ className={classNames('SidebarSummary', {'Sidebar--current': hashState === null})}
25
26
  data-testid="SidebarSummary"
26
27
  >
27
28
  <div className="SidebarSummary__icon">
@@ -32,20 +33,33 @@ export const SidebarSummary: FunctionComponent = () => {
32
33
  );
33
34
  };
34
35
 
35
- const SidebarRuntimeSettings: FunctionComponent<{settings: LH.ConfigSettings}> = ({settings}) => {
36
- const strings = useUIStrings();
36
+ const SidebarRuntimeSettings: FunctionComponent<{settings: LH.ConfigSettings}> =
37
+ ({settings}) => {
38
+ const strings = useLocalizedStrings();
39
+ const env = Util.getEmulationDescriptions(settings);
37
40
 
38
41
  return (
39
42
  <div className="SidebarRuntimeSettings">
40
- <div className="SidebarRuntimeSettings__item">
43
+ <div className="SidebarRuntimeSettings__item" title={strings.runtimeSettingsDevice}>
41
44
  <div className="SidebarRuntimeSettings__item--icon">
42
45
  <EnvIcon/>
43
46
  </div>
44
47
  {
45
- settings.formFactor === 'desktop' ? strings.desktop : strings.mobile
48
+ env.deviceEmulation
46
49
  }
47
50
  </div>
48
- <div className="SidebarRuntimeSettings__item">
51
+ <div
52
+ className="SidebarRuntimeSettings__item"
53
+ title={strings.runtimeSettingsNetworkThrottling}
54
+ >
55
+ <div className="SidebarRuntimeSettings__item--icon">
56
+ <NetworkIcon/>
57
+ </div>
58
+ {
59
+ env.summary
60
+ }
61
+ </div>
62
+ <div className="SidebarRuntimeSettings__item" title={strings.runtimeSettingsCPUThrottling}>
49
63
  <div className="SidebarRuntimeSettings__item--icon">
50
64
  <CpuIcon/>
51
65
  </div>
@@ -57,7 +71,7 @@ const SidebarRuntimeSettings: FunctionComponent<{settings: LH.ConfigSettings}> =
57
71
  );
58
72
  };
59
73
 
60
- export const SidebarHeader: FunctionComponent<{title: string, date: string}> = ({title, date}) => {
74
+ const SidebarHeader: FunctionComponent<{title: string, date: string}> = ({title, date}) => {
61
75
  const i18n = useI18n();
62
76
  return (
63
77
  <div className="SidebarHeader">
@@ -67,7 +81,7 @@ export const SidebarHeader: FunctionComponent<{title: string, date: string}> = (
67
81
  );
68
82
  };
69
83
 
70
- export const Sidebar: FunctionComponent = () => {
84
+ const Sidebar: FunctionComponent = () => {
71
85
  const flowResult = useFlowResult();
72
86
  const firstLhr = flowResult.steps[0].lhr;
73
87
  return (
@@ -82,3 +96,10 @@ export const Sidebar: FunctionComponent = () => {
82
96
  </div>
83
97
  );
84
98
  };
99
+
100
+ export {
101
+ SidebarSummary,
102
+ SidebarRuntimeSettings,
103
+ SidebarHeader,
104
+ Sidebar,
105
+ };
@@ -9,10 +9,15 @@ import {FunctionComponent} from 'preact';
9
9
  import {Util} from '../../../report/renderer/util';
10
10
  import {Separator} from '../common';
11
11
  import {CategoryScore} from '../wrappers/category-score';
12
- import {useUIStrings} from '../i18n/i18n';
12
+ import {useI18n, useStringFormatter, useLocalizedStrings} from '../i18n/i18n';
13
+ import {Markdown} from '../wrappers/markdown';
13
14
 
14
15
  import type {UIStringsType} from '../i18n/ui-strings';
15
16
 
17
+ const MAX_TOOLTIP_AUDITS = 2;
18
+
19
+ type ScoredAuditRef = LH.ReportResult.AuditRef & {result: {score: number}};
20
+
16
21
  function getGatherModeLabel(gatherMode: LH.Result.GatherMode, strings: UIStringsType) {
17
22
  switch (gatherMode) {
18
23
  case 'navigation': return strings.navigationReport;
@@ -30,11 +35,73 @@ function getCategoryRating(rating: string, strings: UIStringsType) {
30
35
  }
31
36
  }
32
37
 
33
- export const SummaryTooltip: FunctionComponent<{
38
+ function getScoreToBeGained(audit: ScoredAuditRef): number {
39
+ return audit.weight * (1 - audit.result.score);
40
+ }
41
+
42
+ function getOverallSavings(audit: LH.ReportResult.AuditRef): number {
43
+ return (
44
+ audit.result.details &&
45
+ audit.result.details.type === 'opportunity' &&
46
+ audit.result.details.overallSavingsMs
47
+ ) || 0;
48
+ }
49
+
50
+ const SummaryTooltipAudit: FunctionComponent<{audit: LH.ReportResult.AuditRef}> = ({audit}) => {
51
+ const rating = Util.calculateRating(audit.result.score, audit.result.scoreDisplayMode);
52
+ return (
53
+ <div className={`SummaryTooltipAudit SummaryTooltipAudit--${rating}`}>
54
+ <Markdown text={audit.result.title}/>
55
+ </div>
56
+ );
57
+ };
58
+
59
+ const SummaryTooltipAudits: FunctionComponent<{category: LH.ReportResult.Category}> =
60
+ ({category}) => {
61
+ const strings = useLocalizedStrings();
62
+
63
+ function isRelevantAudit(audit: LH.ReportResult.AuditRef): audit is ScoredAuditRef {
64
+ return audit.result.score !== null &&
65
+ // Metrics should not be displayed in this group.
66
+ audit.group !== 'metrics' &&
67
+ // Audits in performance group "hidden" should not be counted.
68
+ (audit.group !== 'hidden' || category.id !== 'performance') &&
69
+ // We don't want unweighted audits except for opportunities with potential savings.
70
+ (audit.weight > 0 || getOverallSavings(audit) > 0) &&
71
+ // Passing audits should never be high impact.
72
+ !Util.showAsPassed(audit.result);
73
+ }
74
+
75
+ const audits = category.auditRefs
76
+ .filter(isRelevantAudit)
77
+ .sort((a, b) => {
78
+ // Remaining score should always be 0 for perf opportunities because weight is 0.
79
+ // In that case, we want to sort by `overallSavingsMs`.
80
+ const remainingScoreA = getScoreToBeGained(a);
81
+ const remainingScoreB = getScoreToBeGained(b);
82
+ if (remainingScoreA !== remainingScoreB) return remainingScoreB - remainingScoreA;
83
+ return getOverallSavings(b) - getOverallSavings(a);
84
+ })
85
+ .splice(0, MAX_TOOLTIP_AUDITS);
86
+ if (!audits.length) return null;
87
+
88
+ return (
89
+ <div className="SummaryTooltipAudits">
90
+ <div className="SummaryTooltipAudits__title">{strings.highestImpact}</div>
91
+ {
92
+ audits.map(audit => <SummaryTooltipAudit key={audit.id} audit={audit}/>)
93
+ }
94
+ </div>
95
+ );
96
+ };
97
+
98
+ const SummaryTooltip: FunctionComponent<{
34
99
  category: LH.ReportResult.Category,
35
- gatherMode: LH.Result.GatherMode
36
- }> = ({category, gatherMode}) => {
37
- const strings = useUIStrings();
100
+ gatherMode: LH.Result.GatherMode,
101
+ url: string,
102
+ }> = ({category, gatherMode, url}) => {
103
+ const strings = useLocalizedStrings();
104
+ const str_ = useStringFormatter();
38
105
  const {
39
106
  numPassed,
40
107
  numPassableAudits,
@@ -42,14 +109,17 @@ export const SummaryTooltip: FunctionComponent<{
42
109
  totalWeight,
43
110
  } = Util.calculateCategoryFraction(category);
44
111
 
112
+ const i18n = useI18n();
45
113
  const displayAsFraction = Util.shouldDisplayAsFraction(gatherMode);
46
- const rating = displayAsFraction ?
47
- Util.calculateRating(numPassed / numPassableAudits) :
48
- Util.calculateRating(category.score);
114
+ const score = displayAsFraction ?
115
+ numPassed / numPassableAudits :
116
+ category.score;
117
+ const rating = score === null ? 'error' : Util.calculateRating(score);
49
118
 
50
119
  return (
51
120
  <div className="SummaryTooltip">
52
121
  <div className="SummaryTooltip__title">{getGatherModeLabel(gatherMode, strings)}</div>
122
+ <div className="SummaryTooltip__url">{url}</div>
53
123
  <Separator/>
54
124
  <div className="SummaryTooltip__category">
55
125
  <div className="SummaryTooltip__category-title">
@@ -60,37 +130,35 @@ export const SummaryTooltip: FunctionComponent<{
60
130
  <div className={`SummaryTooltip__rating SummaryTooltip__rating--${rating}`}>
61
131
  <span>{getCategoryRating(rating, strings)}</span>
62
132
  {
63
- !displayAsFraction && category.score && <>
133
+ !displayAsFraction && category.score !== null && <>
64
134
  <span> · </span>
65
- <span>{category.score * 100}</span>
135
+ <span>{i18n.formatNumber(category.score * 100)}</span>
66
136
  </>
67
137
  }
68
138
  </div>
69
139
  }
70
140
  </div>
71
141
  <div className="SummaryTooltip__fraction">
72
- {
73
- // TODO(FLOW-I18N): Placeholder format.
74
- `${numPassed} audits passed / ${numPassableAudits} passable audits`
75
- }
142
+ <span>{str_(strings.passedAuditCount, {numPassed})}</span>
143
+ <span> / </span>
144
+ <span>{str_(strings.passableAuditCount, {numPassableAudits})}</span>
76
145
  </div>
77
- {
78
- // TODO(FLOW-I18N): Placeholder format.
79
- numInformative ?
80
- <div className="SummaryTooltip__informative">
81
- {`${numInformative} informative audits`}
82
- </div> :
83
- null
146
+ {numInformative !== 0 &&
147
+ <div className="SummaryTooltip__informative">
148
+ {str_(strings.informativeAuditCount, {numInformative})}
149
+ </div>
84
150
  }
151
+ <SummaryTooltipAudits category={category}/>
85
152
  </div>
86
153
  );
87
154
  };
88
155
 
89
- export const SummaryCategory: FunctionComponent<{
156
+ const SummaryCategory: FunctionComponent<{
90
157
  category: LH.ReportResult.Category|undefined,
91
158
  href: string,
92
159
  gatherMode: LH.Result.GatherMode,
93
- }> = ({category, href, gatherMode}) => {
160
+ finalUrl: string,
161
+ }> = ({category, href, gatherMode, finalUrl}) => {
94
162
  return (
95
163
  <div className="SummaryCategory">
96
164
  {
@@ -101,10 +169,15 @@ export const SummaryCategory: FunctionComponent<{
101
169
  href={href}
102
170
  gatherMode={gatherMode}
103
171
  />
104
- <SummaryTooltip category={category} gatherMode={gatherMode}/>
172
+ <SummaryTooltip category={category} gatherMode={gatherMode} url={finalUrl}/>
105
173
  </div> :
106
174
  <div className="SummaryCategory__null" data-testid="SummaryCategory__null"/>
107
175
  }
108
176
  </div>
109
177
  );
110
178
  };
179
+
180
+ export {
181
+ SummaryTooltip,
182
+ SummaryCategory,
183
+ };
@@ -11,13 +11,13 @@ import {FlowSegment, FlowStepThumbnail, Separator} from '../common';
11
11
  import {getModeDescription, useFlowResult} from '../util';
12
12
  import {Util} from '../../../report/renderer/util';
13
13
  import {SummaryCategory} from './category';
14
- import {useUIStrings} from '../i18n/i18n';
14
+ import {useStringFormatter, useLocalizedStrings} from '../i18n/i18n';
15
15
 
16
16
  const DISPLAYED_CATEGORIES = ['performance', 'accessibility', 'best-practices', 'seo'];
17
- const THUMBNAIL_WIDTH = 50;
17
+ const THUMBNAIL_WIDTH = 40;
18
18
 
19
19
  const SummaryNavigationHeader: FunctionComponent<{lhr: LH.Result}> = ({lhr}) => {
20
- const strings = useUIStrings();
20
+ const strings = useLocalizedStrings();
21
21
 
22
22
  return (
23
23
  <div className="SummaryNavigationHeader" data-testid="SummaryNavigationHeader">
@@ -44,13 +44,13 @@ const SummaryNavigationHeader: FunctionComponent<{lhr: LH.Result}> = ({lhr}) =>
44
44
  /**
45
45
  * The div should behave like a JSX <>...</>. This still allows us to identify "rows" with CSS selectors.
46
46
  */
47
- export const SummaryFlowStep: FunctionComponent<{
47
+ const SummaryFlowStep: FunctionComponent<{
48
48
  lhr: LH.Result,
49
49
  label: string,
50
50
  hashIndex: number,
51
51
  }> = ({lhr, label, hashIndex}) => {
52
52
  const reportResult = useMemo(() => Util.prepareReportResult(lhr), [lhr]);
53
- const strings = useUIStrings();
53
+ const strings = useLocalizedStrings();
54
54
  const modeDescription = getModeDescription(lhr.gatherMode, strings);
55
55
 
56
56
  return (
@@ -63,7 +63,7 @@ export const SummaryFlowStep: FunctionComponent<{
63
63
  <Separator/>
64
64
  </div>
65
65
  }
66
- <FlowStepThumbnail reportResult={reportResult} width={THUMBNAIL_WIDTH}/>
66
+ <FlowStepThumbnail lhr={lhr} width={THUMBNAIL_WIDTH}/>
67
67
  <FlowSegment mode={lhr.gatherMode}/>
68
68
  <div className="SummaryFlowStep__label">
69
69
  <div className="SummaryFlowStep__mode">{modeDescription}</div>
@@ -76,6 +76,7 @@ export const SummaryFlowStep: FunctionComponent<{
76
76
  category={reportResult.categories[c]}
77
77
  href={`#index=${hashIndex}&anchor=${c}`}
78
78
  gatherMode={lhr.gatherMode}
79
+ finalUrl={lhr.finalUrl}
79
80
  />
80
81
  ))
81
82
  }
@@ -105,9 +106,10 @@ const SummaryFlow: FunctionComponent = () => {
105
106
  );
106
107
  };
107
108
 
108
- export const SummaryHeader: FunctionComponent = () => {
109
+ const SummaryHeader: FunctionComponent = () => {
109
110
  const flowResult = useFlowResult();
110
- const strings = useUIStrings();
111
+ const strings = useLocalizedStrings();
112
+ const str_ = useStringFormatter();
111
113
 
112
114
  let numNavigation = 0;
113
115
  let numTimespan = 0;
@@ -126,11 +128,10 @@ export const SummaryHeader: FunctionComponent = () => {
126
128
  }
127
129
  }
128
130
 
129
- // TODO(FLOW-I18N): Placeholder format.
130
131
  const subtitleCounts = [];
131
- if (numNavigation) subtitleCounts.push(`${numNavigation} navigation reports`);
132
- if (numTimespan) subtitleCounts.push(`${numTimespan} timespan reports`);
133
- if (numSnapshot) subtitleCounts.push(`${numSnapshot} snapshot reports`);
132
+ if (numNavigation) subtitleCounts.push(str_(strings.navigationReportCount, {numNavigation}));
133
+ if (numTimespan) subtitleCounts.push(str_(strings.timespanReportCount, {numTimespan}));
134
+ if (numSnapshot) subtitleCounts.push(str_(strings.snapshotReportCount, {numSnapshot}));
134
135
  const subtitle = subtitleCounts.join(' · ');
135
136
 
136
137
  return (
@@ -150,8 +151,8 @@ const SummarySectionHeader: FunctionComponent = ({children}) => {
150
151
  );
151
152
  };
152
153
 
153
- export const Summary: FunctionComponent = () => {
154
- const strings = useUIStrings();
154
+ const Summary: FunctionComponent = () => {
155
+ const strings = useLocalizedStrings();
155
156
 
156
157
  return (
157
158
  <div className="Summary" data-testid="Summary">
@@ -162,3 +163,9 @@ export const Summary: FunctionComponent = () => {
162
163
  </div>
163
164
  );
164
165
  };
166
+
167
+ export {
168
+ SummaryFlowStep,
169
+ SummaryHeader,
170
+ Summary,
171
+ };