lighthouse 8.6.0-dev.20211029 → 8.6.0-dev.20211102

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 (116) hide show
  1. package/dist/report/flow.js +14 -14
  2. package/dist/report/standalone.js +12 -12
  3. package/flow-report/.eslintrc.cjs +2 -0
  4. package/flow-report/assets/styles.css +1 -1
  5. package/flow-report/src/app.tsx +10 -11
  6. package/flow-report/src/common.tsx +22 -9
  7. package/flow-report/src/header.tsx +3 -3
  8. package/flow-report/src/i18n/i18n.tsx +10 -6
  9. package/flow-report/src/icons.tsx +21 -9
  10. package/flow-report/src/sidebar/flow.tsx +3 -3
  11. package/flow-report/src/sidebar/sidebar.tsx +12 -6
  12. package/flow-report/src/summary/category.tsx +9 -4
  13. package/flow-report/src/summary/summary.tsx +9 -3
  14. package/flow-report/src/util.ts +31 -19
  15. package/flow-report/src/wrappers/report-renderer.tsx +9 -3
  16. package/flow-report/src/wrappers/report.tsx +17 -7
  17. package/flow-report/test/common-test.tsx +25 -10
  18. package/flow-report/test/header-test.tsx +6 -6
  19. package/flow-report/test/setup/env-setup.ts +1 -1
  20. package/flow-report/test/summary/category-test.tsx +5 -5
  21. package/flow-report/test/util-test.tsx +32 -11
  22. package/lighthouse-cli/.eslintrc.cjs +2 -0
  23. package/lighthouse-cli/cli-flags.js +1 -1
  24. package/lighthouse-cli/test/smokehouse/lighthouse-runners/bundle.js +4 -2
  25. package/lighthouse-cli/test/smokehouse/smokehouse.js +1 -1
  26. package/lighthouse-core/audits/audit.js +1 -1
  27. package/lighthouse-core/computed/metrics/lantern-total-blocking-time.js +3 -4
  28. package/lighthouse-core/computed/metrics/tbt-utils.js +57 -0
  29. package/lighthouse-core/computed/metrics/total-blocking-time.js +3 -53
  30. package/lighthouse-core/computed/resource-summary.js +1 -1
  31. package/lighthouse-core/config/config-helpers.js +20 -10
  32. package/lighthouse-core/config/default-config.js +59 -55
  33. package/lighthouse-core/fraggle-rock/config/default-config.js +1 -1
  34. package/lighthouse-core/fraggle-rock/gather/base-gatherer.js +6 -1
  35. package/lighthouse-core/gather/connections/cri.js +1 -1
  36. package/lighthouse-core/gather/gatherers/dobetterweb/response-compression.js +1 -0
  37. package/lighthouse-core/lib/asset-saver.js +4 -1
  38. package/lighthouse-core/lib/lh-env.js +2 -0
  39. package/lighthouse-core/lib/proto-preprocessor.js +14 -2
  40. package/lighthouse-core/lib/tappable-rects.js +8 -15
  41. package/lighthouse-core/lib/url-shim.js +1 -1
  42. package/lighthouse-core/runner.js +1 -1
  43. package/lighthouse-core/util-commonjs.js +25 -11
  44. package/package.json +11 -8
  45. package/report/.eslintrc.cjs +2 -0
  46. package/report/assets/styles.css +226 -250
  47. package/report/assets/templates.html +19 -29
  48. package/report/clients/standalone.js +0 -2
  49. package/report/renderer/category-renderer.js +21 -17
  50. package/report/renderer/components.js +21 -54
  51. package/report/renderer/crc-details-renderer.js +15 -12
  52. package/report/renderer/element-screenshot-renderer.js +2 -2
  53. package/report/renderer/open-tab.js +9 -3
  54. package/report/renderer/performance-category-renderer.js +51 -24
  55. package/report/renderer/report-renderer.js +2 -1
  56. package/report/renderer/report-ui-features.js +2 -3
  57. package/report/renderer/util.js +26 -11
  58. package/report/test/renderer/category-renderer-test.js +2 -2
  59. package/report/test/renderer/performance-category-renderer-test.js +35 -9
  60. package/report/test/renderer/report-renderer-test.js +1 -1
  61. package/report/test/renderer/report-ui-features-test.js +10 -9
  62. package/report/test/renderer/util-test.js +13 -31
  63. package/report/test-assets/faux-psi.js +5 -5
  64. package/shared/localization/format.js +9 -4
  65. package/shared/localization/locales/ar-XB.json +0 -15
  66. package/shared/localization/locales/ar.json +0 -15
  67. package/shared/localization/locales/bg.json +0 -15
  68. package/shared/localization/locales/ca.json +0 -15
  69. package/shared/localization/locales/cs.json +0 -15
  70. package/shared/localization/locales/da.json +0 -15
  71. package/shared/localization/locales/de.json +0 -15
  72. package/shared/localization/locales/el.json +0 -15
  73. package/shared/localization/locales/en-GB.json +0 -15
  74. package/shared/localization/locales/en-US.json +19 -16
  75. package/shared/localization/locales/en-XA.json +0 -15
  76. package/shared/localization/locales/en-XL.json +19 -16
  77. package/shared/localization/locales/es-419.json +0 -15
  78. package/shared/localization/locales/es.json +0 -15
  79. package/shared/localization/locales/fi.json +0 -15
  80. package/shared/localization/locales/fil.json +0 -15
  81. package/shared/localization/locales/fr.json +0 -15
  82. package/shared/localization/locales/he.json +0 -15
  83. package/shared/localization/locales/hi.json +0 -15
  84. package/shared/localization/locales/hr.json +0 -15
  85. package/shared/localization/locales/hu.json +0 -15
  86. package/shared/localization/locales/id.json +0 -15
  87. package/shared/localization/locales/it.json +0 -15
  88. package/shared/localization/locales/ja.json +0 -15
  89. package/shared/localization/locales/ko.json +0 -15
  90. package/shared/localization/locales/lt.json +0 -15
  91. package/shared/localization/locales/lv.json +0 -15
  92. package/shared/localization/locales/nl.json +0 -15
  93. package/shared/localization/locales/no.json +0 -15
  94. package/shared/localization/locales/pl.json +0 -15
  95. package/shared/localization/locales/pt-PT.json +0 -15
  96. package/shared/localization/locales/pt.json +0 -15
  97. package/shared/localization/locales/ro.json +0 -15
  98. package/shared/localization/locales/ru.json +0 -15
  99. package/shared/localization/locales/sk.json +0 -15
  100. package/shared/localization/locales/sl.json +0 -15
  101. package/shared/localization/locales/sr-Latn.json +0 -15
  102. package/shared/localization/locales/sr.json +0 -15
  103. package/shared/localization/locales/sv.json +0 -15
  104. package/shared/localization/locales/ta.json +0 -15
  105. package/shared/localization/locales/te.json +0 -15
  106. package/shared/localization/locales/th.json +0 -15
  107. package/shared/localization/locales/tr.json +0 -15
  108. package/shared/localization/locales/uk.json +0 -15
  109. package/shared/localization/locales/vi.json +0 -15
  110. package/shared/localization/locales/zh-HK.json +0 -15
  111. package/shared/localization/locales/zh-TW.json +0 -15
  112. package/shared/localization/locales/zh.json +0 -15
  113. package/third-party/chromium-synchronization/installability-errors-test.js +2 -0
  114. package/types/lhr/flow.d.ts +3 -2
  115. package/types/rollup-plugin-postprocess.d.ts +10 -0
  116. package/lighthouse-core/audits/dobetterweb/external-anchors-use-rel-noopener.js +0 -96
