lighthouse 8.6.0-dev.20211031 → 8.6.0-dev.20211104

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 (132) hide show
  1. package/dist/report/flow.js +13 -13
  2. package/dist/report/standalone.js +41 -35
  3. package/flow-report/.eslintrc.cjs +2 -0
  4. package/flow-report/assets/standalone-flow-template.html +3 -2
  5. package/flow-report/assets/styles.css +1 -1
  6. package/flow-report/src/app.tsx +10 -11
  7. package/flow-report/src/common.tsx +22 -9
  8. package/flow-report/src/header.tsx +3 -3
  9. package/flow-report/src/i18n/i18n.tsx +10 -6
  10. package/flow-report/src/icons.tsx +30 -9
  11. package/flow-report/src/sidebar/flow.tsx +3 -3
  12. package/flow-report/src/sidebar/sidebar.tsx +30 -11
  13. package/flow-report/src/summary/category.tsx +9 -4
  14. package/flow-report/src/summary/summary.tsx +9 -3
  15. package/flow-report/src/util.ts +31 -19
  16. package/flow-report/src/wrappers/report-renderer.tsx +10 -3
  17. package/flow-report/src/wrappers/report.tsx +19 -10
  18. package/flow-report/standalone-flow.tsx +4 -3
  19. package/flow-report/test/common-test.tsx +25 -10
  20. package/flow-report/test/header-test.tsx +6 -6
  21. package/flow-report/test/setup/env-setup.ts +1 -1
  22. package/flow-report/test/sidebar/sidebar-test.tsx +43 -1
  23. package/flow-report/test/summary/category-test.tsx +5 -5
  24. package/flow-report/test/util-test.tsx +32 -11
  25. package/lighthouse-cli/.eslintrc.cjs +2 -0
  26. package/lighthouse-cli/cli-flags.js +1 -1
  27. package/lighthouse-cli/test/smokehouse/lighthouse-runners/bundle.js +4 -2
  28. package/lighthouse-cli/test/smokehouse/smokehouse.js +1 -1
  29. package/lighthouse-core/audits/audit.js +1 -1
  30. package/lighthouse-core/computed/metrics/lantern-total-blocking-time.js +3 -4
  31. package/lighthouse-core/computed/metrics/tbt-utils.js +57 -0
  32. package/lighthouse-core/computed/metrics/total-blocking-time.js +3 -53
  33. package/lighthouse-core/computed/resource-summary.js +1 -1
  34. package/lighthouse-core/config/config-helpers.js +20 -10
  35. package/lighthouse-core/config/default-config.js +59 -55
  36. package/lighthouse-core/fraggle-rock/config/default-config.js +1 -1
  37. package/lighthouse-core/fraggle-rock/gather/base-gatherer.js +6 -1
  38. package/lighthouse-core/gather/connections/cri.js +1 -1
  39. package/lighthouse-core/gather/gatherers/dobetterweb/response-compression.js +1 -0
  40. package/lighthouse-core/lib/asset-saver.js +4 -1
  41. package/lighthouse-core/lib/lh-env.js +2 -0
  42. package/lighthouse-core/lib/proto-preprocessor.js +14 -2
  43. package/lighthouse-core/lib/tappable-rects.js +8 -15
  44. package/lighthouse-core/lib/url-shim.js +1 -1
  45. package/lighthouse-core/runner.js +1 -1
  46. package/lighthouse-core/util-commonjs.js +25 -11
  47. package/package.json +11 -8
  48. package/report/.eslintrc.cjs +2 -0
  49. package/report/assets/standalone-template.html +2 -3
  50. package/report/assets/styles.css +227 -251
  51. package/report/assets/templates.html +19 -29
  52. package/report/clients/bundle.js +1 -0
  53. package/report/clients/standalone.js +6 -15
  54. package/report/renderer/api.js +32 -0
  55. package/report/renderer/category-renderer.js +22 -18
  56. package/report/renderer/components.js +46 -79
  57. package/report/renderer/crc-details-renderer.js +15 -12
  58. package/report/renderer/dom.js +16 -1
  59. package/report/renderer/drop-down-menu.js +2 -2
  60. package/report/renderer/element-screenshot-renderer.js +8 -8
  61. package/report/renderer/features-util.js +1 -1
  62. package/report/renderer/open-tab.js +9 -3
  63. package/report/renderer/performance-category-renderer.js +51 -24
  64. package/report/renderer/report-renderer.js +34 -10
  65. package/report/renderer/report-ui-features.js +36 -33
  66. package/report/renderer/swap-locale-feature.js +3 -3
  67. package/report/renderer/topbar-features.js +52 -48
  68. package/report/renderer/util.js +26 -11
  69. package/report/test/clients/bundle-test.js +8 -0
  70. package/report/test/renderer/category-renderer-test.js +2 -2
  71. package/report/test/renderer/performance-category-renderer-test.js +35 -9
  72. package/report/test/renderer/report-renderer-axe-test.js +5 -2
  73. package/report/test/renderer/report-renderer-test.js +14 -11
  74. package/report/test/renderer/report-ui-features-test.js +20 -10
  75. package/report/test/renderer/util-test.js +13 -31
  76. package/report/test-assets/faux-psi-template.html +2 -7
  77. package/report/test-assets/faux-psi.js +20 -39
  78. package/report/types/html-renderer.d.ts +2 -0
  79. package/report/types/report-renderer.d.ts +23 -0
  80. package/shared/localization/format.js +9 -4
  81. package/shared/localization/locales/ar-XB.json +0 -15
  82. package/shared/localization/locales/ar.json +0 -15
  83. package/shared/localization/locales/bg.json +0 -15
  84. package/shared/localization/locales/ca.json +0 -15
  85. package/shared/localization/locales/cs.json +0 -15
  86. package/shared/localization/locales/da.json +0 -15
  87. package/shared/localization/locales/de.json +0 -15
  88. package/shared/localization/locales/el.json +0 -15
  89. package/shared/localization/locales/en-GB.json +0 -15
  90. package/shared/localization/locales/en-US.json +19 -16
  91. package/shared/localization/locales/en-XA.json +0 -15
  92. package/shared/localization/locales/en-XL.json +19 -16
  93. package/shared/localization/locales/es-419.json +0 -15
  94. package/shared/localization/locales/es.json +0 -15
  95. package/shared/localization/locales/fi.json +0 -15
  96. package/shared/localization/locales/fil.json +0 -15
  97. package/shared/localization/locales/fr.json +0 -15
  98. package/shared/localization/locales/he.json +0 -15
  99. package/shared/localization/locales/hi.json +0 -15
  100. package/shared/localization/locales/hr.json +0 -15
  101. package/shared/localization/locales/hu.json +0 -15
  102. package/shared/localization/locales/id.json +0 -15
  103. package/shared/localization/locales/it.json +0 -15
  104. package/shared/localization/locales/ja.json +0 -15
  105. package/shared/localization/locales/ko.json +0 -15
  106. package/shared/localization/locales/lt.json +0 -15
  107. package/shared/localization/locales/lv.json +0 -15
  108. package/shared/localization/locales/nl.json +0 -15
  109. package/shared/localization/locales/no.json +0 -15
  110. package/shared/localization/locales/pl.json +0 -15
  111. package/shared/localization/locales/pt-PT.json +0 -15
  112. package/shared/localization/locales/pt.json +0 -15
  113. package/shared/localization/locales/ro.json +0 -15
  114. package/shared/localization/locales/ru.json +0 -15
  115. package/shared/localization/locales/sk.json +0 -15
  116. package/shared/localization/locales/sl.json +0 -15
  117. package/shared/localization/locales/sr-Latn.json +0 -15
  118. package/shared/localization/locales/sr.json +0 -15
  119. package/shared/localization/locales/sv.json +0 -15
  120. package/shared/localization/locales/ta.json +0 -15
  121. package/shared/localization/locales/te.json +0 -15
  122. package/shared/localization/locales/th.json +0 -15
  123. package/shared/localization/locales/tr.json +0 -15
  124. package/shared/localization/locales/uk.json +0 -15
  125. package/shared/localization/locales/vi.json +0 -15
  126. package/shared/localization/locales/zh-HK.json +0 -15
  127. package/shared/localization/locales/zh-TW.json +0 -15
  128. package/shared/localization/locales/zh.json +0 -15
  129. package/third-party/chromium-synchronization/installability-errors-test.js +2 -0
  130. package/types/lhr/flow.d.ts +3 -2
  131. package/types/rollup-plugin-postprocess.d.ts +10 -0
  132. package/lighthouse-core/audits/dobetterweb/external-anchors-use-rel-noopener.js +0 -96
