lighthouse 8.3.0-dev.20210907 → 8.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.md +1 -1
- package/dist/report/standalone.js +196 -0
- package/jest.config.js +0 -4
- package/lighthouse-cli/cli-flags.js +6 -14
- package/lighthouse-cli/run.js +1 -1
- package/lighthouse-cli/test/smokehouse/frontends/smokehouse-bin.js +3 -9
- package/lighthouse-cli/test/smokehouse/report-assert.js +16 -36
- package/lighthouse-core/audits/large-javascript-libraries.js +166 -0
- package/lighthouse-core/audits/seo/http-status-code.js +6 -2
- package/lighthouse-core/audits/viewport.js +1 -2
- package/lighthouse-core/config/config-helpers.js +75 -9
- package/lighthouse-core/config/config.js +50 -6
- package/lighthouse-core/config/constants.js +0 -1
- package/lighthouse-core/config/default-config.js +0 -3
- package/lighthouse-core/config/experimental-config.js +8 -0
- package/lighthouse-core/config/metrics-to-audits.js +0 -1
- package/lighthouse-core/fraggle-rock/config/config.js +4 -3
- package/lighthouse-core/fraggle-rock/config/default-config.js +1 -3
- package/lighthouse-core/fraggle-rock/config/validation.js +0 -201
- package/lighthouse-core/fraggle-rock/gather/base-artifacts.js +0 -4
- package/lighthouse-core/fraggle-rock/gather/navigation-runner.js +8 -16
- package/lighthouse-core/fraggle-rock/gather/runner-helpers.js +0 -4
- package/lighthouse-core/fraggle-rock/gather/session.js +5 -30
- package/lighthouse-core/gather/driver/navigation.js +6 -7
- package/lighthouse-core/gather/driver/wait-for-condition.js +0 -27
- package/lighthouse-core/gather/fetcher.js +7 -17
- package/lighthouse-core/gather/gather-runner.js +0 -1
- package/lighthouse-core/gather/gatherers/seo/robots-txt.js +6 -2
- package/lighthouse-core/gather/gatherers/seo/tap-targets.js +2 -2
- package/lighthouse-core/gather/gatherers/stacks.js +1 -1
- package/lighthouse-core/lib/asset-saver.js +2 -2
- package/lighthouse-core/lib/cdt/SDK.js +0 -1
- package/lighthouse-core/lib/csp-evaluator.js +4 -17
- package/{report/generator → lighthouse-core/lib}/file-namer.js +0 -0
- package/lighthouse-core/lib/i18n/README.md +1 -1
- package/lighthouse-core/lib/i18n/i18n.js +7 -7
- package/lighthouse-core/lib/i18n/locales/ar-XB.json +18 -0
- package/lighthouse-core/lib/i18n/locales/ar.json +18 -0
- package/lighthouse-core/lib/i18n/locales/bg.json +18 -0
- package/lighthouse-core/lib/i18n/locales/ca.json +18 -0
- package/lighthouse-core/lib/i18n/locales/cs.json +18 -0
- package/lighthouse-core/lib/i18n/locales/da.json +18 -0
- package/lighthouse-core/lib/i18n/locales/de.json +18 -0
- package/lighthouse-core/lib/i18n/locales/el.json +18 -0
- package/lighthouse-core/lib/i18n/locales/en-GB.json +18 -0
- package/lighthouse-core/lib/i18n/locales/en-US.json +18 -18
- package/lighthouse-core/lib/i18n/locales/en-XA.json +18 -0
- package/lighthouse-core/lib/i18n/locales/en-XL.json +18 -18
- package/lighthouse-core/lib/i18n/locales/es-419.json +18 -0
- package/lighthouse-core/lib/i18n/locales/es.json +18 -0
- package/lighthouse-core/lib/i18n/locales/fi.json +18 -0
- package/lighthouse-core/lib/i18n/locales/fil.json +18 -0
- package/lighthouse-core/lib/i18n/locales/fr.json +18 -0
- package/lighthouse-core/lib/i18n/locales/he.json +18 -0
- package/lighthouse-core/lib/i18n/locales/hi.json +18 -0
- package/lighthouse-core/lib/i18n/locales/hr.json +18 -0
- package/lighthouse-core/lib/i18n/locales/hu.json +18 -0
- package/lighthouse-core/lib/i18n/locales/id.json +18 -0
- package/lighthouse-core/lib/i18n/locales/it.json +18 -0
- package/lighthouse-core/lib/i18n/locales/ja.json +18 -0
- package/lighthouse-core/lib/i18n/locales/ko.json +18 -0
- package/lighthouse-core/lib/i18n/locales/lt.json +18 -0
- package/lighthouse-core/lib/i18n/locales/lv.json +18 -0
- package/lighthouse-core/lib/i18n/locales/nl.json +18 -0
- package/lighthouse-core/lib/i18n/locales/no.json +18 -0
- package/lighthouse-core/lib/i18n/locales/pl.json +18 -0
- package/lighthouse-core/lib/i18n/locales/pt-PT.json +18 -0
- package/lighthouse-core/lib/i18n/locales/pt.json +18 -0
- package/lighthouse-core/lib/i18n/locales/ro.json +18 -0
- package/lighthouse-core/lib/i18n/locales/ru.json +18 -0
- package/lighthouse-core/lib/i18n/locales/sk.json +18 -0
- package/lighthouse-core/lib/i18n/locales/sl.json +18 -0
- package/lighthouse-core/lib/i18n/locales/sr-Latn.json +18 -0
- package/lighthouse-core/lib/i18n/locales/sr.json +18 -0
- package/lighthouse-core/lib/i18n/locales/sv.json +18 -0
- package/lighthouse-core/lib/i18n/locales/ta.json +18 -0
- package/lighthouse-core/lib/i18n/locales/te.json +18 -0
- package/lighthouse-core/lib/i18n/locales/th.json +18 -0
- package/lighthouse-core/lib/i18n/locales/tr.json +18 -0
- package/lighthouse-core/lib/i18n/locales/uk.json +18 -0
- package/lighthouse-core/lib/i18n/locales/vi.json +18 -0
- package/lighthouse-core/lib/i18n/locales/zh-HK.json +18 -0
- package/lighthouse-core/lib/i18n/locales/zh-TW.json +18 -0
- package/lighthouse-core/lib/i18n/locales/zh.json +18 -0
- package/lighthouse-core/lib/large-javascript-libraries/bundlephobia-database.json +62 -0
- package/lighthouse-core/lib/large-javascript-libraries/library-suggestions.js +20 -0
- package/lighthouse-core/lib/network-request.js +1 -3
- package/lighthouse-core/lib/traces/pwmetrics-events.js +2 -2
- package/lighthouse-core/lib/url-shim.js +1 -7
- package/lighthouse-core/runner.js +1 -4
- package/lighthouse-core/util-commonjs.js +2 -2
- package/now.json +1 -1
- package/package.json +13 -20
- package/report/README.md +2 -2
- package/report/assets/standalone-template.html +1 -0
- package/report/assets/styles.css +27 -23
- package/report/assets/templates.html +40 -37
- package/report/clients/bundle.js +0 -1
- package/report/{package.json → clients/package.json} +0 -0
- package/report/clients/psi.js +3 -13
- package/report/clients/standalone.js +1 -1
- package/report/{types/buffer.d.ts → clients/treemap.js} +13 -11
- package/report/renderer/category-renderer.js +2 -2
- package/report/renderer/components.js +48 -46
- package/report/renderer/crc-details-renderer.js +16 -16
- package/report/renderer/details-renderer.js +1 -1
- package/report/renderer/dom.js +0 -17
- package/report/renderer/element-screenshot-renderer.js +9 -9
- package/{flow-report/test/setup/global-setup.ts → report/renderer/file-namer.js} +2 -5
- package/report/renderer/logger.js +4 -4
- package/report/renderer/package.json +4 -0
- package/report/renderer/report-renderer.js +11 -4
- package/report/renderer/report-ui-features.js +656 -43
- package/report/renderer/text-encoding.js +1 -1
- package/report/renderer/util.js +2 -2
- package/report/{generator/report-assets.js → report-assets.js} +3 -9
- package/report/{generator/report-generator.js → report-generator.js} +9 -37
- package/report/test/clients/package.json +4 -0
- package/report/test/clients/psi-test.js +1 -3
- package/report/test/renderer/category-renderer-test.js +3 -8
- package/report/test/renderer/components-test.js +0 -35
- package/report/test/renderer/crc-details-renderer-test.js +4 -5
- package/report/test/renderer/details-renderer-test.js +1 -3
- package/report/test/renderer/dom-test.js +6 -8
- package/report/test/renderer/i18n-test.js +0 -1
- package/report/test/renderer/package.json +4 -0
- package/report/test/renderer/performance-category-renderer-test.js +27 -30
- package/report/test/renderer/pwa-category-renderer-test.js +1 -2
- package/report/test/renderer/report-renderer-test.js +0 -1
- package/report/test/renderer/report-ui-features-test.js +28 -20
- package/report/test/renderer/snippet-renderer-test.js +0 -1
- package/report/test/renderer/text-encoding-test.js +0 -2
- package/report/test/renderer/util-test.js +0 -1
- package/report/test/{generator/report-generator-test.js → report-generator-test.js} +7 -9
- package/third-party/chromium-synchronization/installability-errors-test.js +27 -28
- package/third-party/snyk/snapshot.json +0 -2
- package/tsconfig.json +5 -24
- package/types/artifacts.d.ts +18 -5
- package/types/{lhr/audit-details.d.ts → audit-details.d.ts} +3 -18
- package/types/audit.d.ts +58 -7
- package/types/{lhr/budget.d.ts → budget.d.ts} +0 -0
- package/types/bundle-phobia-cli/index.d.ts +40 -0
- package/types/config.d.ts +8 -3
- package/types/externs.d.ts +119 -5
- package/types/gatherer.d.ts +2 -3
- package/types/global-lh.d.ts +14 -16
- package/{report/types/report-result.d.ts → types/html-renderer.d.ts} +17 -8
- package/types/{lhr/i18n.d.ts → i18n.d.ts} +25 -2
- package/types/{lhr/lhr.d.ts → lhr.d.ts} +9 -41
- package/types/smokehouse.d.ts +3 -3
- package/types/{lhr/treemap.d.ts → treemap.d.ts} +3 -3
- package/flow-report/assets/standalone-flow-template.html +0 -38
- package/flow-report/assets/styles.css +0 -310
- package/flow-report/jest.config.js +0 -24
- package/flow-report/src/app.tsx +0 -40
- package/flow-report/src/common.tsx +0 -72
- package/flow-report/src/icons.tsx +0 -96
- package/flow-report/src/sidebar/flow.tsx +0 -72
- package/flow-report/src/sidebar/sidebar.tsx +0 -89
- package/flow-report/src/summary/summary.tsx +0 -178
- package/flow-report/src/util.ts +0 -160
- package/flow-report/src/wrappers/gauge.tsx +0 -40
- package/flow-report/src/wrappers/report-renderer.tsx +0 -46
- package/flow-report/src/wrappers/report.tsx +0 -71
- package/flow-report/standalone-flow.tsx +0 -23
- package/flow-report/test/app-test.tsx +0 -48
- package/flow-report/test/common-test.tsx +0 -131
- package/flow-report/test/setup/env-setup.ts +0 -25
- package/flow-report/test/sidebar/flow-test.tsx +0 -71
- package/flow-report/test/sidebar/sidebar-test.tsx +0 -53
- package/flow-report/test/summary/summary-test.tsx +0 -132
- package/flow-report/test/util-test.tsx +0 -109
- package/flow-report/tsconfig.json +0 -39
- package/flow-report/types/flow-report.d.ts +0 -27
- package/lighthouse-core/audits/lcp-lazy-loaded.js +0 -83
- package/report/.eslintrc.cjs +0 -33
- package/report/generator/README.md +0 -9
- package/report/generator/flow-report-assets.js +0 -20
- package/report/generator/package.json +0 -4
- package/report/generator/tsconfig.json +0 -28
- package/report/renderer/drop-down-menu.js +0 -215
- package/report/renderer/features-util.js +0 -26
- package/report/renderer/open-tab.js +0 -137
- package/report/renderer/topbar-features.js +0 -309
- package/report/test/generator/file-namer-test.js +0 -28
- package/report/test/generator/package.json +0 -4
- package/report/test-assets/faux-psi-template.html +0 -126
- package/report/test-assets/faux-psi.js +0 -92
- package/report/tsconfig.json +0 -39
- package/report/types/augment-dom.d.ts +0 -24
- package/report/types/html-renderer.d.ts +0 -28
- package/types/lhr/audit-result.d.ts +0 -59
- package/types/lhr/flow.d.ts +0 -16
- package/types/lhr/settings.d.ts +0 -105
- package/types/lhr/tsconfig.json +0 -24
- package/types/query-selector.d.ts +0 -36
|
@@ -1,215 +0,0 @@
|
|
|
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
|
-
/* eslint-env browser */
|
|
9
|
-
|
|
10
|
-
/** @typedef {import('./dom.js').DOM} DOM */
|
|
11
|
-
|
|
12
|
-
export class DropDownMenu {
|
|
13
|
-
/**
|
|
14
|
-
* @param {DOM} dom
|
|
15
|
-
*/
|
|
16
|
-
constructor(dom) {
|
|
17
|
-
/** @type {DOM} */
|
|
18
|
-
this._dom = dom;
|
|
19
|
-
/** @type {HTMLElement} */
|
|
20
|
-
this._toggleEl; // eslint-disable-line no-unused-expressions
|
|
21
|
-
/** @type {HTMLElement} */
|
|
22
|
-
this._menuEl; // eslint-disable-line no-unused-expressions
|
|
23
|
-
|
|
24
|
-
this.onDocumentKeyDown = this.onDocumentKeyDown.bind(this);
|
|
25
|
-
this.onToggleClick = this.onToggleClick.bind(this);
|
|
26
|
-
this.onToggleKeydown = this.onToggleKeydown.bind(this);
|
|
27
|
-
this.onMenuFocusOut = this.onMenuFocusOut.bind(this);
|
|
28
|
-
this.onMenuKeydown = this.onMenuKeydown.bind(this);
|
|
29
|
-
|
|
30
|
-
this._getNextMenuItem = this._getNextMenuItem.bind(this);
|
|
31
|
-
this._getNextSelectableNode = this._getNextSelectableNode.bind(this);
|
|
32
|
-
this._getPreviousMenuItem = this._getPreviousMenuItem.bind(this);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* @param {function(MouseEvent): any} menuClickHandler
|
|
37
|
-
*/
|
|
38
|
-
setup(menuClickHandler) {
|
|
39
|
-
this._toggleEl = this._dom.find('button.lh-tools__button', this._dom.document());
|
|
40
|
-
this._toggleEl.addEventListener('click', this.onToggleClick);
|
|
41
|
-
this._toggleEl.addEventListener('keydown', this.onToggleKeydown);
|
|
42
|
-
|
|
43
|
-
this._menuEl = this._dom.find('div.lh-tools__dropdown', this._dom.document());
|
|
44
|
-
this._menuEl.addEventListener('keydown', this.onMenuKeydown);
|
|
45
|
-
this._menuEl.addEventListener('click', menuClickHandler);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
close() {
|
|
49
|
-
this._toggleEl.classList.remove('lh-active');
|
|
50
|
-
this._toggleEl.setAttribute('aria-expanded', 'false');
|
|
51
|
-
if (this._menuEl.contains(this._dom.document().activeElement)) {
|
|
52
|
-
// Refocus on the tools button if the drop down last had focus
|
|
53
|
-
this._toggleEl.focus();
|
|
54
|
-
}
|
|
55
|
-
this._menuEl.removeEventListener('focusout', this.onMenuFocusOut);
|
|
56
|
-
this._dom.document().removeEventListener('keydown', this.onDocumentKeyDown);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* @param {HTMLElement} firstFocusElement
|
|
61
|
-
*/
|
|
62
|
-
open(firstFocusElement) {
|
|
63
|
-
if (this._toggleEl.classList.contains('lh-active')) {
|
|
64
|
-
// If the drop down is already open focus on the element
|
|
65
|
-
firstFocusElement.focus();
|
|
66
|
-
} else {
|
|
67
|
-
// Wait for drop down transition to complete so options are focusable.
|
|
68
|
-
this._menuEl.addEventListener('transitionend', () => {
|
|
69
|
-
firstFocusElement.focus();
|
|
70
|
-
}, {once: true});
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
this._toggleEl.classList.add('lh-active');
|
|
74
|
-
this._toggleEl.setAttribute('aria-expanded', 'true');
|
|
75
|
-
this._menuEl.addEventListener('focusout', this.onMenuFocusOut);
|
|
76
|
-
this._dom.document().addEventListener('keydown', this.onDocumentKeyDown);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Click handler for tools button.
|
|
81
|
-
* @param {Event} e
|
|
82
|
-
*/
|
|
83
|
-
onToggleClick(e) {
|
|
84
|
-
e.preventDefault();
|
|
85
|
-
e.stopImmediatePropagation();
|
|
86
|
-
|
|
87
|
-
if (this._toggleEl.classList.contains('lh-active')) {
|
|
88
|
-
this.close();
|
|
89
|
-
} else {
|
|
90
|
-
this.open(this._getNextMenuItem());
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Handler for tool button.
|
|
96
|
-
* @param {KeyboardEvent} e
|
|
97
|
-
*/
|
|
98
|
-
onToggleKeydown(e) {
|
|
99
|
-
switch (e.code) {
|
|
100
|
-
case 'ArrowUp':
|
|
101
|
-
e.preventDefault();
|
|
102
|
-
this.open(this._getPreviousMenuItem());
|
|
103
|
-
break;
|
|
104
|
-
case 'ArrowDown':
|
|
105
|
-
case 'Enter':
|
|
106
|
-
case ' ':
|
|
107
|
-
e.preventDefault();
|
|
108
|
-
this.open(this._getNextMenuItem());
|
|
109
|
-
break;
|
|
110
|
-
default:
|
|
111
|
-
// no op
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Handler for tool DropDown.
|
|
117
|
-
* @param {KeyboardEvent} e
|
|
118
|
-
*/
|
|
119
|
-
onMenuKeydown(e) {
|
|
120
|
-
const el = /** @type {?HTMLElement} */ (e.target);
|
|
121
|
-
|
|
122
|
-
switch (e.code) {
|
|
123
|
-
case 'ArrowUp':
|
|
124
|
-
e.preventDefault();
|
|
125
|
-
this._getPreviousMenuItem(el).focus();
|
|
126
|
-
break;
|
|
127
|
-
case 'ArrowDown':
|
|
128
|
-
e.preventDefault();
|
|
129
|
-
this._getNextMenuItem(el).focus();
|
|
130
|
-
break;
|
|
131
|
-
case 'Home':
|
|
132
|
-
e.preventDefault();
|
|
133
|
-
this._getNextMenuItem().focus();
|
|
134
|
-
break;
|
|
135
|
-
case 'End':
|
|
136
|
-
e.preventDefault();
|
|
137
|
-
this._getPreviousMenuItem().focus();
|
|
138
|
-
break;
|
|
139
|
-
default:
|
|
140
|
-
// no op
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Keydown handler for the document.
|
|
146
|
-
* @param {KeyboardEvent} e
|
|
147
|
-
*/
|
|
148
|
-
onDocumentKeyDown(e) {
|
|
149
|
-
if (e.keyCode === 27) { // ESC
|
|
150
|
-
this.close();
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Focus out handler for the drop down menu.
|
|
156
|
-
* @param {FocusEvent} e
|
|
157
|
-
*/
|
|
158
|
-
onMenuFocusOut(e) {
|
|
159
|
-
const focusedEl = /** @type {?HTMLElement} */ (e.relatedTarget);
|
|
160
|
-
|
|
161
|
-
if (!this._menuEl.contains(focusedEl)) {
|
|
162
|
-
this.close();
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* @param {Array<Node>} allNodes
|
|
168
|
-
* @param {?HTMLElement=} startNode
|
|
169
|
-
* @return {HTMLElement}
|
|
170
|
-
*/
|
|
171
|
-
_getNextSelectableNode(allNodes, startNode) {
|
|
172
|
-
const nodes = allNodes.filter(/** @return {node is HTMLElement} */ (node) => {
|
|
173
|
-
if (!(node instanceof HTMLElement)) {
|
|
174
|
-
return false;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// 'Save as Gist' option may be disabled.
|
|
178
|
-
if (node.hasAttribute('disabled')) {
|
|
179
|
-
return false;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// 'Save as Gist' option may have display none.
|
|
183
|
-
if (window.getComputedStyle(node).display === 'none') {
|
|
184
|
-
return false;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
return true;
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
let nextIndex = startNode ? (nodes.indexOf(startNode) + 1) : 0;
|
|
191
|
-
if (nextIndex >= nodes.length) {
|
|
192
|
-
nextIndex = 0;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
return nodes[nextIndex];
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* @param {?HTMLElement=} startEl
|
|
200
|
-
* @return {HTMLElement}
|
|
201
|
-
*/
|
|
202
|
-
_getNextMenuItem(startEl) {
|
|
203
|
-
const nodes = Array.from(this._menuEl.childNodes);
|
|
204
|
-
return this._getNextSelectableNode(nodes, startEl);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* @param {?HTMLElement=} startEl
|
|
209
|
-
* @return {HTMLElement}
|
|
210
|
-
*/
|
|
211
|
-
_getPreviousMenuItem(startEl) {
|
|
212
|
-
const nodes = Array.from(this._menuEl.childNodes).reverse();
|
|
213
|
-
return this._getNextSelectableNode(nodes, startEl);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
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
|
-
/* eslint-env browser */
|
|
9
|
-
|
|
10
|
-
/** @typedef {import('./dom.js').DOM} DOM */
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* @param {DOM} dom
|
|
14
|
-
* @param {boolean} [force]
|
|
15
|
-
*/
|
|
16
|
-
export function toggleDarkTheme(dom, force) {
|
|
17
|
-
const el = dom.find('.lh-vars', dom.document());
|
|
18
|
-
// This seems unnecessary, but in DevTools, passing "undefined" as the second
|
|
19
|
-
// parameter acts like passing "false".
|
|
20
|
-
// https://github.com/ChromeDevTools/devtools-frontend/blob/dd6a6d4153647c2a4203c327c595692c5e0a4256/front_end/dom_extension/DOMExtension.js#L809-L819
|
|
21
|
-
if (typeof force === 'undefined') {
|
|
22
|
-
el.classList.toggle('lh-dark');
|
|
23
|
-
} else {
|
|
24
|
-
el.classList.toggle('lh-dark', force);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2021 The Lighthouse Authors. All Rights Reserved.
|
|
4
|
-
*
|
|
5
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
-
* you may not use this file except in compliance with the License.
|
|
7
|
-
* You may obtain a copy of the License at
|
|
8
|
-
*
|
|
9
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
-
*
|
|
11
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
-
* distributed under the License is distributed on an "AS-IS" BASIS,
|
|
13
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
-
* See the License for the specific language governing permissions and
|
|
15
|
-
* limitations under the License.
|
|
16
|
-
*/
|
|
17
|
-
'use strict';
|
|
18
|
-
|
|
19
|
-
import {TextEncoding} from './text-encoding.js';
|
|
20
|
-
|
|
21
|
-
/* eslint-env browser */
|
|
22
|
-
|
|
23
|
-
function getAppsOrigin() {
|
|
24
|
-
const isVercel = window.location.host.endsWith('.vercel.app');
|
|
25
|
-
const isDev = new URLSearchParams(window.location.search).has('dev');
|
|
26
|
-
|
|
27
|
-
if (isVercel) return `https://${window.location.host}/gh-pages`;
|
|
28
|
-
if (isDev) return 'http://localhost:8000';
|
|
29
|
-
return 'https://googlechrome.github.io/lighthouse';
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* The popup's window.name is keyed by version+url+fetchTime, so we reuse/select tabs correctly.
|
|
34
|
-
* @param {LH.Result} json
|
|
35
|
-
* @protected
|
|
36
|
-
*/
|
|
37
|
-
function computeWindowNameSuffix(json) {
|
|
38
|
-
// @ts-expect-error - If this is a v2 LHR, use old `generatedTime`.
|
|
39
|
-
const fallbackFetchTime = /** @type {string} */ (json.generatedTime);
|
|
40
|
-
const fetchTime = json.fetchTime || fallbackFetchTime;
|
|
41
|
-
return `${json.lighthouseVersion}-${json.requestedUrl}-${fetchTime}`;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Opens a new tab to an external page and sends data using postMessage.
|
|
46
|
-
* @param {{lhr: LH.Result} | LH.Treemap.Options} data
|
|
47
|
-
* @param {string} url
|
|
48
|
-
* @param {string} windowName
|
|
49
|
-
* @protected
|
|
50
|
-
*/
|
|
51
|
-
function openTabAndSendData(data, url, windowName) {
|
|
52
|
-
const origin = new URL(url).origin;
|
|
53
|
-
// Chrome doesn't allow us to immediately postMessage to a popup right
|
|
54
|
-
// after it's created. Normally, we could also listen for the popup window's
|
|
55
|
-
// load event, however it is cross-domain and won't fire. Instead, listen
|
|
56
|
-
// for a message from the target app saying "I'm open".
|
|
57
|
-
window.addEventListener('message', function msgHandler(messageEvent) {
|
|
58
|
-
if (messageEvent.origin !== origin) {
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
if (popup && messageEvent.data.opened) {
|
|
62
|
-
popup.postMessage(data, origin);
|
|
63
|
-
window.removeEventListener('message', msgHandler);
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
const popup = window.open(url, windowName);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Opens a new tab to an external page and sends data via base64 encoded url params.
|
|
72
|
-
* @param {{lhr: LH.Result} | LH.Treemap.Options} data
|
|
73
|
-
* @param {string} url_
|
|
74
|
-
* @param {string} windowName
|
|
75
|
-
* @protected
|
|
76
|
-
*/
|
|
77
|
-
async function openTabWithUrlData(data, url_, windowName) {
|
|
78
|
-
const url = new URL(url_);
|
|
79
|
-
const gzip = Boolean(window.CompressionStream);
|
|
80
|
-
url.hash = await TextEncoding.toBase64(JSON.stringify(data), {
|
|
81
|
-
gzip,
|
|
82
|
-
});
|
|
83
|
-
if (gzip) url.searchParams.set('gzip', '1');
|
|
84
|
-
window.open(url.toString(), windowName);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Opens a new tab to the online viewer and sends the local page's JSON results
|
|
89
|
-
* to the online viewer using URL.fragment
|
|
90
|
-
* @param {LH.Result} lhr
|
|
91
|
-
* @protected
|
|
92
|
-
*/
|
|
93
|
-
export async function openViewer(lhr) {
|
|
94
|
-
const windowName = 'viewer-' + computeWindowNameSuffix(lhr);
|
|
95
|
-
const url = getAppsOrigin() + '/viewer/';
|
|
96
|
-
await openTabWithUrlData({lhr}, url, windowName);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Same as openViewer, but uses postMessage.
|
|
101
|
-
* @param {LH.Result} lhr
|
|
102
|
-
* @protected
|
|
103
|
-
*/
|
|
104
|
-
export async function openViewerAndSendData(lhr) {
|
|
105
|
-
const windowName = 'viewer-' + computeWindowNameSuffix(lhr);
|
|
106
|
-
const url = getAppsOrigin() + '/viewer/';
|
|
107
|
-
openTabAndSendData({lhr}, url, windowName);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Opens a new tab to the treemap app and sends the JSON results using URL.fragment
|
|
112
|
-
* @param {LH.Result} json
|
|
113
|
-
*/
|
|
114
|
-
export function openTreemap(json) {
|
|
115
|
-
const treemapData = json.audits['script-treemap-data'].details;
|
|
116
|
-
if (!treemapData) {
|
|
117
|
-
throw new Error('no script treemap data found');
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/** @type {LH.Treemap.Options} */
|
|
121
|
-
const treemapOptions = {
|
|
122
|
-
lhr: {
|
|
123
|
-
requestedUrl: json.requestedUrl,
|
|
124
|
-
finalUrl: json.finalUrl,
|
|
125
|
-
audits: {
|
|
126
|
-
'script-treemap-data': json.audits['script-treemap-data'],
|
|
127
|
-
},
|
|
128
|
-
configSettings: {
|
|
129
|
-
locale: json.configSettings.locale,
|
|
130
|
-
},
|
|
131
|
-
},
|
|
132
|
-
};
|
|
133
|
-
const url = getAppsOrigin() + '/treemap/';
|
|
134
|
-
const windowName = 'treemap-' + computeWindowNameSuffix(json);
|
|
135
|
-
|
|
136
|
-
openTabWithUrlData(treemapOptions, url, windowName);
|
|
137
|
-
}
|
|
@@ -1,309 +0,0 @@
|
|
|
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
|
-
/* eslint-env browser */
|
|
9
|
-
|
|
10
|
-
/** @typedef {import('./dom.js').DOM} DOM */
|
|
11
|
-
/** @typedef {import('./report-ui-features').ReportUIFeatures} ReportUIFeatures */
|
|
12
|
-
|
|
13
|
-
import {DropDownMenu} from './drop-down-menu.js';
|
|
14
|
-
import {toggleDarkTheme} from './features-util.js';
|
|
15
|
-
import {openViewer, openViewerAndSendData} from './open-tab.js';
|
|
16
|
-
|
|
17
|
-
export class TopbarFeatures {
|
|
18
|
-
/**
|
|
19
|
-
* @param {ReportUIFeatures} reportUIFeatures
|
|
20
|
-
* @param {DOM} dom
|
|
21
|
-
*/
|
|
22
|
-
constructor(reportUIFeatures, dom) {
|
|
23
|
-
/** @type {LH.Result} */
|
|
24
|
-
this.lhr; // eslint-disable-line no-unused-expressions
|
|
25
|
-
this._reportUIFeatures = reportUIFeatures;
|
|
26
|
-
this._dom = dom;
|
|
27
|
-
/** @type {Document} */
|
|
28
|
-
this._document = this._dom.document();
|
|
29
|
-
this._dropDownMenu = new DropDownMenu(this._dom);
|
|
30
|
-
this._copyAttempt = false;
|
|
31
|
-
/** @type {HTMLElement} */
|
|
32
|
-
this.topbarEl; // eslint-disable-line no-unused-expressions
|
|
33
|
-
/** @type {HTMLElement} */
|
|
34
|
-
this.scoreScaleEl; // eslint-disable-line no-unused-expressions
|
|
35
|
-
/** @type {HTMLElement} */
|
|
36
|
-
this.stickyHeaderEl; // eslint-disable-line no-unused-expressions
|
|
37
|
-
/** @type {HTMLElement} */
|
|
38
|
-
this.highlightEl; // eslint-disable-line no-unused-expressions
|
|
39
|
-
this.onDropDownMenuClick = this.onDropDownMenuClick.bind(this);
|
|
40
|
-
this.onKeyUp = this.onKeyUp.bind(this);
|
|
41
|
-
this.onCopy = this.onCopy.bind(this);
|
|
42
|
-
this.collapseAllDetails = this.collapseAllDetails.bind(this);
|
|
43
|
-
this._updateStickyHeaderOnScroll = this._updateStickyHeaderOnScroll.bind(this);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* @param {LH.Result} lhr
|
|
48
|
-
*/
|
|
49
|
-
enable(lhr) {
|
|
50
|
-
this.lhr = lhr;
|
|
51
|
-
this._document.addEventListener('keyup', this.onKeyUp);
|
|
52
|
-
this._document.addEventListener('copy', this.onCopy);
|
|
53
|
-
this._dropDownMenu.setup(this.onDropDownMenuClick);
|
|
54
|
-
this._setUpCollapseDetailsAfterPrinting();
|
|
55
|
-
|
|
56
|
-
const topbarLogo = this._dom.find('.lh-topbar__logo', this._document);
|
|
57
|
-
topbarLogo.addEventListener('click', () => toggleDarkTheme(this._dom));
|
|
58
|
-
|
|
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
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Handler for tool button.
|
|
82
|
-
* @param {Event} e
|
|
83
|
-
*/
|
|
84
|
-
onDropDownMenuClick(e) {
|
|
85
|
-
e.preventDefault();
|
|
86
|
-
|
|
87
|
-
const el = /** @type {?Element} */ (e.target);
|
|
88
|
-
|
|
89
|
-
if (!el || !el.hasAttribute('data-action')) {
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
switch (el.getAttribute('data-action')) {
|
|
94
|
-
case 'copy':
|
|
95
|
-
this.onCopyButtonClick();
|
|
96
|
-
break;
|
|
97
|
-
case 'print-summary':
|
|
98
|
-
this.collapseAllDetails();
|
|
99
|
-
this._print();
|
|
100
|
-
break;
|
|
101
|
-
case 'print-expanded':
|
|
102
|
-
this.expandAllDetails();
|
|
103
|
-
this._print();
|
|
104
|
-
break;
|
|
105
|
-
case 'save-json': {
|
|
106
|
-
const jsonStr = JSON.stringify(this.lhr, null, 2);
|
|
107
|
-
this._reportUIFeatures._saveFile(new Blob([jsonStr], {type: 'application/json'}));
|
|
108
|
-
break;
|
|
109
|
-
}
|
|
110
|
-
case 'save-html': {
|
|
111
|
-
const htmlStr = this._reportUIFeatures.getReportHtml();
|
|
112
|
-
try {
|
|
113
|
-
this._reportUIFeatures._saveFile(new Blob([htmlStr], {type: 'text/html'}));
|
|
114
|
-
} catch (e) {
|
|
115
|
-
this._dom.fireEventOn('lh-log', this._document, {
|
|
116
|
-
cmd: 'error', msg: 'Could not export as HTML. ' + e.message,
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
break;
|
|
120
|
-
}
|
|
121
|
-
case 'open-viewer': {
|
|
122
|
-
// DevTools cannot send data with postMessage, and we only want to use the URL fragment
|
|
123
|
-
// approach for viewer when needed, so check the environment and choose accordingly.
|
|
124
|
-
if (this._dom.isDevTools()) {
|
|
125
|
-
openViewer(this.lhr);
|
|
126
|
-
} else {
|
|
127
|
-
openViewerAndSendData(this.lhr);
|
|
128
|
-
}
|
|
129
|
-
break;
|
|
130
|
-
}
|
|
131
|
-
case 'save-gist': {
|
|
132
|
-
this._reportUIFeatures.saveAsGist();
|
|
133
|
-
break;
|
|
134
|
-
}
|
|
135
|
-
case 'toggle-dark': {
|
|
136
|
-
toggleDarkTheme(this._dom);
|
|
137
|
-
break;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
this._dropDownMenu.close();
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Handle copy events.
|
|
146
|
-
* @param {ClipboardEvent} e
|
|
147
|
-
*/
|
|
148
|
-
onCopy(e) {
|
|
149
|
-
// Only handle copy button presses (e.g. ignore the user copying page text).
|
|
150
|
-
if (this._copyAttempt && e.clipboardData) {
|
|
151
|
-
// We want to write our own data to the clipboard, not the user's text selection.
|
|
152
|
-
e.preventDefault();
|
|
153
|
-
e.clipboardData.setData('text/plain', JSON.stringify(this.lhr, null, 2));
|
|
154
|
-
|
|
155
|
-
this._dom.fireEventOn('lh-log', this._document, {
|
|
156
|
-
cmd: 'log', msg: 'Report JSON copied to clipboard',
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
this._copyAttempt = false;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Copies the report JSON to the clipboard (if supported by the browser).
|
|
165
|
-
*/
|
|
166
|
-
onCopyButtonClick() {
|
|
167
|
-
this._dom.fireEventOn('lh-analytics', this._document, {
|
|
168
|
-
cmd: 'send',
|
|
169
|
-
fields: {hitType: 'event', eventCategory: 'report', eventAction: 'copy'},
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
try {
|
|
173
|
-
if (this._document.queryCommandSupported('copy')) {
|
|
174
|
-
this._copyAttempt = true;
|
|
175
|
-
|
|
176
|
-
// Note: In Safari 10.0.1, execCommand('copy') returns true if there's
|
|
177
|
-
// a valid text selection on the page. See http://caniuse.com/#feat=clipboard.
|
|
178
|
-
if (!this._document.execCommand('copy')) {
|
|
179
|
-
this._copyAttempt = false; // Prevent event handler from seeing this as a copy attempt.
|
|
180
|
-
|
|
181
|
-
this._dom.fireEventOn('lh-log', this._document, {
|
|
182
|
-
cmd: 'warn', msg: 'Your browser does not support copy to clipboard.',
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
} catch (e) {
|
|
187
|
-
this._copyAttempt = false;
|
|
188
|
-
this._dom.fireEventOn('lh-log', this._document, {cmd: 'log', msg: e.message});
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Keyup handler for the document.
|
|
194
|
-
* @param {KeyboardEvent} e
|
|
195
|
-
*/
|
|
196
|
-
onKeyUp(e) {
|
|
197
|
-
// Ctrl+P - Expands audit details when user prints via keyboard shortcut.
|
|
198
|
-
if ((e.ctrlKey || e.metaKey) && e.keyCode === 80) {
|
|
199
|
-
this._dropDownMenu.close();
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Expands all audit `<details>`.
|
|
205
|
-
* Ideally, a print stylesheet could take care of this, but CSS has no way to
|
|
206
|
-
* open a `<details>` element.
|
|
207
|
-
*/
|
|
208
|
-
expandAllDetails() {
|
|
209
|
-
const details = this._dom.findAll('.lh-categories details', this._document);
|
|
210
|
-
details.map(detail => detail.open = true);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Collapses all audit `<details>`.
|
|
215
|
-
* open a `<details>` element.
|
|
216
|
-
*/
|
|
217
|
-
collapseAllDetails() {
|
|
218
|
-
const details = this._dom.findAll('.lh-categories details', this._document);
|
|
219
|
-
details.map(detail => detail.open = false);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
_print() {
|
|
223
|
-
self.print();
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Resets the state of page before capturing the page for export.
|
|
228
|
-
* When the user opens the exported HTML page, certain UI elements should
|
|
229
|
-
* be in their closed state (not opened) and the templates should be unstamped.
|
|
230
|
-
*/
|
|
231
|
-
resetUIState() {
|
|
232
|
-
this._dropDownMenu.close();
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Finds the first scrollable ancestor of `element`. Falls back to the document.
|
|
237
|
-
* @param {Element} element
|
|
238
|
-
* @return {Node}
|
|
239
|
-
*/
|
|
240
|
-
_getScrollParent(element) {
|
|
241
|
-
const {overflowY} = window.getComputedStyle(element);
|
|
242
|
-
const isScrollable = overflowY !== 'visible' && overflowY !== 'hidden';
|
|
243
|
-
|
|
244
|
-
if (isScrollable) {
|
|
245
|
-
return element;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
if (element.parentElement) {
|
|
249
|
-
return this._getScrollParent(element.parentElement);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
return document;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Sets up listeners to collapse audit `<details>` when the user closes the
|
|
257
|
-
* print dialog, all `<details>` are collapsed.
|
|
258
|
-
*/
|
|
259
|
-
_setUpCollapseDetailsAfterPrinting() {
|
|
260
|
-
// FF and IE implement these old events.
|
|
261
|
-
if ('onbeforeprint' in self) {
|
|
262
|
-
self.addEventListener('afterprint', this.collapseAllDetails);
|
|
263
|
-
} else {
|
|
264
|
-
// Note: FF implements both window.onbeforeprint and media listeners. However,
|
|
265
|
-
// it doesn't matchMedia doesn't fire when matching 'print'.
|
|
266
|
-
self.matchMedia('print').addListener(mql => {
|
|
267
|
-
if (mql.matches) {
|
|
268
|
-
this.expandAllDetails();
|
|
269
|
-
} else {
|
|
270
|
-
this.collapseAllDetails();
|
|
271
|
-
}
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
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);
|
|
280
|
-
|
|
281
|
-
// Highlighter will be absolutely positioned at first gauge, then transformed on scroll.
|
|
282
|
-
this.highlightEl = this._dom.createChildOf(this.stickyHeaderEl, 'div', 'lh-highlighter');
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
_updateStickyHeaderOnScroll() {
|
|
286
|
-
// Show sticky header when the score scale begins to go underneath the topbar.
|
|
287
|
-
const topbarBottom = this.topbarEl.getBoundingClientRect().bottom;
|
|
288
|
-
const scoreScaleTop = this.scoreScaleEl.getBoundingClientRect().top;
|
|
289
|
-
const showStickyHeader = topbarBottom >= scoreScaleTop;
|
|
290
|
-
|
|
291
|
-
// Highlight mini gauge when section is in view.
|
|
292
|
-
// In view = the last category that starts above the middle of the window.
|
|
293
|
-
const categoryEls = Array.from(this._document.querySelectorAll('.lh-category'));
|
|
294
|
-
const categoriesAboveTheMiddle =
|
|
295
|
-
categoryEls.filter(el => el.getBoundingClientRect().top - window.innerHeight / 2 < 0);
|
|
296
|
-
const highlightIndex =
|
|
297
|
-
categoriesAboveTheMiddle.length > 0 ? categoriesAboveTheMiddle.length - 1 : 0;
|
|
298
|
-
|
|
299
|
-
// Category order matches gauge order in sticky header.
|
|
300
|
-
const gaugeWrapperEls = this.stickyHeaderEl.querySelectorAll('.lh-gauge__wrapper');
|
|
301
|
-
const gaugeToHighlight = gaugeWrapperEls[highlightIndex];
|
|
302
|
-
const origin = gaugeWrapperEls[0].getBoundingClientRect().left;
|
|
303
|
-
const offset = gaugeToHighlight.getBoundingClientRect().left - origin;
|
|
304
|
-
|
|
305
|
-
// Mutate at end to avoid layout thrashing.
|
|
306
|
-
this.highlightEl.style.transform = `translate(${offset}px)`;
|
|
307
|
-
this.stickyHeaderEl.classList.toggle('lh-sticky-header--visible', showStickyHeader);
|
|
308
|
-
}
|
|
309
|
-
}
|