@@ -27,6 +27,8 @@ module.exports = {
27
27
  ],
28
28
  'newlines-between': 'always',
29
29
  }],
30
+ 'import/group-exports': 2,
31
+ 'import/exports-last': 2,
30
32
  '@typescript-eslint/type-annotation-spacing': 2,
31
33
  },
32
34
  overrides: [
@@ -297,7 +297,7 @@
297
297
 
298
298
  .SummaryFlow {
299
299
  display: grid;
300
- max-width: var(--report-width);
300
+ max-width: var(--report-content-width);
301
301
  grid-auto-rows: min-content;
302
302
  grid-template-columns: min-content min-content minmax(var(--min-text-width), max-content) 1fr 1fr 1fr 1fr;
303
303
  align-items: center;
@@ -5,25 +5,24 @@
5
5
  */
6
6
 
7
7
  import {FunctionComponent} from 'preact';
8
- import {useEffect, useRef, useState} from 'preact/hooks';
8
+ import {useLayoutEffect, useRef, useState} from 'preact/hooks';
9
9
 
10
10
  import {ReportRendererProvider} from './wrappers/report-renderer';
11
11
  import {Sidebar} from './sidebar/sidebar';
12
12
  import {Summary} from './summary/summary';
13
- import {classNames, FlowResultContext, useCurrentLhr, useHashParam} from './util';
13
+ import {classNames, FlowResultContext, useHashState} from './util';
14
14
  import {Report} from './wrappers/report';
15
15
  import {Topbar} from './topbar';
16
16
  import {Header} from './header';
17
17
  import {I18nProvider} from './i18n/i18n';
18
18
 
19
19
  const Content: FunctionComponent = () => {
20
- const currentLhr = useCurrentLhr();
21
- const anchor = useHashParam('anchor');
20
+ const hashState = useHashState();
22
21
  const ref = useRef<HTMLDivElement>(null);
23
22
 
24
- useEffect(() => {
25
- if (anchor) {
26
- const el = document.getElementById(anchor);
23
+ useLayoutEffect(() => {
24
+ if (hashState && hashState.anchor) {
25
+ const el = document.getElementById(hashState.anchor);
27
26
  if (el) {
28
27
  el.scrollIntoView({behavior: 'smooth'});
29
28
  return;
@@ -32,15 +31,15 @@ const Content: FunctionComponent = () => {
32
31
 
33
32
  // Scroll to top no anchor is found.
34
33
  if (ref.current) ref.current.scrollTop = 0;
35
- }, [anchor, currentLhr]);
34
+ }, [hashState]);
36
35
 
37
36
  return (
38
37
  <div ref={ref} className="Content">
39
38
  {
40
- currentLhr ?
39
+ hashState ?
41
40
  <>
42
- <Header currentLhr={currentLhr}/>
43
- <Report currentLhr={currentLhr}/>
41
+ <Header hashState={hashState}/>
42
+ <Report hashState={hashState}/>
44
43
  </> :
45
44
  <Summary/>
46
45
  }
@@ -12,11 +12,11 @@ import {getFilmstripFrames, getScreenDimensions, getFullPageScreenshot} from './
12
12
 
13
13
  const ANIMATION_FRAME_DURATION_MS = 500;
14
14
 
15
- export const Separator: FunctionComponent = () => {
15
+ const Separator: FunctionComponent = () => {
16
16
  return <div className="Separator" role="separator"></div>;
17
17
  };
18
18
 
19
- export const FlowStepIcon: FunctionComponent<{mode: LH.Result.GatherMode}> = ({mode}) => {
19
+ const FlowStepIcon: FunctionComponent<{mode: LH.Result.GatherMode}> = ({mode}) => {
20
20
  return <>
21
21
  {
22
22
  mode === 'navigation' && <NavigationIcon/>
@@ -30,7 +30,7 @@ export const FlowStepIcon: FunctionComponent<{mode: LH.Result.GatherMode}> = ({m
30
30
  </>;
31
31
  };
32
32
 
33
- export const FlowSegment: FunctionComponent<{mode?: LH.Result.GatherMode}> = ({mode}) => {
33
+ const FlowSegment: FunctionComponent<{mode?: LH.Result.GatherMode}> = ({mode}) => {
34
34
  return (
35
35
  <div className="FlowSegment">
36
36
  <div className="FlowSegment__top-line"/>
@@ -71,12 +71,12 @@ const FlowStepAnimatedThumbnail: FunctionComponent<{
71
71
  );
72
72
  };
73
73
 
74
- export const FlowStepThumbnail: FunctionComponent<{
74
+ const FlowStepThumbnail: FunctionComponent<{
75
75
  lhr: LH.Result,
76
76
  width?: number,
77
77
  height?: number,
78
78
  }> = ({lhr, width, height}) => {
79
- const screenshot = getFullPageScreenshot(lhr);
79
+ const fullPageScreenshot = getFullPageScreenshot(lhr);
80
80
  const frames = getFilmstripFrames(lhr);
81
81
 
82
82
  // Resize the image to fit the viewport aspect ratio.
@@ -92,19 +92,32 @@ export const FlowStepThumbnail: FunctionComponent<{
92
92
  return <></>;
93
93
  }
94
94
 
95
- if (lhr.gatherMode === 'timespan' && frames && frames.length) {
96
- return <FlowStepAnimatedThumbnail frames={frames} width={width} height={height} />;
95
+ let thumbnail;
96
+ if (frames && frames.length) {
97
+ thumbnail = frames[frames.length - 1].data;
98
+ if (lhr.gatherMode === 'timespan') {
99
+ return <FlowStepAnimatedThumbnail frames={frames} width={width} height={height} />;
100
+ }
101
+ } else {
102
+ thumbnail = fullPageScreenshot?.screenshot.data;
97
103
  }
98
104
 
99
105
  return <>
100
106
  {
101
- screenshot &&
107
+ thumbnail &&
102
108
  <img
103
109
  className="FlowStepThumbnail"
104
- src={screenshot.screenshot.data}
110
+ src={thumbnail}
105
111
  style={{width, height}}
106
112
  alt="Screenshot of a page tested by Lighthouse"
107
113
  />
108
114
  }
109
115
  </>;
110
116
  };
117
+
118
+ export {
119
+ Separator,
120
+ FlowStepIcon,
121
+ FlowSegment,
122
+ FlowStepThumbnail,
123
+ };
@@ -29,10 +29,10 @@ const HeaderThumbnail: FunctionComponent<{
29
29
  );
30
30
  };
31
31
 
32
- export const Header: FunctionComponent<{currentLhr: LH.FlowResult.LhrRef}> =
33
- ({currentLhr}) => {
32
+ export const Header: FunctionComponent<{hashState: LH.FlowResult.HashState}> =
33
+ ({hashState}) => {
34
34
  const flowResult = useFlowResult();
35
- const {index} = currentLhr;
35
+ const {index} = hashState;
36
36
 
37
37
  const step = flowResult.steps[index];
38
38
  const prevStep = flowResult.steps[index - 1];
@@ -31,23 +31,23 @@ function useLhrLocale() {
31
31
  };
32
32
  }
33
33
 
34
- export function useI18n() {
34
+ function useI18n() {
35
35
  return useContext(I18nContext);
36
36
  }
37
37
 
38
- export function useLocalizedStrings() {
38
+ function useLocalizedStrings() {
39
39
  const i18n = useI18n();
40
40
  return i18n.strings;
41
41
  }
42
42
 
43
- export function useStringFormatter() {
43
+ function useStringFormatter() {
44
44
  const {locale} = useLhrLocale();
45
45
  return (str: string, values?: Record<string, string|number>) => {
46
46
  return formatMessage(str, values, locale);
47
47
  };
48
48
  }
49
49
 
50
- export const I18nProvider: FunctionComponent = ({children}) => {
50
+ const I18nProvider: FunctionComponent = ({children}) => {
51
51
  const {locale, lhrStrings} = useLhrLocale();
52
52
 
53
53
  const i18n = useMemo(() => {
@@ -65,7 +65,6 @@ export const I18nProvider: FunctionComponent = ({children}) => {
65
65
 
66
66
  // Initialize renderer util i18n for strings rendered in wrapped components.
67
67
  // TODO: Don't attach global i18n to `Util`.
68
- // @ts-ignore TS reports as read-only.
69
68
  Util.i18n = i18n;
70
69
 
71
70
  return i18n;
@@ -78,4 +77,9 @@ export const I18nProvider: FunctionComponent = ({children}) => {
78
77
  );
79
78
  };
80
79
 
81
-
80
+ export {
81
+ useI18n,
82
+ useLocalizedStrings,
83
+ useStringFormatter,
84
+ I18nProvider,
85
+ };
@@ -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,7 @@ export const EnvIcon: FunctionComponent = () => {
104
104
  );
105
105
  };
106
106
 
107
- export const CpuIcon: FunctionComponent = () => {
107
+ const CpuIcon: FunctionComponent = () => {
108
108
  return (
109
109
  <svg width="16" height="16" viewBox="0 0 16 16" fill="none" role="img">
110
110
  <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 +112,7 @@ export const CpuIcon: FunctionComponent = () => {
112
112
  );
113
113
  };
114
114
 
115
- export const HamburgerIcon: FunctionComponent = () => {
115
+ const HamburgerIcon: FunctionComponent = () => {
116
116
  return (
117
117
  <svg viewBox="0 0 18 12" width="18" height="12" role="img">
118
118
  <rect width="18" height="2" fill="currentColor"></rect>
@@ -122,10 +122,22 @@ export const HamburgerIcon: FunctionComponent = () => {
122
122
  );
123
123
  };
124
124
 
125
- export const InfoIcon: FunctionComponent = () => {
125
+ const InfoIcon: FunctionComponent = () => {
126
126
  return (
127
127
  <svg width="14" height="14" viewBox="0 0 14 14" fill="none">
128
128
  <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"/>
129
129
  </svg>
130
130
  );
131
131
  };
132
+
133
+ export {
134
+ SummaryIcon,
135
+ NavigationIcon,
136
+ TimespanIcon,
137
+ SnapshotIcon,
138
+ CloseIcon,
139
+ EnvIcon,
140
+ CpuIcon,
141
+ HamburgerIcon,
142
+ InfoIcon,
143
+ };
@@ -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
  })
@@ -9,11 +9,11 @@ import {FunctionComponent} from 'preact';
9
9
  import {Separator} from '../common';
10
10
  import {useI18n, useLocalizedStrings} from '../i18n/i18n';
11
11
  import {CpuIcon, EnvIcon, SummaryIcon} from '../icons';
12
- import {classNames, useCurrentLhr, useFlowResult} from '../util';
12
+ import {classNames, useHashState, useFlowResult} from '../util';
13
13
  import {SidebarFlow} from './flow';
14
14
 
15
- export const SidebarSummary: FunctionComponent = () => {
16
- const currentLhr = useCurrentLhr();
15
+ const SidebarSummary: FunctionComponent = () => {
16
+ const hashState = useHashState();
17
17
  const strings = useLocalizedStrings();
18
18
 
19
19
  const url = new URL(location.href);
@@ -21,7 +21,7 @@ export const SidebarSummary: FunctionComponent = () => {
21
21
  return (
22
22
  <a
23
23
  href={url.href}
24
- className={classNames('SidebarSummary', {'Sidebar--current': currentLhr === null})}
24
+ className={classNames('SidebarSummary', {'Sidebar--current': hashState === null})}
25
25
  data-testid="SidebarSummary"
26
26
  >
27
27
  <div className="SidebarSummary__icon">
@@ -59,7 +59,7 @@ const SidebarRuntimeSettings: FunctionComponent<{settings: LH.ConfigSettings}> =
59
59
  );
60
60
  };
61
61
 
62
- export const SidebarHeader: FunctionComponent<{title: string, date: string}> = ({title, date}) => {
62
+ const SidebarHeader: FunctionComponent<{title: string, date: string}> = ({title, date}) => {
63
63
  const i18n = useI18n();
64
64
  return (
65
65
  <div className="SidebarHeader">
@@ -69,7 +69,7 @@ export const SidebarHeader: FunctionComponent<{title: string, date: string}> = (
69
69
  );
70
70
  };
71
71
 
72
- export const Sidebar: FunctionComponent = () => {
72
+ const Sidebar: FunctionComponent = () => {
73
73
  const flowResult = useFlowResult();
74
74
  const firstLhr = flowResult.steps[0].lhr;
75
75
  return (
@@ -84,3 +84,9 @@ export const Sidebar: FunctionComponent = () => {
84
84
  </div>
85
85
  );
86
86
  };
87
+
88
+ export {
89
+ SidebarSummary,
90
+ SidebarHeader,
91
+ Sidebar,
92
+ };
@@ -64,8 +64,8 @@ const SummaryTooltipAudits: FunctionComponent<{category: LH.ReportResult.Categor
64
64
  return audit.result.score !== null &&
65
65
  // Metrics should not be displayed in this group.
66
66
  audit.group !== 'metrics' &&
67
- // Audits in performance without a group are hidden.
68
- (audit.group !== undefined || category.id !== 'performance') &&
67
+ // Audits in performance group "hidden" should not be counted.
68
+ (audit.group !== 'hidden' || category.id !== 'performance') &&
69
69
  // We don't want unweighted audits except for opportunities with potential savings.
70
70
  (audit.weight > 0 || getOverallSavings(audit) > 0) &&
71
71
  // Passing audits should never be high impact.
@@ -95,7 +95,7 @@ const SummaryTooltipAudits: FunctionComponent<{category: LH.ReportResult.Categor
95
95
  );
96
96
  };
97
97
 
98
- export const SummaryTooltip: FunctionComponent<{
98
+ const SummaryTooltip: FunctionComponent<{
99
99
  category: LH.ReportResult.Category,
100
100
  gatherMode: LH.Result.GatherMode,
101
101
  url: string,
@@ -153,7 +153,7 @@ export const SummaryTooltip: FunctionComponent<{
153
153
  );
154
154
  };
155
155
 
156
- export const SummaryCategory: FunctionComponent<{
156
+ const SummaryCategory: FunctionComponent<{
157
157
  category: LH.ReportResult.Category|undefined,
158
158
  href: string,
159
159
  gatherMode: LH.Result.GatherMode,
@@ -176,3 +176,8 @@ export const SummaryCategory: FunctionComponent<{
176
176
  </div>
177
177
  );
178
178
  };
179
+
180
+ export {
181
+ SummaryTooltip,
182
+ SummaryCategory,
183
+ };
@@ -44,7 +44,7 @@ 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,
@@ -106,7 +106,7 @@ const SummaryFlow: FunctionComponent = () => {
106
106
  );
107
107
  };
108
108
 
109
- export const SummaryHeader: FunctionComponent = () => {
109
+ const SummaryHeader: FunctionComponent = () => {
110
110
  const flowResult = useFlowResult();
111
111
  const strings = useLocalizedStrings();
112
112
  const str_ = useStringFormatter();
@@ -151,7 +151,7 @@ const SummarySectionHeader: FunctionComponent = ({children}) => {
151
151
  );
152
152
  };
153
153
 
154
- export const Summary: FunctionComponent = () => {
154
+ const Summary: FunctionComponent = () => {
155
155
  const strings = useLocalizedStrings();
156
156
 
157
157
  return (
@@ -163,3 +163,9 @@ export const Summary: FunctionComponent = () => {
163
163
  </div>
164
164
  );
165
165
  };
166
+
167
+ export {
168
+ SummaryFlowStep,
169
+ SummaryHeader,
170
+ Summary,
171
+ };
@@ -9,14 +9,14 @@ import {useContext, useEffect, useMemo, useState} from 'preact/hooks';
9
9
 
10
10
  import type {UIStringsType} from './i18n/ui-strings';
11
11
 
12
- export const FlowResultContext = createContext<LH.FlowResult|undefined>(undefined);
12
+ const FlowResultContext = createContext<LH.FlowResult|undefined>(undefined);
13
13
 
14
14
  function getHashParam(param: string): string|null {
15
15
  const params = new URLSearchParams(location.hash.replace('#', '?'));
16
16
  return params.get(param);
17
17
  }
18
18
 
19
- export function classNames(...args: Array<string|undefined|Record<string, boolean>>): string {
19
+ function classNames(...args: Array<string|undefined|Record<string, boolean>>): string {
20
20
  const classes = [];
21
21
  for (const arg of args) {
22
22
  if (!arg) continue;
@@ -35,12 +35,12 @@ export function classNames(...args: Array<string|undefined|Record<string, boolea
35
35
  return classes.join(' ');
36
36
  }
37
37
 
38
- export function getScreenDimensions(reportResult: LH.Result) {
38
+ function getScreenDimensions(reportResult: LH.Result) {
39
39
  const {width, height} = reportResult.configSettings.screenEmulation;
40
40
  return {width, height};
41
41
  }
42
42
 
43
- export function getFullPageScreenshot(reportResult: LH.Result) {
43
+ function getFullPageScreenshot(reportResult: LH.Result) {
44
44
  const fullPageScreenshotAudit = reportResult.audits['full-page-screenshot'];
45
45
  const fullPageScreenshot =
46
46
  fullPageScreenshotAudit &&
@@ -51,7 +51,7 @@ export function getFullPageScreenshot(reportResult: LH.Result) {
51
51
  return fullPageScreenshot || null;
52
52
  }
53
53
 
54
- export function getFilmstripFrames(
54
+ function getFilmstripFrames(
55
55
  reportResult: LH.Result
56
56
  ): Array<{data: string}> | undefined {
57
57
  const filmstripAudit = reportResult.audits['screenshot-thumbnails'];
@@ -65,7 +65,7 @@ export function getFilmstripFrames(
65
65
  return frameItems || undefined;
66
66
  }
67
67
 
68
- export function getModeDescription(mode: LH.Result.GatherMode, strings: UIStringsType) {
68
+ function getModeDescription(mode: LH.Result.GatherMode, strings: UIStringsType) {
69
69
  switch (mode) {
70
70
  case 'navigation': return strings.navigationDescription;
71
71
  case 'timespan': return strings.timespanDescription;
@@ -73,33 +73,33 @@ export function getModeDescription(mode: LH.Result.GatherMode, strings: UIString
73
73
  }
74
74
  }
75
75
 
76
- export function useFlowResult(): LH.FlowResult {
76
+ function useFlowResult(): LH.FlowResult {
77
77
  const flowResult = useContext(FlowResultContext);
78
78
  if (!flowResult) throw Error('useFlowResult must be called in the FlowResultContext');
79
79
  return flowResult;
80
80
  }
81
81
 
82
- export function useHashParam(param: string) {
83
- const [paramValue, setParamValue] = useState(getHashParam(param));
82
+ function useHashParams(...params: string[]) {
83
+ const [paramValues, setParamValues] = useState(params.map(getHashParam));
84
84
 
85
85
  // Use two-way-binding on the URL hash.
86
- // Triggers a re-render if the param changes.
86
+ // Triggers a re-render if any param changes.
87
87
  useEffect(() => {
88
88
  function hashListener() {
89
- const newIndexString = getHashParam(param);
90
- if (newIndexString === paramValue) return;
91
- setParamValue(newIndexString);
89
+ const newParamValues = params.map(getHashParam);
90
+ if (newParamValues.every((value, i) => value === paramValues[i])) return;
91
+ setParamValues(newParamValues);
92
92
  }
93
93
  window.addEventListener('hashchange', hashListener);
94
94
  return () => window.removeEventListener('hashchange', hashListener);
95
- }, [paramValue]);
95
+ }, [paramValues]);
96
96
 
97
- return paramValue;
97
+ return paramValues;
98
98
  }
99
99
 
100
- export function useCurrentLhr(): LH.FlowResult.LhrRef|null {
100
+ function useHashState(): LH.FlowResult.HashState|null {
101
101
  const flowResult = useFlowResult();
102
- const indexString = useHashParam('index');
102
+ const [indexString, anchor] = useHashParams('index', 'anchor');
103
103
 
104
104
  // Memoize result so a new object is not created on every call.
105
105
  return useMemo(() => {
@@ -117,6 +117,18 @@ export function useCurrentLhr(): LH.FlowResult.LhrRef|null {
117
117
  return null;
118
118
  }
119
119
 
120
- return {value: step.lhr, index};
121
- }, [indexString, flowResult]);
120
+ return {currentLhr: step.lhr, index, anchor};
121
+ }, [indexString, flowResult, anchor]);
122
122
  }
123
+
124
+ export {
125
+ FlowResultContext,
126
+ classNames,
127
+ getScreenDimensions,
128
+ getFullPageScreenshot,
129
+ getFilmstripFrames,
130
+ getModeDescription,
131
+ useFlowResult,
132
+ useHashParams,
133
+ useHashState,
134
+ };
@@ -19,15 +19,15 @@ interface ReportRendererGlobals {
19
19
  reportRenderer: ReportRenderer,
20
20
  }
21
21
 
22
- export const ReportRendererContext = createContext<ReportRendererGlobals|undefined>(undefined);
22
+ const ReportRendererContext = createContext<ReportRendererGlobals|undefined>(undefined);
23
23
 
24
- export function useReportRenderer() {
24
+ function useReportRenderer() {
25
25
  const globals = useContext(ReportRendererContext);
26
26
  if (!globals) throw Error('Globals not defined');
27
27
  return globals;
28
28
  }
29
29
 
30
- export const ReportRendererProvider: FunctionComponent = ({children}) => {
30
+ const ReportRendererProvider: FunctionComponent = ({children}) => {
31
31
  const globals = useMemo(() => {
32
32
  const dom = new DOM(document);
33
33
  const detailsRenderer = new DetailsRenderer(dom);
@@ -44,3 +44,9 @@ export const ReportRendererProvider: FunctionComponent = ({children}) => {
44
44
  <ReportRendererContext.Provider value={globals}>{children}</ReportRendererContext.Provider>
45
45
  );
46
46
  };
47
+
48
+ export {
49
+ ReportRendererContext,
50
+ ReportRendererProvider,
51
+ useReportRenderer,
52
+ };
@@ -16,7 +16,7 @@ import {useReportRenderer} from './report-renderer';
16
16
  * This function converts any anchor links under the provided element to a flow report link.
17
17
  * e.g. <a href="#link"> -> <a href="#index=0&anchor=link">
18
18
  */
19
- export function convertChildAnchors(element: HTMLElement, index: number) {
19
+ function convertChildAnchors(element: HTMLElement, index: number) {
20
20
  const links = element.querySelectorAll('a[href]') as NodeListOf<HTMLAnchorElement>;
21
21
  for (const link of links) {
22
22
  // Check if the link destination is in the report.
@@ -30,20 +30,25 @@ export function convertChildAnchors(element: HTMLElement, index: number) {
30
30
 
31
31
  const nodeId = link.hash.substr(1);
32
32
  link.hash = `#index=${index}&anchor=${nodeId}`;
33
+ link.onclick = e => {
34
+ e.preventDefault();
35
+ const el = document.getElementById(nodeId);
36
+ if (el) el.scrollIntoView();
37
+ };
33
38
  }
34
39
  }
35
40
 
36
- export const Report: FunctionComponent<{currentLhr: LH.FlowResult.LhrRef}> =
37
- ({currentLhr}) => {
41
+ const Report: FunctionComponent<{hashState: LH.FlowResult.HashState}> =
42
+ ({hashState}) => {
38
43
  const {dom, reportRenderer} = useReportRenderer();
39
44
  const ref = useRef<HTMLDivElement>(null);
40
45
 
41
46
  useLayoutEffect(() => {
42
47
  if (ref.current) {
43
48
  dom.clearComponentCache();
44
- reportRenderer.renderReport(currentLhr.value, ref.current);
45
- convertChildAnchors(ref.current, currentLhr.index);
46
- const fullPageScreenshot = getFullPageScreenshot(currentLhr.value);
49
+ reportRenderer.renderReport(hashState.currentLhr, ref.current);
50
+ convertChildAnchors(ref.current, hashState.index);
51
+ const fullPageScreenshot = getFullPageScreenshot(hashState.currentLhr);
47
52
  if (fullPageScreenshot) {
48
53
  const container = dom.find('.lh-container', ref.current);
49
54
  ElementScreenshotRenderer.installOverlayFeature({
@@ -60,9 +65,14 @@ export const Report: FunctionComponent<{currentLhr: LH.FlowResult.LhrRef}> =
60
65
  return () => {
61
66
  if (ref.current) ref.current.textContent = '';
62
67
  };
63
- }, [reportRenderer, currentLhr]);
68
+ }, [reportRenderer, hashState]);
64
69
 
65
70
  return (
66
71
  <div ref={ref} className="lh-root" data-testid="Report"/>
67
72
  );
68
73
  };
74
+
75
+ export {
76
+ convertChildAnchors,
77
+ Report,
78
+ };