@@ -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,26 +30,30 @@ 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
- const container = dom.find('.lh-container', ref.current);
49
53
  ElementScreenshotRenderer.installOverlayFeature({
50
54
  dom,
51
- reportEl: container,
52
- overlayContainerEl: container,
55
+ rootEl: ref.current,
56
+ overlayContainerEl: ref.current,
53
57
  fullPageScreenshot,
54
58
  });
55
59
  }
@@ -60,9 +64,14 @@ export const Report: FunctionComponent<{currentLhr: LH.FlowResult.LhrRef}> =
60
64
  return () => {
61
65
  if (ref.current) ref.current.textContent = '';
62
66
  };
63
- }, [reportRenderer, currentLhr]);
67
+ }, [reportRenderer, hashState]);
64
68
 
65
69
  return (
66
70
  <div ref={ref} className="lh-root" data-testid="Report"/>
67
71
  );
68
72
  };
73
+
74
+ export {
75
+ convertChildAnchors,
76
+ Report,
77
+ };
@@ -15,9 +15,10 @@ import {App} from './src/app';
15
15
 
16
16
  // Used by standalone-flow.html
17
17
  function __initLighthouseFlowReport__() {
18
- const root = document.body.querySelector('main');
19
- if (!root) throw Error('Root element not found');
20
- render(<App flowResult={window.__LIGHTHOUSE_FLOW_JSON__} />, root);
18
+ // TODO(adamraine): add lh-vars, etc classes programmatically instead of in the HTML template
19
+ const container = document.body.querySelector('main');
20
+ if (!container) throw Error('Container element not found');
21
+ render(<App flowResult={window.__LIGHTHOUSE_FLOW_JSON__} />, container);
21
22
  }
