lighthouse 8.6.0-dev.20211101 → 8.6.0-dev.20211105

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 (107) hide show
  1. package/dist/report/flow.js +14 -14
  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/common.tsx +11 -4
  7. package/flow-report/src/i18n/i18n.tsx +10 -6
  8. package/flow-report/src/icons.tsx +30 -9
  9. package/flow-report/src/sidebar/sidebar.tsx +27 -8
  10. package/flow-report/src/summary/category.tsx +7 -2
  11. package/flow-report/src/summary/summary.tsx +9 -3
  12. package/flow-report/src/util.ts +21 -9
  13. package/flow-report/src/wrappers/report-renderer.tsx +10 -3
  14. package/flow-report/src/wrappers/report.tsx +9 -5
  15. package/flow-report/standalone-flow.tsx +4 -3
  16. package/flow-report/test/setup/env-setup.ts +1 -1
  17. package/flow-report/test/sidebar/sidebar-test.tsx +43 -1
  18. package/lighthouse-cli/.eslintrc.cjs +2 -0
  19. package/lighthouse-cli/cli-flags.js +1 -1
  20. package/lighthouse-core/audits/audit.js +1 -1
  21. package/lighthouse-core/computed/resource-summary.js +1 -1
  22. package/lighthouse-core/config/default-config.js +0 -2
  23. package/lighthouse-core/gather/connections/cri.js +1 -1
  24. package/lighthouse-core/lib/proto-preprocessor.js +14 -2
  25. package/lighthouse-core/lib/url-shim.js +1 -1
  26. package/lighthouse-core/util-commonjs.js +24 -9
  27. package/package.json +3 -3
  28. package/report/.eslintrc.cjs +2 -0
  29. package/report/assets/standalone-template.html +2 -3
  30. package/report/assets/styles.css +227 -251
  31. package/report/assets/templates.html +19 -29
  32. package/report/clients/bundle.js +1 -0
  33. package/report/clients/standalone.js +6 -13
  34. package/report/renderer/api.js +32 -0
  35. package/report/renderer/category-renderer.js +22 -18
  36. package/report/renderer/components.js +46 -79
  37. package/report/renderer/crc-details-renderer.js +15 -12
  38. package/report/renderer/dom.js +16 -1
  39. package/report/renderer/drop-down-menu.js +2 -2
  40. package/report/renderer/element-screenshot-renderer.js +6 -6
  41. package/report/renderer/features-util.js +1 -1
  42. package/report/renderer/open-tab.js +9 -3
  43. package/report/renderer/performance-category-renderer.js +31 -19
  44. package/report/renderer/report-renderer.js +34 -10
  45. package/report/renderer/report-ui-features.js +36 -33
  46. package/report/renderer/swap-locale-feature.js +3 -3
  47. package/report/renderer/topbar-features.js +52 -48
  48. package/report/renderer/util.js +25 -9
  49. package/report/test/clients/bundle-test.js +8 -0
  50. package/report/test/renderer/category-renderer-test.js +2 -2
  51. package/report/test/renderer/performance-category-renderer-test.js +1 -1
  52. package/report/test/renderer/report-renderer-axe-test.js +5 -2
  53. package/report/test/renderer/report-renderer-test.js +13 -10
  54. package/report/test/renderer/report-ui-features-test.js +17 -10
  55. package/report/test-assets/faux-psi-template.html +2 -7
  56. package/report/test-assets/faux-psi.js +20 -39
  57. package/report/types/html-renderer.d.ts +2 -0
  58. package/report/types/report-renderer.d.ts +23 -0
  59. package/shared/localization/locales/ar-XB.json +0 -15
  60. package/shared/localization/locales/ar.json +0 -15
  61. package/shared/localization/locales/bg.json +0 -15
  62. package/shared/localization/locales/ca.json +0 -15
  63. package/shared/localization/locales/cs.json +0 -15
  64. package/shared/localization/locales/da.json +0 -15
  65. package/shared/localization/locales/de.json +0 -15
  66. package/shared/localization/locales/el.json +0 -15
  67. package/shared/localization/locales/en-GB.json +0 -15
  68. package/shared/localization/locales/en-US.json +18 -15
  69. package/shared/localization/locales/en-XA.json +0 -15
  70. package/shared/localization/locales/en-XL.json +18 -15
  71. package/shared/localization/locales/es-419.json +0 -15
  72. package/shared/localization/locales/es.json +0 -15
  73. package/shared/localization/locales/fi.json +0 -15
  74. package/shared/localization/locales/fil.json +0 -15
  75. package/shared/localization/locales/fr.json +0 -15
  76. package/shared/localization/locales/he.json +0 -15
  77. package/shared/localization/locales/hi.json +0 -15
  78. package/shared/localization/locales/hr.json +0 -15
  79. package/shared/localization/locales/hu.json +0 -15
  80. package/shared/localization/locales/id.json +0 -15
  81. package/shared/localization/locales/it.json +0 -15
  82. package/shared/localization/locales/ja.json +0 -15
  83. package/shared/localization/locales/ko.json +0 -15
  84. package/shared/localization/locales/lt.json +0 -15
  85. package/shared/localization/locales/lv.json +0 -15
  86. package/shared/localization/locales/nl.json +0 -15
  87. package/shared/localization/locales/no.json +0 -15
  88. package/shared/localization/locales/pl.json +0 -15
  89. package/shared/localization/locales/pt-PT.json +0 -15
  90. package/shared/localization/locales/pt.json +0 -15
  91. package/shared/localization/locales/ro.json +0 -15
  92. package/shared/localization/locales/ru.json +0 -15
  93. package/shared/localization/locales/sk.json +0 -15
  94. package/shared/localization/locales/sl.json +0 -15
  95. package/shared/localization/locales/sr-Latn.json +0 -15
  96. package/shared/localization/locales/sr.json +0 -15
  97. package/shared/localization/locales/sv.json +0 -15
  98. package/shared/localization/locales/ta.json +0 -15
  99. package/shared/localization/locales/te.json +0 -15
  100. package/shared/localization/locales/th.json +0 -15
  101. package/shared/localization/locales/tr.json +0 -15
  102. package/shared/localization/locales/uk.json +0 -15
  103. package/shared/localization/locales/vi.json +0 -15
  104. package/shared/localization/locales/zh-HK.json +0 -15
  105. package/shared/localization/locales/zh-TW.json +0 -15
  106. package/shared/localization/locales/zh.json +0 -15
  107. package/lighthouse-core/audits/dobetterweb/external-anchors-use-rel-noopener.js +0 -96