22
23
 
23
24
  window.__initLighthouseFlowReport__ = __initLighthouseFlowReport__;
@@ -24,10 +24,19 @@ describe('FlowStepThumbnail', () => {
24
24
  'full-page-screenshot': {
25
25
  details: {
26
26
  type: 'full-page-screenshot',
27
- screenshot: {data: 'baef01', width: 400, height: 600},
27
+ screenshot: {data: 'FPS', width: 400, height: 600},
28
28
  nodes: {},
29
29
  },
30
30
  },
31
+ 'screenshot-thumbnails': {
32
+ details: {
33
+ type: 'filmstrip',
34
+ items: [
35
+ {data: 'frame1'},
36
+ {data: 'frame2'},
37
+ ],
38
+ },
39
+ },
31
40
  },
32
41
  } as any;
33
42
  });
@@ -63,17 +72,23 @@ describe('FlowStepThumbnail', () => {
63
72
  expect(thumbnail.style.height).toEqual('150px');
64
73
  });
65
74
 
75
+ it('uses last filmstrip thumbnail', () => {
76
+ const root = render(<FlowStepThumbnail lhr={lhr} height={150} />);
77
+
78
+ const thumbnail = root.getByAltText(/Screenshot/) as HTMLImageElement;
79
+ expect(thumbnail.src).toContain('frame2');
80
+ });
81
+
82
+ it('uses full page screenshot if filmstrip unavailable', () => {
83
+ delete lhr.audits['screenshot-thumbnails'];
84
+ const root = render(<FlowStepThumbnail lhr={lhr} height={150} />);
85
+
86
+ const thumbnail = root.getByAltText(/Screenshot/) as HTMLImageElement;
87
+ expect(thumbnail.src).toContain('FPS');
88
+ });
89
+
66
90
  it('renders animated thumbnail for timespan', async () => {
67
91
  lhr.gatherMode = 'timespan';
68
- lhr.audits['screenshot-thumbnails'] = {
69
- details: {
70
- type: 'filmstrip',
71
- items: [
72
- {data: 'frame1'},
73
- {data: 'frame2'},
74
- ],
75
- },
76
- } as any;
77
92
  const root = render(<FlowStepThumbnail lhr={lhr} height={150} />);
78
93
 
79
94
  const thumbnail = root.getByAltText(/Animated/) as HTMLImageElement;
@@ -25,8 +25,8 @@ beforeEach(() => {
25
25
  });
26
26
 
27
27
  it('renders all sections for a middle step', () => {
28
- const currentLhr = {index: 1} as any;
29
- const root = render(<Header currentLhr={currentLhr}/>, {wrapper});
28
+ const hashState = {index: 1} as any;
29
+ const root = render(<Header hashState={hashState}/>, {wrapper});
30
30
 
31
31
  expect(root.baseElement.querySelector('.Header__prev-thumbnail')).toBeTruthy();
32
32
  expect(root.baseElement.querySelector('.Header__prev-title')).toBeTruthy();
@@ -35,8 +35,8 @@ it('renders all sections for a middle step', () => {
35
35
  });
36
36
 
37
37
  it('renders only next section for first step', () => {
38
- const currentLhr = {index: 0} as any;
39
- const root = render(<Header currentLhr={currentLhr}/>, {wrapper});
38
+ const hashState = {index: 0} as any;
39
+ const root = render(<Header hashState={hashState}/>, {wrapper});
40
40
 
41
41
  expect(root.baseElement.querySelector('.Header__prev-thumbnail')).toBeFalsy();
42
42
  expect(root.baseElement.querySelector('.Header__prev-title')).toBeFalsy();
@@ -45,8 +45,8 @@ it('renders only next section for first step', () => {
45
45
  });
46
46
 
47
47
  it('renders only previous section for last step', () => {
48
- const currentLhr = {index: 3} as any;
49
- const root = render(<Header currentLhr={currentLhr}/>, {wrapper});
48
+ const hashState = {index: 3} as any;
49
+ const root = render(<Header hashState={hashState}/>, {wrapper});
50
50
 
51
51
  expect(root.baseElement.querySelector('.Header__prev-thumbnail')).toBeTruthy();
52
52
  expect(root.baseElement.querySelector('.Header__prev-title')).toBeTruthy();
@@ -10,7 +10,7 @@ import {JSDOM} from 'jsdom';
10
10
  /**
11
11
  * The jest environment "jsdom" does not work when preact is combined with the report renderer.
12
12
  */
13
- export function setupJsDom() {
13
+ function setupJsDom() {
14
14
  const {window} = new JSDOM(undefined, {
15
15
  url: 'file:///Users/example/report.html/',
16
16
  });
@@ -8,7 +8,7 @@ import {render} from '@testing-library/preact';
8
8
  import {FunctionComponent} from 'preact';
9
9
 
10
10
  import {I18nProvider} from '../../src/i18n/i18n';
11
- import {SidebarHeader, SidebarSummary} from '../../src/sidebar/sidebar';
11
+ import {SidebarHeader, SidebarRuntimeSettings, SidebarSummary} from '../../src/sidebar/sidebar';
12
12
  import {FlowResultContext} from '../../src/util';
13
13
  import {flowResult} from '../sample-flow';
14
14
 
@@ -45,3 +45,45 @@ describe('SidebarSummary', () => {
45
45
  expect(link.classList).toContain('Sidebar--current');
46
46
  });
47
47
  });
48
+
49
+ describe('SidebarRuntimeSettings', () => {
50
+ it('displays default runtime settings', async () => {
51
+ const settings = {
52
+ formFactor: 'mobile',
53
+ throttlingMethod: 'simulate',
54
+ throttling: {
55
+ cpuSlowdownMultiplier: 4,
56
+ requestLatencyMs: 150 * 3.75,
57
+ downloadThroughputKbps: 1.6 * 1024 * 0.9,
58
+ uploadThroughputKbps: 750 * 0.9,
59
+ throughputKbps: 1.6 * 1024,
60
+ rttMs: 150,
61
+ },
62
+ } as any;
63
+ const root = render(<SidebarRuntimeSettings settings={settings}/>, {wrapper});
64
+
65
+ expect(root.getByText('Emulated Moto G4')).toBeTruthy();
66
+ expect(root.getByText('Slow 4G throttling')).toBeTruthy();
67
+ expect(root.getByText('4x slowdown'));
68
+ });
69
+
70
+ it('displays custom runtime settings', async () => {
71
+ const settings = {
72
+ formFactor: 'desktop',
73
+ throttlingMethod: 'devtools',
74
+ throttling: {
75
+ cpuSlowdownMultiplier: 1,
76
+ requestLatencyMs: 1,
77
+ downloadThroughputKbps: 1,
78
+ uploadThroughputKbps: 1,
79
+ throughputKbps: 1,
80
+ rttMs: 1,
81
+ },
82
+ } as any;
83
+ const root = render(<SidebarRuntimeSettings settings={settings}/>, {wrapper});
84
+
85
+ expect(root.getByText('Emulated Desktop')).toBeTruthy();
86
+ expect(root.getByText('Custom throttling')).toBeTruthy();
87
+ expect(root.getByText('1x slowdown'));
88
+ });
89
+ });
@@ -51,7 +51,7 @@ describe('SummaryTooltip', () => {
51
51
  expect(() => root.getByText(/^[0-9]+$/)).toThrow();
52
52
  expect(root.getByText('2 audits passed')).toBeTruthy();
53
53
  expect(root.getByText('3 passable audits')).toBeTruthy();
54
- expect(root.getByText('https://example.com'));
54
+ expect(root.getByText('https://example.com')).toBeTruthy();
55
55
  });
56
56
 
57
57
  it('renders tooltip without rating', async () => {
@@ -76,7 +76,7 @@ describe('SummaryTooltip', () => {
76
76
  expect(() => root.getByText(/^[0-9]+$/)).toThrow();
77
77
  expect(root.getByText('2 audits passed')).toBeTruthy();
78
78
  expect(root.getByText('3 passable audits')).toBeTruthy();
79
- expect(root.getByText('https://example.com'));
79
+ expect(root.getByText('https://example.com')).toBeTruthy();
80
80
  });
81
81
 
82
82
  it('renders scored category tooltip with score', async () => {
@@ -101,7 +101,7 @@ describe('SummaryTooltip', () => {
101
101
  expect(root.getByText('100')).toBeTruthy();
102
102
  expect(root.getByText('2 audits passed')).toBeTruthy();
103
103
  expect(root.getByText('3 passable audits')).toBeTruthy();
104
- expect(root.getByText('https://example.com'));
104
+ expect(root.getByText('https://example.com')).toBeTruthy();
105
105
  });
106
106
 
107
107
  it('renders informative audit count if any', async () => {
@@ -127,7 +127,7 @@ describe('SummaryTooltip', () => {
127
127
  expect(root.getByText('2 audits passed')).toBeTruthy();
128
128
  expect(root.getByText('2 passable audits')).toBeTruthy();
129
129
  expect(root.getByText('1 informative audit')).toBeTruthy();
130
- expect(root.getByText('https://example.com'));
130
+ expect(root.getByText('https://example.com')).toBeTruthy();
131
131
  });
132
132
 
133
133
  it('renders highest impact audits', async () => {
@@ -192,7 +192,7 @@ describe('SummaryTooltip', () => {
192
192
  auditRefs: [
193
193
  /* eslint-disable max-len */
194
194
  {result: {score: 1, scoreDisplayMode: 'binary', title: 'Audit 1'}, weight: 1, group: 'diagnostics'},
195
- {result: {score: 0, scoreDisplayMode: 'binary', title: 'Audit 2'}, weight: 1},
195
+ {result: {score: 0, scoreDisplayMode: 'binary', title: 'Audit 2'}, weight: 1, group: 'hidden'},
196
196
  {result: {score: null, scoreDisplayMode: 'informative', title: 'Audit 3'}, weight: 1, group: 'diagnostics'},
197
197
  /* eslint-enable max-len */
198
198
  ],
@@ -9,7 +9,7 @@ import {renderHook} from '@testing-library/preact-hooks';
9
9
  import {FunctionComponent} from 'preact';
10
10
  import {act} from 'preact/test-utils';
11
11
 
12
- import {FlowResultContext, useCurrentLhr} from '../src/util';
12
+ import {FlowResultContext, useHashState} from '../src/util';
13
13
  import {flowResult} from './sample-flow';
14
14
 
15
15
  let wrapper: FunctionComponent;
@@ -21,54 +21,75 @@ beforeEach(() => {
21
21
  );
22
22
  });
23
23
 
24
- describe('useCurrentLhr', () => {
24
+ describe('useHashState', () => {
25
25
  it('gets current lhr index from url hash', () => {
26
26
  global.location.hash = '#index=1';
27
- const {result} = renderHook(() => useCurrentLhr(), {wrapper});
27
+ const {result} = renderHook(() => useHashState(), {wrapper});
28
28
  expect(console.warn).not.toHaveBeenCalled();
29
29
  expect(result.current).toEqual({
30
30
  index: 1,
31
- value: flowResult.steps[1].lhr,
31
+ currentLhr: flowResult.steps[1].lhr,
32
+ anchor: null,
33
+ });
34
+ });
35
+
36
+ it('gets anchor id from url hash', () => {
37
+ global.location.hash = '#index=1&anchor=seo';
38
+ const {result} = renderHook(() => useHashState(), {wrapper});
39
+ expect(console.warn).not.toHaveBeenCalled();
40
+ expect(result.current).toEqual({
41
+ index: 1,
42
+ currentLhr: flowResult.steps[1].lhr,
43
+ anchor: 'seo',
32
44
  });
33
45
  });
34
46
 
35
47
  it('changes on navigation', async () => {
36
48
  global.location.hash = '#index=1';
37
- const render = renderHook(() => useCurrentLhr(), {wrapper});
49
+ const render = renderHook(() => useHashState(), {wrapper});
38
50
 
39
51
  expect(render.result.current).toEqual({
40
52
  index: 1,
41
- value: flowResult.steps[1].lhr,
53
+ currentLhr: flowResult.steps[1].lhr,
54
+ anchor: null,
42
55
  });
43
56
 
44
57
  await act(() => {
45
- global.location.hash = '#index=2';
58
+ global.location.hash = '#index=2&anchor=seo';
46
59
  });
47
60
  await render.waitForNextUpdate();
48
61
 
49
62
  expect(console.warn).not.toHaveBeenCalled();
50
63
  expect(render.result.current).toEqual({
51
64
  index: 2,
52
- value: flowResult.steps[2].lhr,
65
+ currentLhr: flowResult.steps[2].lhr,
66
+ anchor: 'seo',
53
67
  });
54
68
  });
55
69
 
56
70
  it('return null if lhr index is unset', () => {
57
- const {result} = renderHook(() => useCurrentLhr(), {wrapper});
71
+ const {result} = renderHook(() => useHashState(), {wrapper});
58
72
  expect(console.warn).not.toHaveBeenCalled();
59
73
  expect(result.current).toBeNull();
60
74
  });
61
75
 
62
76
  it('return null if lhr index is out of bounds', () => {
63
77
  global.location.hash = '#index=5';
64
- const {result} = renderHook(() => useCurrentLhr(), {wrapper});
78
+ const {result} = renderHook(() => useHashState(), {wrapper});
65
79
  expect(console.warn).toHaveBeenCalled();
66
80
  expect(result.current).toBeNull();
67
81
  });
68
82
 
69
83
  it('returns null for invalid value', () => {
70
84
  global.location.hash = '#index=OHNO';
71
- const {result} = renderHook(() => useCurrentLhr(), {wrapper});
85
+ const {result} = renderHook(() => useHashState(), {wrapper});
86
+ expect(console.warn).toHaveBeenCalled();
87
+ expect(result.current).toBeNull();
88
+ });
89
+
90
+ it('returns null for invalid value with valid anchor', () => {
91
+ global.location.hash = '#index=OHNO&anchor=seo';
92
+ const {result} = renderHook(() => useHashState(), {wrapper});
72
93
  expect(console.warn).toHaveBeenCalled();
73
94
  expect(result.current).toBeNull();
74
95
  });
@@ -23,5 +23,7 @@ module.exports = {
23
23
  ],
24
24
  'newlines-between': 'always',
25
25
  }],
26
+ 'import/group-exports': 2,
27
+ 'import/exports-last': 2,
26
28
  },
27
29
  };
@@ -156,7 +156,7 @@ function getFlags(manualArgv, options = {}) {
156
156
  },
157
157
  'hostname': {
158
158
  type: 'string',
159
- default: 'localhost',
159
+ default: '127.0.0.1',
160
160
  describe: 'The hostname to use for the debugging protocol.',
161
161
  },
162
162
  'form-factor': {
@@ -11,9 +11,12 @@
11
11
  * Currently uses `lighthouse-dt-bundle.js`.
12
12
  */
13
13
 
14
+ import fs from 'fs';
15
+
14
16
  import ChromeLauncher from 'chrome-launcher';
15
17
 
16
18
  import ChromeProtocol from '../../../../lighthouse-core/gather/connections/cri.js';
19
+ import {LH_ROOT} from '../../../../root.js';
17
20
 
18
21
  const originalRequire = global.require;
19
22
  if (typeof globalThis === 'undefined') {
@@ -22,8 +25,7 @@ if (typeof globalThis === 'undefined') {
22
25
  }
23
26
 
24
27
  // Load bundle, which creates a `global.runBundledLighthouse`.
25
- // @ts-ignore - file exists if `yarn build-all` is run, but not used for types anyways.
26
- import '../../../../dist/lighthouse-dt-bundle.js'; // eslint-disable-line
28
+ eval(fs.readFileSync(LH_ROOT + '/dist/lighthouse-dt-bundle.js', 'utf-8'));
27
29
 
28
30
  global.require = originalRequire;
29
31
 
@@ -215,7 +215,7 @@ function logChildProcessError(localConsole, err) {
215
215
  localConsole.adoptStdStrings(err);
216
216
  }
217
217
 
218
- localConsole.log(log.redify('Error: ') + err.message);
218
+ localConsole.log(log.redify('Error: ') + err + '\n' + err.stack);
219
219
  }
220
220
 
221
221
  /**
@@ -7,7 +7,7 @@
7
7
 
8
8
  const {isUnderTest} = require('../lib/lh-env.js');
9
9
  const statistics = require('../lib/statistics.js');
10
- const Util = require('../util-commonjs.js');
10
+ const {Util} = require('../util-commonjs.js');
11
11
 
12
12
  const DEFAULT_PASS = 'defaultPass';
13
13
 
@@ -10,6 +10,7 @@ const LanternMetric = require('./lantern-metric.js');
10
10
  const BaseNode = require('../../lib/dependency-graph/base-node.js');
11
11
  const LanternFirstContentfulPaint = require('./lantern-first-contentful-paint.js');
12
12
  const LanternInteractive = require('./lantern-interactive.js');
13
+ const {BLOCKING_TIME_THRESHOLD, calculateSumOfBlockingTime} = require('./tbt-utils.js');
13
14
 
14
15
  /** @typedef {BaseNode.Node} Node */
15
16
 
@@ -66,9 +67,7 @@ class LanternTotalBlockingTime extends LanternMetric {
66
67
  ? extras.interactiveResult.optimisticEstimate.timeInMs
67
68
  : extras.interactiveResult.pessimisticEstimate.timeInMs;
68
69
 
69
- // Require here to resolve circular dependency.
70
- const TotalBlockingTime = require('./total-blocking-time.js');
71
- const minDurationMs = TotalBlockingTime.BLOCKING_TIME_THRESHOLD;
70
+ const minDurationMs = BLOCKING_TIME_THRESHOLD;
72
71
 
73
72
  const events = LanternTotalBlockingTime.getTopLevelEvents(
74
73
  simulation.nodeTimings,
@@ -76,7 +75,7 @@ class LanternTotalBlockingTime extends LanternMetric {
76
75
  );
77
76
 
78
77
  return {
79
- timeInMs: TotalBlockingTime.calculateSumOfBlockingTime(
78
+ timeInMs: calculateSumOfBlockingTime(
80
79
  events,
81
80
  fcpTimeInMs,
82
81
  interactiveTimeMs
@@ -0,0 +1,57 @@
1
+ /**
2
+ * @license Copyright 2021 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
+ 'use strict';
7
+
8
+ const BLOCKING_TIME_THRESHOLD = 50;
9
+
10
+ /**
11
+ * @param {Array<{start: number, end: number, duration: number}>} topLevelEvents
12
+ * @param {number} startTimeMs
13
+ * @param {number} endTimeMs
14
+ * @return {number}
15
+ */
16
+ function calculateSumOfBlockingTime(topLevelEvents, startTimeMs, endTimeMs) {
17
+ if (endTimeMs <= startTimeMs) return 0;
18
+
19
+ const threshold = BLOCKING_TIME_THRESHOLD;
20
+ let sumBlockingTime = 0;
21
+ for (const event of topLevelEvents) {
22
+ // Early exit for small tasks, which should far outnumber long tasks.
23
+ if (event.duration < threshold) continue;
24
+
25
+ // We only want to consider tasks that fall in our time range (FCP and TTI for navigations).
26
+ // FCP is picked as the lower bound because there is little risk of user input happening
27
+ // before FCP so Long Queuing Qelay regions do not harm user experience. Developers should be
28
+ // optimizing to reach FCP as fast as possible without having to worry about task lengths.
29
+ if (event.end < startTimeMs) continue;
30
+
31
+ // TTI is picked as the upper bound because we want a well defined end point for page load.
32
+ if (event.start > endTimeMs) continue;
33
+
34
+ // We first perform the clipping, and then calculate Blocking Region. So if we have a 150ms
35
+ // task [0, 150] and FCP happens midway at 50ms, we first clip the task to [50, 150], and then
36
+ // calculate the Blocking Region to be [100, 150]. The rational here is that tasks before FCP
37
+ // are unimportant, so we care whether the main thread is busy more than 50ms at a time only
38
+ // after FCP.
39
+ const clippedStart = Math.max(event.start, startTimeMs);
40
+ const clippedEnd = Math.min(event.end, endTimeMs);
41
+ const clippedDuration = clippedEnd - clippedStart;
42
+ if (clippedDuration < threshold) continue;
43
+
44
+ // The duration of the task beyond 50ms at the beginning is considered the Blocking Region.
45
+ // Example:
46
+ // [ 250ms Task ]
47
+ // | First 50ms | Blocking Region (200ms) |
48
+ sumBlockingTime += clippedDuration - threshold;
49
+ }
50
+
51
+ return sumBlockingTime;
52
+ }
53
+
54
+ module.exports = {
55
+ BLOCKING_TIME_THRESHOLD,
56
+ calculateSumOfBlockingTime,
57
+ };
@@ -10,6 +10,7 @@ const ComputedMetric = require('./metric.js');
10
10
  const TraceProcessor = require('../../lib/tracehouse/trace-processor.js');
11
11
  const LanternTotalBlockingTime = require('./lantern-total-blocking-time.js');
12
12
  const TimetoInteractive = require('./interactive.js');
13
+ const {calculateSumOfBlockingTime} = require('./tbt-utils.js');
13
14
 
14
15
  /**
15
16
  * @fileoverview This audit determines Total Blocking Time.
@@ -25,57 +26,6 @@ const TimetoInteractive = require('./interactive.js');
25
26
  * to smaller improvements to main thread responsiveness.
26
27
  */
27
28
  class TotalBlockingTime extends ComputedMetric {
28
- /**
29
- * @return {number}
30
- */
31
- static get BLOCKING_TIME_THRESHOLD() {
32
- return 50;
33
- }
34
-
35
- /**
36
- * @param {Array<{start: number, end: number, duration: number}>} topLevelEvents
37
- * @param {number} startTimeMs
38
- * @param {number} endTimeMs
39
- * @return {number}
40
- */
41
- static calculateSumOfBlockingTime(topLevelEvents, startTimeMs, endTimeMs) {
42
- if (endTimeMs <= startTimeMs) return 0;
43
-
44
- const threshold = TotalBlockingTime.BLOCKING_TIME_THRESHOLD;
45
- let sumBlockingTime = 0;
46
- for (const event of topLevelEvents) {
47
- // Early exit for small tasks, which should far outnumber long tasks.
48
- if (event.duration < threshold) continue;
49
-
50
- // We only want to consider tasks that fall in our time range (FCP and TTI for navigations).
51
- // FCP is picked as the lower bound because there is little risk of user input happening
52
- // before FCP so Long Queuing Qelay regions do not harm user experience. Developers should be
53
- // optimizing to reach FCP as fast as possible without having to worry about task lengths.
54
- if (event.end < startTimeMs) continue;
55
-
56
- // TTI is picked as the upper bound because we want a well defined end point for page load.
57
- if (event.start > endTimeMs) continue;
58
-
59
- // We first perform the clipping, and then calculate Blocking Region. So if we have a 150ms
60
- // task [0, 150] and FCP happens midway at 50ms, we first clip the task to [50, 150], and then
61
- // calculate the Blocking Region to be [100, 150]. The rational here is that tasks before FCP
62
- // are unimportant, so we care whether the main thread is busy more than 50ms at a time only
63
- // after FCP.
64
- const clippedStart = Math.max(event.start, startTimeMs);
65
- const clippedEnd = Math.min(event.end, endTimeMs);
66
- const clippedDuration = clippedEnd - clippedStart;
67
- if (clippedDuration < threshold) continue;
68
-
69
- // The duration of the task beyond 50ms at the beginning is considered the Blocking Region.
70
- // Example:
71
- // [ 250ms Task ]
72
- // | First 50ms | Blocking Region (200ms) |
73
- sumBlockingTime += clippedDuration - threshold;
74
- }
75
-
76
- return sumBlockingTime;
77
- }
78
-
79
29
  /**
80
30
  * @param {LH.Artifacts.MetricComputationData} data
81
31
  * @param {LH.Artifacts.ComputedContext} context
@@ -100,7 +50,7 @@ class TotalBlockingTime extends ComputedMetric {
100
50
  const interactiveTimeMs = (await TimetoInteractive.request(metricData, context)).timing;
101
51
 
102
52
  return {
103
- timing: TotalBlockingTime.calculateSumOfBlockingTime(
53
+ timing: calculateSumOfBlockingTime(
104
54
  events,
105
55
  firstContentfulPaint,
106
56
  interactiveTimeMs
@@ -108,7 +58,7 @@ class TotalBlockingTime extends ComputedMetric {
108
58
  };
109
59
  } else {
110
60
  return {
111
- timing: TotalBlockingTime.calculateSumOfBlockingTime(
61
+ timing: calculateSumOfBlockingTime(
112
62
  events,
113
63
  0,
114
64
  data.processedTrace.timestamps.traceEnd
@@ -10,7 +10,7 @@ const NetworkRecords = require('./network-records.js');
10
10
  const URL = require('../lib/url-shim.js');
11
11
  const NetworkRequest = require('../lib/network-request.js');
12
12
  const Budget = require('../config/budget.js');
13
- const Util = require('../util-commonjs.js');
13
+ const {Util} = require('../util-commonjs.js');
14
14
 
15
15
  /** @typedef {{count: number, resourceSize: number, transferSize: number}} ResourceEntry */
16
16