@@ -24,15 +24,13 @@ export class TopbarFeatures {
24
24
  this.lhr; // eslint-disable-line no-unused-expressions
25
25
  this._reportUIFeatures = reportUIFeatures;
26
26
  this._dom = dom;
27
- /** @type {Document} */
28
- this._document = this._dom.document();
29
27
  this._dropDownMenu = new DropDownMenu(this._dom);
30
28
  this._copyAttempt = false;
31
29
  /** @type {HTMLElement} */
32
30
  this.topbarEl; // eslint-disable-line no-unused-expressions
33
31
  /** @type {HTMLElement} */
34
- this.scoreScaleEl; // eslint-disable-line no-unused-expressions
35
- /** @type {HTMLElement} */
32
+ this.categoriesEl; // eslint-disable-line no-unused-expressions
33
+ /** @type {HTMLElement?} */
36
34
  this.stickyHeaderEl; // eslint-disable-line no-unused-expressions
37
35
  /** @type {HTMLElement} */
38
36
  this.highlightEl; // eslint-disable-line no-unused-expressions
@@ -40,7 +38,6 @@ export class TopbarFeatures {
40
38
  this.onKeyUp = this.onKeyUp.bind(this);
41
39
  this.onCopy = this.onCopy.bind(this);
42
40
  this.collapseAllDetails = this.collapseAllDetails.bind(this);
43
- this._updateStickyHeaderOnScroll = this._updateStickyHeaderOnScroll.bind(this);
44
41
  }
45
42
 
46
43
  /**
@@ -48,33 +45,15 @@ export class TopbarFeatures {
48
45
  */
49
46
  enable(lhr) {
50
47
  this.lhr = lhr;
51
- this._document.addEventListener('keyup', this.onKeyUp);
52
- this._document.addEventListener('copy', this.onCopy);
48
+ this._dom.rootEl.addEventListener('keyup', this.onKeyUp);
49
+ this._dom.document().addEventListener('copy', this.onCopy);
53
50
  this._dropDownMenu.setup(this.onDropDownMenuClick);
54
51
  this._setUpCollapseDetailsAfterPrinting();
55
52
 
56
- const topbarLogo = this._dom.find('.lh-topbar__logo', this._document);
53
+ const topbarLogo = this._dom.find('.lh-topbar__logo', this._dom.rootEl);
57
54
  topbarLogo.addEventListener('click', () => toggleDarkTheme(this._dom));
58
55
 
59
- // There is only a sticky header when at least 2 categories are present.
60
- if (Object.keys(this.lhr.categories).length >= 2) {
61
- this._setupStickyHeaderElements();
62
- const containerEl = this._dom.find('.lh-container', this._document);
63
- const elToAddScrollListener = this._getScrollParent(containerEl);
64
- elToAddScrollListener.addEventListener('scroll', this._updateStickyHeaderOnScroll);
65
-
66
- // Use ResizeObserver where available.
67
- // TODO: there is an issue with incorrect position numbers and, as a result, performance
68
- // issues due to layout thrashing.
69
- // See https://github.com/GoogleChrome/lighthouse/pull/9023/files#r288822287 for details.
70
- // For now, limit to DevTools.
71
- if (this._dom.isDevTools()) {
72
- const resizeObserver = new window.ResizeObserver(this._updateStickyHeaderOnScroll);
73
- resizeObserver.observe(containerEl);
74
- } else {
75
- window.addEventListener('resize', this._updateStickyHeaderOnScroll);
76
- }
77
- }
56
+ this._setupStickyHeader();
78
57
  }
79
58
 
80
59
  /**
@@ -112,7 +91,7 @@ export class TopbarFeatures {
112
91
  try {
113
92
  this._reportUIFeatures._saveFile(new Blob([htmlStr], {type: 'text/html'}));
114
93
  } catch (e) {
115
- this._dom.fireEventOn('lh-log', this._document, {
94
+ this._dom.fireEventOn('lh-log', this._dom.document(), {
116
95
  cmd: 'error', msg: 'Could not export as HTML. ' + e.message,
117
96
  });
118
97
  }
@@ -152,7 +131,7 @@ export class TopbarFeatures {
152
131
  e.preventDefault();
153
132
  e.clipboardData.setData('text/plain', JSON.stringify(this.lhr, null, 2));
154
133
 
155
- this._dom.fireEventOn('lh-log', this._document, {
134
+ this._dom.fireEventOn('lh-log', this._dom.document(), {
156
135
  cmd: 'log', msg: 'Report JSON copied to clipboard',
157
136
  });
158
137
  }
@@ -164,28 +143,28 @@ export class TopbarFeatures {
164
143
  * Copies the report JSON to the clipboard (if supported by the browser).
165
144
  */
166
145
  onCopyButtonClick() {
167
- this._dom.fireEventOn('lh-analytics', this._document, {
146
+ this._dom.fireEventOn('lh-analytics', this._dom.document(), {
168
147
  cmd: 'send',
169
148
  fields: {hitType: 'event', eventCategory: 'report', eventAction: 'copy'},
170
149
  });
171
150
 
172
151
  try {
173
- if (this._document.queryCommandSupported('copy')) {
152
+ if (this._dom.document().queryCommandSupported('copy')) {
174
153
  this._copyAttempt = true;
175
154
 
176
155
  // Note: In Safari 10.0.1, execCommand('copy') returns true if there's
177
156
  // a valid text selection on the page. See http://caniuse.com/#feat=clipboard.
178
- if (!this._document.execCommand('copy')) {
157
+ if (!this._dom.document().execCommand('copy')) {
179
158
  this._copyAttempt = false; // Prevent event handler from seeing this as a copy attempt.
180
159
 
181
- this._dom.fireEventOn('lh-log', this._document, {
160
+ this._dom.fireEventOn('lh-log', this._dom.document(), {
182
161
  cmd: 'warn', msg: 'Your browser does not support copy to clipboard.',
183
162
  });
184
163
  }
185
164
  }
186
165
  } catch (e) {
187
166
  this._copyAttempt = false;
188
- this._dom.fireEventOn('lh-log', this._document, {cmd: 'log', msg: e.message});
167
+ this._dom.fireEventOn('lh-log', this._dom.document(), {cmd: 'log', msg: e.message});
189
168
  }
190
169
  }
191
170
 
@@ -206,7 +185,7 @@ export class TopbarFeatures {
206
185
  * open a `<details>` element.
207
186
  */
208
187
  expandAllDetails() {
209
- const details = this._dom.findAll('.lh-categories details', this._document);
188
+ const details = this._dom.findAll('.lh-categories details', this._dom.rootEl);
210
189
  details.map(detail => detail.open = true);
211
190
  }
212
191
 
@@ -215,7 +194,7 @@ export class TopbarFeatures {
215
194
  * open a `<details>` element.
216
195
  */
217
196
  collapseAllDetails() {
218
- const details = this._dom.findAll('.lh-categories details', this._document);
197
+ const details = this._dom.findAll('.lh-categories details', this._dom.rootEl);
219
198
  details.map(detail => detail.open = false);
220
199
  }
221
200
 
@@ -235,7 +214,7 @@ export class TopbarFeatures {
235
214
  /**
236
215
  * Finds the first scrollable ancestor of `element`. Falls back to the document.
237
216
  * @param {Element} element
238
- * @return {Node}
217
+ * @return {Element | Document}
239
218
  */
240
219
  _getScrollParent(element) {
241
220
  const {overflowY} = window.getComputedStyle(element);
@@ -273,24 +252,49 @@ export class TopbarFeatures {
273
252
  }
274
253
  }
275
254
 
276
- _setupStickyHeaderElements() {
277
- this.topbarEl = this._dom.find('div.lh-topbar', this._document);
278
- this.scoreScaleEl = this._dom.find('div.lh-scorescale', this._document);
279
- this.stickyHeaderEl = this._dom.find('div.lh-sticky-header', this._document);
255
+ _setupStickyHeader() {
256
+ // Cache these elements to avoid qSA on each onscroll.
257
+ this.topbarEl = this._dom.find('div.lh-topbar', this._dom.rootEl);
258
+ this.categoriesEl = this._dom.find('div.lh-categories', this._dom.rootEl);
259
+
260
+ // Defer behind rAF to avoid forcing layout.
261
+ window.requestAnimationFrame(() => window.requestAnimationFrame(() => {
262
+ // Only present in the DOM if it'll be used (>=2 categories)
263
+ try {
264
+ this.stickyHeaderEl = this._dom.find('div.lh-sticky-header', this._dom.rootEl);
265
+ } catch {
266
+ return;
267
+ }
280
268
 
281
- // Highlighter will be absolutely positioned at first gauge, then transformed on scroll.
282
- this.highlightEl = this._dom.createChildOf(this.stickyHeaderEl, 'div', 'lh-highlighter');
269
+ // Highlighter will be absolutely positioned at first gauge, then transformed on scroll.
270
+ this.highlightEl = this._dom.createChildOf(this.stickyHeaderEl, 'div', 'lh-highlighter');
271
+
272
+ // Update sticky header visibility and highlight when page scrolls/resizes.
273
+ const scrollParent = this._getScrollParent(this._dom.rootEl);
274
+ // The 'scroll' handler must be should be on {Element | Document}...
275
+ scrollParent.addEventListener('scroll', () => this._updateStickyHeader());
276
+ // However resizeObserver needs an element, *not* the document.
277
+ const resizeTarget = scrollParent instanceof window.Document
278
+ ? document.documentElement
279
+ : scrollParent;
280
+ new window.ResizeObserver(() => this._updateStickyHeader()).observe(resizeTarget);
281
+ }));
283
282
  }
284
283
 
285
- _updateStickyHeaderOnScroll() {
286
- // Show sticky header when the score scale begins to go underneath the topbar.
284
+ /**
285
+ * Toggle visibility and update highlighter position
286
+ */
287
+ _updateStickyHeader() {
288
+ if (!this.stickyHeaderEl) return;
289
+
290
+ // Show sticky header when the main 5 gauges clear the topbar.
287
291
  const topbarBottom = this.topbarEl.getBoundingClientRect().bottom;
288
- const scoreScaleTop = this.scoreScaleEl.getBoundingClientRect().top;
289
- const showStickyHeader = topbarBottom >= scoreScaleTop;
292
+ const categoriesTop = this.categoriesEl.getBoundingClientRect().top;
293
+ const showStickyHeader = topbarBottom >= categoriesTop;
290
294
 
291
295
  // Highlight mini gauge when section is in view.
292
296
  // In view = the last category that starts above the middle of the window.
293
- const categoryEls = Array.from(this._document.querySelectorAll('.lh-category'));
297
+ const categoryEls = Array.from(this._dom.rootEl.querySelectorAll('.lh-category'));
294
298
  const categoriesAboveTheMiddle =
295
299
  categoryEls.filter(el => el.getBoundingClientRect().top - window.innerHeight / 2 < 0);
296
300
  const highlightIndex =
@@ -38,7 +38,11 @@ const listOfTlds = [
38
38
  'web', 'spb', 'blog', 'jus', 'kiev', 'mil', 'wi', 'qc', 'ca', 'bel', 'on',
39
39
  ];
40
40
 
41
- export class Util {
41
+ class Util {
42
+ /** @type {I18n<typeof UIStrings>} */
43
+ // @ts-expect-error: Is set in report renderer.
44
+ static i18n = null;
45
+
42
46
  static get PASS_THRESHOLD() {
43
47
  return PASS_THRESHOLD;
44
48
  }
@@ -417,7 +421,7 @@ export class Util {
417
421
  throttling.downloadThroughputKbps === 1.6 * 1024 * 0.9 &&
418
422
  throttling.uploadThroughputKbps === 750 * 0.9;
419
423
  };
420
- summary = `${isSlow4G() ? 'Slow 4G' : 'Custom'} throttling by DevTools`;
424
+ summary = isSlow4G() ? Util.i18n.strings.runtimeSlow4g : Util.i18n.strings.runtimeCustom;
421
425
  break;
422
426
  }
423
427
  case 'simulate': {
@@ -429,7 +433,7 @@ export class Util {
429
433
  const isSlow4G = () => {
430
434
  return rttMs === 150 && throughputKbps === 1.6 * 1024;
431
435
  };
432
- summary = isSlow4G() ? 'Simulated slow 4G' : 'Custom simulated throttling';
436
+ summary = isSlow4G() ? Util.i18n.strings.runtimeSlow4g : Util.i18n.strings.runtimeCustom;
433
437
  break;
434
438
  }
435
439
  default:
@@ -554,14 +558,10 @@ Util.getUniqueSuffix = (() => {
554
558
  };
555
559
  })();
556
560
 
557
- /** @type {I18n<typeof Util['UIStrings']>} */
558
- // @ts-expect-error: Is set in report renderer.
559
- Util.i18n = null;
560
-
561
561
  /**
562
562
  * Report-renderer-specific strings.
563
563
  */
564
- Util.UIStrings = {
564
+ const UIStrings = {
565
565
  /** Disclaimer shown to users below the metric values (First Contentful Paint, Time to Interactive, etc) to warn them that the numbers they see will likely change slightly the next time they run Lighthouse. */
566
566
  varianceDisclaimer: 'Values are estimated and may vary. The [performance score is calculated](https://web.dev/performance-scoring/) directly from these metrics.',
567
567
  /** Text link pointing to an interactive calculator that explains Lighthouse scoring. The link text should be fairly short. */
@@ -661,6 +661,22 @@ Util.UIStrings = {
661
661
 
662
662
  /** Descriptive explanation for environment throttling that was provided by the runtime environment instead of provided by Lighthouse throttling. */
663
663
  throttlingProvided: 'Provided by environment',
664
+ /** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Show' and 'Hide'. */
665
+ show: 'Show',
666
+ /** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Show' and 'Hide'. */
667
+ hide: 'Hide',
668
+ /** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Expand view' and 'Collapse view'. */
669
+ expandView: 'Expand view',
670
+ /** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Expand view' and 'Collapse view'. */
671
+ collapseView: 'Collapse view',
672
+ /** Label indicating that Lighthouse throttled the page to emulate a slow 4G network connection. */
673
+ runtimeSlow4g: 'Slow 4G throttling',
674
+ /** Label indicating that Lighthouse throttled the page using custom throttling settings. */
675
+ runtimeCustom: 'Custom throttling',
664
676
  };
677
+ Util.UIStrings = UIStrings;
665
678
 
666
- export const UIStrings = Util.UIStrings;
679
+ export {
680
+ Util,
681
+ UIStrings,
682
+ };
@@ -9,6 +9,7 @@
9
9
  import fs from 'fs';
10
10
 
11
11
  import jsdom from 'jsdom';
12
+ import {jest} from '@jest/globals';
12
13
 
13
14
  import * as lighthouseRenderer from '../../clients/bundle.js';
14
15
  import {LH_ROOT} from '../../../root.js';
@@ -22,16 +23,23 @@ const sampleResultsStr =
22
23
  describe('lighthouseRenderer bundle', () => {
23
24
  let document;
24
25
  beforeAll(() => {
26
+ global.console.warn = jest.fn();
27
+
25
28
  const {window} = new jsdom.JSDOM();
26
29
  document = window.document;
27
30
 
28
31
  global.window = global.self = window;
32
+ global.window.requestAnimationFrame = fn => fn();
29
33
  // Stub out matchMedia for Node.
30
34
  global.self.matchMedia = function() {
31
35
  return {
32
36
  addListener: function() {},
33
37
  };
34
38
  };
39
+ global.window.ResizeObserver = class ResizeObserver {
40
+ observe() { }
41
+ unobserve() { }
42
+ };
35
43
  });
36
44
 
37
45
  afterAll(() => {
@@ -197,8 +197,8 @@ describe('CategoryRenderer', () => {
197
197
  'score shows informative and dash icon');
198
198
 
199
199
  assert.ok(pwaCategory.manualDescription);
200
- const description = categoryDOM
201
- .querySelector('.lh-clump--manual .lh-audit-group__description').textContent;
200
+ const description = categoryDOM.querySelector('.lh-clump--manual').closest('.lh-audit-group')
201
+ .querySelector('.lh-audit-group__description').textContent;
202
202
  // may need to be adjusted if description includes a link at the beginning
203
203
  assert.ok(description.startsWith(pwaCategory.manualDescription.substring(0, 20)),
204
204
  'no manual description');
@@ -192,7 +192,7 @@ describe('PerfCategoryRenderer', () => {
192
192
 
193
193
  it('renders the passed audits', () => {
194
194
  const categoryDOM = renderer.render(category, sampleResults.categoryGroups);
195
- const passedSection = categoryDOM.querySelector('.lh-category > .lh-clump--passed');
195
+ const passedSection = categoryDOM.querySelector('.lh-clump--passed');
196
196
 
197
197
  const passedAudits = category.auditRefs.filter(audit =>
198
198
  !audit.group &&
@@ -8,6 +8,7 @@
8
8
  /* eslint-env jest */
9
9
 
10
10
  import jsdom from 'jsdom';
11
+ import {jest} from '@jest/globals';
11
12
 
12
13
  import {Util} from '../../renderer/util.js';
13
14
  import {DOM} from '../../renderer/dom.js';
@@ -23,6 +24,8 @@ describe('ReportRendererAxe', () => {
23
24
  let sampleResults;
24
25
 
25
26
  beforeAll(async () => {
27
+ global.console.warn = jest.fn();
28
+
26
29
  const {window} = new jsdom.JSDOM();
27
30
  const dom = new DOM(window.document);
28
31
  const detailsRenderer = new DetailsRenderer(dom);
@@ -47,10 +50,10 @@ describe('ReportRendererAxe', () => {
47
50
  });
48
51
 
49
52
  it('renders without axe violations', async () => {
50
- const container = renderer._dom._document.createElement('main');
53
+ const container = renderer._dom.document().createElement('main');
51
54
  const output = renderer.renderReport(sampleResults, container);
52
55
 
53
- renderer._dom._document.body.appendChild(container);
56
+ renderer._dom.document().body.appendChild(container);
54
57
 
55
58
  const config = {
56
59
  rules: {
@@ -10,6 +10,7 @@
10
10
  import {strict as assert} from 'assert';
11
11
 
12
12
  import jsdom from 'jsdom';
13
+ import {jest} from '@jest/globals';
13
14
 
14
15
  import {Util} from '../../renderer/util.js';
15
16
  import URL from '../../../lighthouse-core/lib/url-shim.js';
@@ -26,6 +27,8 @@ describe('ReportRenderer', () => {
26
27
  let sampleResults;
27
28
 
28
29
  beforeAll(() => {
30
+ global.console.warn = jest.fn();
31
+
29
32
  // Stub out matchMedia for Node.
30
33
  global.matchMedia = function() {
31
34
  return {
@@ -50,7 +53,7 @@ describe('ReportRenderer', () => {
50
53
 
51
54
  describe('renderReport', () => {
52
55
  it('should render a report', () => {
53
- const container = renderer._dom._document.body;
56
+ const container = renderer._dom.document().body;
54
57
  const output = renderer.renderReport(sampleResults, container);
55
58
  assert.ok(output.querySelector('.lh-header-container'), 'has a header');
56
59
  assert.ok(output.querySelector('.lh-report'), 'has report body');
@@ -60,7 +63,7 @@ describe('ReportRenderer', () => {
60
63
  });
61
64
 
62
65
  it('renders additional reports by replacing the existing one', () => {
63
- const container = renderer._dom._document.body;
66
+ const container = renderer._dom.document().body;
64
67
  const oldReport = Array.from(renderer.renderReport(sampleResults, container).children);
65
68
  const newReport = Array.from(renderer.renderReport(sampleResults, container).children);
66
69
  assert.ok(!oldReport.find(node => container.contains(node)), 'old report was removed');
@@ -86,7 +89,7 @@ describe('ReportRenderer', () => {
86
89
  auditRefs: [],
87
90
  };
88
91
 
89
- const container = renderer._dom._document.body;
92
+ const container = renderer._dom.document().body;
90
93
  const output = renderer.renderReport(sampleResultsCopy, container);
91
94
 
92
95
  function isPWAGauge(el) {
@@ -126,7 +129,7 @@ describe('ReportRenderer', () => {
126
129
  title: 'Some Plugin',
127
130
  auditRefs: [],
128
131
  };
129
- const container = renderer._dom._document.body;
132
+ const container = renderer._dom.document().body;
130
133
  const output = renderer.renderReport(sampleResultsCopy, container);
131
134
  const scoresHeaderElem = output.querySelector('.lh-scores-header');
132
135
 
@@ -140,7 +143,7 @@ describe('ReportRenderer', () => {
140
143
  });
141
144
 
142
145
  it('should not mutate a report object', () => {
143
- const container = renderer._dom._document.body;
146
+ const container = renderer._dom.document().body;
144
147
  const originalResults = JSON.parse(JSON.stringify(sampleResults));
145
148
  renderer.renderReport(sampleResults, container);
146
149
  assert.deepStrictEqual(sampleResults, originalResults);
@@ -148,13 +151,13 @@ describe('ReportRenderer', () => {
148
151
 
149
152
  it('renders no warning section when no lighthouseRunWarnings occur', () => {
150
153
  const warningResults = Object.assign({}, sampleResults, {runWarnings: []});
151
- const container = renderer._dom._document.body;
154
+ const container = renderer._dom.document().body;
152
155
  const output = renderer.renderReport(warningResults, container);
153
156
  assert.strictEqual(output.querySelector('.lh-warnings--toplevel'), null);
154
157
  });
155
158
 
156
159
  it('renders a warning section', () => {
157
- const container = renderer._dom._document.body;
160
+ const container = renderer._dom.document().body;
158
161
  const output = renderer.renderReport(sampleResults, container);
159
162
 
160
163
  const warningEls = output.querySelectorAll('.lh-warnings--toplevel > ul > li');
@@ -165,7 +168,7 @@ describe('ReportRenderer', () => {
165
168
  const warningResults = Object.assign({}, sampleResults, {
166
169
  runWarnings: ['[I am a link](https://example.com/)'],
167
170
  });
168
- const container = renderer._dom._document.body;
171
+ const container = renderer._dom.document().body;
169
172
  const output = renderer.renderReport(warningResults, container);
170
173
 
171
174
  const warningEls = output.querySelectorAll('.lh-warnings--toplevel ul li a');
@@ -196,7 +199,7 @@ describe('ReportRenderer', () => {
196
199
  // Make sure we have a channel in the LHR.
197
200
  assert.ok(lhrChannel.length > 2);
198
201
 
199
- const container = renderer._dom._document.body;
202
+ const container = renderer._dom.document().body;
200
203
  const output = renderer.renderReport(sampleResults, container);
201
204
 
202
205
  const DOCS_ORIGINS = ['https://developers.google.com', 'https://web.dev'];
@@ -225,7 +228,7 @@ describe('ReportRenderer', () => {
225
228
 
226
229
  assert.ok(notApplicableCount > 20); // Make sure something's being tested.
227
230
 
228
- const container = renderer._dom._document.body;
231
+ const container = renderer._dom.document().body;
229
232
  const reportElement = renderer.renderReport(sampleResults, container);
230
233
  const notApplicableElementCount = reportElement
231
234
  .querySelectorAll('.lh-audit--notapplicable').length;
@@ -10,6 +10,7 @@
10
10
  import {strict as assert} from 'assert';
11
11
 
12
12
  import jsdom from 'jsdom';
13
+ import {jest} from '@jest/globals';
13
14
 
14
15
  import reportAssets from '../../generator/report-assets.js';
15
16
  import {Util} from '../../renderer/util.js';
@@ -33,13 +34,15 @@ describe('ReportUIFeatures', () => {
33
34
  const categoryRenderer = new CategoryRenderer(dom, detailsRenderer);
34
35
  const renderer = new ReportRenderer(dom, categoryRenderer);
35
36
  const reportUIFeatures = new ReportUIFeatures(dom);
36
- const container = dom.find('main', dom._document);
37
+ const container = dom.find('body', dom.document());
37
38
  renderer.renderReport(lhr, container);
38
39
  reportUIFeatures.initFeatures(lhr);
39
40
  return container;
40
41
  }
41
42
 
42
43
  beforeAll(() => {
44
+ global.console.warn = jest.fn();
45
+
43
46
  // Stub out matchMedia for Node.
44
47
  global.matchMedia = function() {
45
48
  return {
@@ -57,14 +60,20 @@ describe('ReportUIFeatures', () => {
57
60
 
58
61
  global.HTMLElement = document.window.HTMLElement;
59
62
  global.HTMLInputElement = document.window.HTMLInputElement;
63
+ global.HTMLInputElement = document.window.HTMLInputElement;
60
64
 
61
65
  global.window = document.window;
66
+ global.window.requestAnimationFrame = fn => fn();
62
67
  global.window.getComputedStyle = function() {
63
68
  return {
64
69
  marginTop: '10px',
65
70
  height: '10px',
66
71
  };
67
72
  };
73
+ global.window.ResizeObserver = class ResizeObserver {
74
+ observe() { }
75
+ unobserve() { }
76
+ };
68
77
 
69
78
  dom = new DOM(document.window.document);
70
79
  sampleResults = Util.prepareReportResult(sampleResultsOrig);
@@ -256,17 +265,15 @@ describe('ReportUIFeatures', () => {
256
265
  .toThrowError('query #uses-rel-preconnect .lh-3p-filter-input not found');
257
266
  });
258
267
 
259
- it('filter is disabled and checked for when just third party resources', () => {
260
- const filterCheckbox =
261
- dom.find('#render-blocking-resources .lh-3p-filter-input', container);
262
- expect(filterCheckbox.disabled).toEqual(true);
263
- expect(filterCheckbox.checked).toEqual(true);
268
+ it('filter is hidden when just third party resources', () => {
269
+ const filterControl =
270
+ dom.find('#render-blocking-resources .lh-3p-filter', container);
271
+ expect(filterControl.hidden).toEqual(true);
264
272
  });
265
273
 
266
- it('filter is disabled and not checked for just first party resources', () => {
267
- const filterCheckbox = dom.find('#uses-text-compression .lh-3p-filter-input', container);
268
- expect(filterCheckbox.disabled).toEqual(true);
269
- expect(filterCheckbox.checked).toEqual(false);
274
+ it('filter is hidden for just first party resources', () => {
275
+ const filterControl = dom.find('#uses-text-compression .lh-3p-filter', container);
276
+ expect(filterControl.hidden).toEqual(true);
270
277
  });
271
278
  });
272
279
  });
@@ -66,7 +66,6 @@
66
66
  }
67
67
 
68
68
  .tab-panel {
69
- padding: 30px 0;
70
69
  border-top: 1px solid #ccc;
71
70
  }
72
71
 
@@ -101,13 +100,9 @@ body {
101
100
  <label for="tab2">Desktop</label>
102
101
 
103
102
  <div class="tab-panels">
104
- <section id="mobile" class="tab-panel">
105
- <main></main>
106
- </section>
103
+ <section id="mobile" class="tab-panel"></section>
107
104
 
108
- <section id="desktop" class="tab-panel">
109
- <main></main>
110
- </section>
105
+ <section id="desktop" class="tab-panel"></section>
111
106
 
112
107
  </div>
113
108
  </div>
@@ -26,43 +26,25 @@ const lighthouseRenderer = window['report'];
26
26
  for (const [tabId, lhr] of Object.entries(lhrs)) {
27
27
  await distinguishLHR(lhr, tabId);
28
28
 
29
- const container = document.querySelector(`#${tabId} main`);
29
+ const container = document.querySelector(`section#${tabId}`);
30
30
  if (!container) throw new Error('Unexpected DOM. Bailing.');
31
31
 
32
- renderLHReport(lhr, container);
33
- }
34
- })();
35
-
36
- /**
37
- * @param {LH.Result} lhrData
38
- * @param {HTMLElement} reportContainer
39
- */
40
- function renderLHReport(lhrData, reportContainer) {
41
- /**
42
- * @param {Document} doc
43
- */
44
- function getRenderer(doc) {
45
- const dom = new lighthouseRenderer.DOM(doc);
46
- return new lighthouseRenderer.ReportRenderer(dom);
47
- }
48
-
49
- const renderer = getRenderer(reportContainer.ownerDocument);
50
- reportContainer.classList.add('lh-root', 'lh-vars');
51
-
52
- try {
53
- renderer.renderReport(lhrData, reportContainer);
54
- // TODO: handle topbar removal better
55
- // TODO: display warnings if appropriate.
56
- for (const el of reportContainer.querySelectorAll('.lh-topbar, .lh-warnings')) {
57
- el.setAttribute('hidden', 'true');
32
+ try {
33
+ const reportRootEl = lighthouseRenderer.renderReport(lhr, {
34
+ omitTopbar: true,
35
+ disableAutoDarkModeAndFireworks: true,
36
+ });
37
+ // TODO: display warnings if appropriate.
38
+ for (const el of reportRootEl.querySelectorAll('.lh-warnings')) {
39
+ el.setAttribute('hidden', 'true');
40
+ }
41
+ container.append(reportRootEl);
42
+ } catch (e) {
43
+ console.error(e);
44
+ container.textContent = 'Error: LHR failed to render.';
58
45
  }
59
- const features = new lighthouseRenderer.ReportUIFeatures(renderer._dom);
60
- features.initFeatures(lhrData);
61
- } catch (e) {
62
- console.error(e);
63
- reportContainer.textContent = 'Error: LHR failed to render.';
64
46
  }
65
- }
47
+ })();
66
48
 
67
49
 
68
50
  /**
@@ -72,7 +54,6 @@ function renderLHReport(lhrData, reportContainer) {
72
54
  * @param {string} tabId
73
55
  */
74
56
  async function distinguishLHR(lhr, tabId) {
75
- lhr.categories.performance.title += ` ${tabId}`; // for easier identification
76
57
  if (tabId === 'desktop') {
77
58
  lhr.categories.performance.score = 0.81;
78
59
  }
@@ -101,15 +82,15 @@ async function decorateScreenshot(datauri, tabId) {
101
82
  img.src = datauri;
102
83
  });
103
84
  const c = document.createElement('canvas');
104
- c.width = img.width;
105
- c.height = img.height;
85
+ c.width = tabId === 'desktop' ? 280 : img.width;
86
+ c.height = tabId === 'desktop' ? 194 : img.height;
106
87
 
107
88
  const ctx = c.getContext('2d');
108
89
  if (!ctx) throw new Error();
109
- ctx.drawImage(img, 0, 0);
110
- ctx.font = `${img.width / 2}px serif`;
90
+ ctx.drawImage(img, 0, 0, c.width, c.height);
91
+ ctx.font = `${c.width / 2}px serif`;
111
92
  ctx.textAlign = 'center';
112
93
  ctx.globalAlpha = 0.7;
113
- ctx.fillText(tabId === 'mobile' ? '📱' : '💻', img.width / 2, Math.min(img.height / 2, 700));
94
+ ctx.fillText(tabId === 'mobile' ? '📱' : '💻', c.width / 2, Math.min(c.height / 2, 700));
114
95
  return c.toDataURL();
115
96
  }
@@ -8,6 +8,7 @@ import AuditDetails from '../../types/lhr/audit-details';
8
8
  import {FormattedIcu as FormattedIcu_, IcuMessage as IcuMessage_} from '../../types/lhr/i18n';
9
9
  import LHResult from '../../types/lhr/lhr';
10
10
  import ReportResult_ from './report-result';
11
+ import Renderer_ from './report-renderer';
11
12
  import * as Settings from '../../types/lhr/settings';
12
13
  import Treemap_ from '../../types/lhr/treemap';
13
14
 
@@ -16,6 +17,7 @@ declare global {
16
17
  module LH {
17
18
  export import Result = LHResult;
18
19
  export import ReportResult = ReportResult_;
20
+ export import Renderer = Renderer_;
19
21
  export import Locale = Settings.Locale;
20
22
  export type IcuMessage = IcuMessage_;
21
23
  export type FormattedIcu<T> = FormattedIcu_<T>;