-
Notifications
You must be signed in to change notification settings - Fork 9.6k
devtools: enable sticky header, top bar, and report ui features #9023
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 24 commits
41aa187
000f841
c0bf21d
1e040cc
a6e722e
7129ae1
ca0afd1
4a6a796
fe61b86
720dbb4
0153c0e
009c7d2
0ee54c8
cc763f8
7ace084
6650b66
8063f18
63c9638
739b20a
4b86fb4
bdbf379
181a5ed
2031b5e
b2016ea
f16f78f
e3a4ba0
19215e9
6059968
9dd16c1
82bb8e6
aec1ac3
0c1b09e
656e941
8ded84e
6dabf64
df79fcb
427baf5
d383120
b6879b1
26f2864
018a64b
2f3da14
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -91,17 +91,6 @@ class ReportRenderer { | |
| return el; | ||
| } | ||
|
|
||
| /** | ||
| * @return {Element} | ||
| */ | ||
| _renderReportShortHeader() { | ||
| const shortHeaderContainer = this._dom.createElement('div', 'lh-header-container'); | ||
| const wrapper = this._dom.cloneTemplate('#tmpl-lh-scores-wrapper', this._templateContext); | ||
| shortHeaderContainer.appendChild(wrapper); | ||
| return shortHeaderContainer; | ||
| } | ||
|
|
||
|
|
||
| /** | ||
| * @param {LH.ReportResult} report | ||
| * @return {DocumentFragment} | ||
|
|
@@ -195,16 +184,8 @@ class ReportRenderer { | |
| * @return {DocumentFragment} | ||
| */ | ||
| _renderReport(report) { | ||
| let header; | ||
| const headerContainer = this._dom.createElement('div'); | ||
| if (this._dom.isDevTools()) { | ||
| headerContainer.classList.add('lh-header-plain'); | ||
| header = this._renderReportShortHeader(); | ||
| } else { | ||
| headerContainer.classList.add('lh-header-sticky'); | ||
| header = this._renderReportHeader(); | ||
| } | ||
| headerContainer.appendChild(header); | ||
| headerContainer.appendChild(this._renderReportHeader()); | ||
|
|
||
| const container = this._dom.createElement('div', 'lh-container'); | ||
| const reportSection = container.appendChild(this._dom.createElement('div', 'lh-report')); | ||
|
|
@@ -242,38 +223,29 @@ class ReportRenderer { | |
| wrapper.appendChild(renderer.render(category, report.categoryGroups)); | ||
| } | ||
|
|
||
| const reportFragment = this._dom.createFragment(); | ||
| const topbarDocumentFragment = this._renderReportTopbar(report); | ||
| reportFragment.appendChild(topbarDocumentFragment); | ||
|
|
||
| if (scoreHeader) { | ||
| const scoreGauges = | ||
| const makeScoreGauges = () => | ||
|
||
| this._renderScoreGauges(report, categoryRenderer, specificCategoryRenderers); | ||
| scoreHeader.append(...scoreGauges); | ||
|
|
||
| const scoreScale = this._dom.cloneTemplate('#tmpl-lh-scorescale', this._templateContext); | ||
| const scoresContainer = this._dom.find('.lh-scores-container', headerContainer); | ||
| scoreHeader.append(...makeScoreGauges()); | ||
| scoresContainer.appendChild(scoreHeader); | ||
| scoresContainer.appendChild(scoreScale); | ||
| } | ||
|
|
||
| reportSection.appendChild(this._renderReportFooter(report)); | ||
|
|
||
| const reportFragment = this._dom.createFragment(); | ||
|
|
||
| if (!this._dom.isDevTools()) { | ||
| const topbarDocumentFragment = this._renderReportTopbar(report); | ||
| reportFragment.appendChild(topbarDocumentFragment); | ||
| } | ||
|
|
||
| if (scoreHeader && !this._dom.isDevTools()) { | ||
| const stickyHeader = this._dom.createElement('div', 'lh-sticky-header'); | ||
| this._dom.createChildOf(stickyHeader, 'div', 'lh-highlighter'); | ||
|
|
||
| const scoreGauges = | ||
| this._renderScoreGauges(report, categoryRenderer, specificCategoryRenderers); | ||
| stickyHeader.append(...scoreGauges); | ||
|
|
||
| stickyHeader.append(...makeScoreGauges()); | ||
| reportFragment.appendChild(stickyHeader); | ||
| } | ||
|
|
||
| reportFragment.appendChild(headerContainer); | ||
| reportFragment.appendChild(container); | ||
| reportSection.appendChild(this._renderReportFooter(report)); | ||
|
|
||
| return reportFragment; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -46,6 +46,8 @@ class ReportUIFeatures { | |
| this._dom = dom; | ||
| /** @type {Document} */ | ||
| this._document = this._dom.document(); | ||
| /** @type {ParentNode} */ | ||
| this._templateContext = this._dom.document(); | ||
| /** @type {boolean} */ | ||
| this._copyAttempt = false; | ||
| /** @type {HTMLElement} */ | ||
|
|
@@ -78,33 +80,36 @@ class ReportUIFeatures { | |
| * @param {LH.Result} report | ||
| */ | ||
| initFeatures(report) { | ||
| if (this._dom.isDevTools()) return; | ||
|
|
||
| this.json = report; | ||
| this._setupMediaQueryListeners(); | ||
| this._setupExportButton(); | ||
| this._setupThirdPartyFilter(); | ||
| this._setUpCollapseDetailsAfterPrinting(); | ||
| this._resetUIState(); | ||
| this._document.addEventListener('keyup', this.onKeyUp); | ||
| this._document.addEventListener('copy', this.onCopy); | ||
|
|
||
| // Some features in the top right drop down menu don't work in the DevTools | ||
| // client. They could with some tweaks, but currently they don't. For example: | ||
| // Saving as HTML/JSON - does not bring up a file dialog, as one would expect in DevTools. | ||
|
||
| // also, it saves the AuditsPanel HTML, which is funky. | ||
| if (this._dom.isDevTools()) { | ||
|
||
| this._dom.find('.lh-export__button', this._document).remove(); | ||
| } else { | ||
| this._setupMediaQueryListeners(); | ||
| this._setupExportButton(); | ||
| this._setUpCollapseDetailsAfterPrinting(); | ||
| this._resetUIState(); | ||
| this._document.addEventListener('keyup', this.onKeyUp); | ||
| this._document.addEventListener('copy', this.onCopy); | ||
| } | ||
|
|
||
| const topbarLogo = this._dom.find('.lh-topbar__logo', this._document); | ||
| topbarLogo.addEventListener('click', () => this._toggleDarkTheme()); | ||
| this._setupThirdPartyFilter(); | ||
|
|
||
| let turnOffTheLights = false; | ||
| if (window.matchMedia('(prefers-color-scheme: dark)').matches) { | ||
| turnOffTheLights = true; | ||
| } | ||
|
|
||
| // Fireworks. | ||
| const scoresAll100 = Object.values(report.categories).every(cat => cat.score === 1); | ||
| if (!this._dom.isDevTools() && scoresAll100) { | ||
| const scoresAll100 = Object.values(this.json.categories).every(cat => cat.score === 1); | ||
| if (scoresAll100) { | ||
| turnOffTheLights = true; | ||
| const scoresContainer = this._dom.find('.lh-scores-container', this._document); | ||
| scoresContainer.classList.add('score100'); | ||
| scoresContainer.addEventListener('click', _ => { | ||
| scoresContainer.classList.toggle('fireworks-paused'); | ||
| }); | ||
| this._enableFireworks(); | ||
| } | ||
|
|
||
| if (turnOffTheLights) { | ||
|
|
@@ -114,11 +119,35 @@ class ReportUIFeatures { | |
| // There is only a sticky header when at least 2 categories are present. | ||
| if (Object.keys(this.json.categories).length >= 2) { | ||
| this._setupStickyHeaderElements(); | ||
| this._document.addEventListener('scroll', this._updateStickyHeaderOnScroll); | ||
|
|
||
| /** @type {Document | HTMLElement} */ | ||
| let elToAddScrollListener = this._document; | ||
brendankenny marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if (this._dom.isDevTools()) { | ||
| elToAddScrollListener = this._dom.find('.audits2-results-container', this._document); | ||
| } | ||
| elToAddScrollListener.addEventListener('scroll', this._updateStickyHeaderOnScroll); | ||
|
|
||
| window.addEventListener('resize', this._updateStickyHeaderOnScroll); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Define a custom element for <templates> to be extracted from. For example: | ||
| * this.setTemplateContext(new DOMParser().parseFromString(htmlStr, 'text/html')) | ||
| * @param {ParentNode} context | ||
| */ | ||
| setTemplateContext(context) { | ||
| this._templateContext = context; | ||
| } | ||
|
|
||
| _enableFireworks() { | ||
| const scoresContainer = this._dom.find('.lh-scores-container', this._document); | ||
| scoresContainer.classList.add('score100'); | ||
| scoresContainer.addEventListener('click', _ => { | ||
| scoresContainer.classList.toggle('fireworks-paused'); | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Fires a custom DOM event on target. | ||
| * @param {string} name Name of the event. | ||
|
|
@@ -179,13 +208,13 @@ class ReportUIFeatures { | |
| if (thirdPartyRows.size === urlItems.length || !thirdPartyRows.size) return; | ||
|
|
||
| // create input box | ||
| const filterTemplate = this._dom.cloneTemplate('#tmpl-lh-3p-filter', this._document); | ||
| const filterTemplate = this._dom.cloneTemplate('#tmpl-lh-3p-filter', this._templateContext); | ||
| const filterInput = this._dom.find('input', filterTemplate); | ||
| const id = `lh-3p-filter-label--${index}`; | ||
|
|
||
| filterInput.id = id; | ||
| filterInput.addEventListener('change', e => { | ||
| // Remove rows from the dom and keep track of them to readd on uncheck. | ||
| // Remove rows from the dom and keep track of them to re-add on uncheck. | ||
brendankenny marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // Why removing instead of hiding? To keep nth-child(even) background-colors working. | ||
| if (e.target instanceof HTMLInputElement && !e.target.checked) { | ||
| for (const row of thirdPartyRows.values()) { | ||
|
|
@@ -545,7 +574,15 @@ class ReportUIFeatures { | |
| * @param {boolean} [force] | ||
| */ | ||
| _toggleDarkTheme(force) { | ||
| this._document.body.classList.toggle('dark', force); | ||
| const el = this._dom.find('.lh-vars', this._document); | ||
| // This seems unnecessary, but in DevTools, passing "undefined" as the second | ||
brendankenny marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // parameter acts like passing "false". | ||
| // https://github.com/ChromeDevTools/devtools-frontend/blob/dd6a6d4153647c2a4203c327c595692c5e0a4256/front_end/dom_extension/DOMExtension.js#L809-L819 | ||
| if (typeof force === 'undefined') { | ||
| el.classList.toggle('dark'); | ||
| } else { | ||
| el.classList.toggle('dark', force); | ||
| } | ||
| } | ||
|
|
||
| _updateStickyHeaderOnScroll() { | ||
|
|
@@ -565,11 +602,13 @@ class ReportUIFeatures { | |
| // Category order matches gauge order in sticky header. | ||
| const gaugeWrapperEls = this.stickyHeaderEl.querySelectorAll('.lh-gauge__wrapper'); | ||
| const gaugeToHighlight = gaugeWrapperEls[highlightIndex]; | ||
| const offset = gaugeToHighlight.getBoundingClientRect().left + 'px'; | ||
| // This is normally 0 - but in DevTools, it's not, since the entire report could be docked. | ||
brendankenny marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| const origin = this.stickyHeaderEl.getBoundingClientRect().left; | ||
| const offset = gaugeToHighlight.getBoundingClientRect().left - origin; | ||
|
|
||
| // Mutate at end to avoid layout thrashing. | ||
| this.stickyHeaderEl.classList.toggle('lh-sticky-header--visible', showStickyHeader); | ||
| this.highlightEl.style.left = offset; | ||
| this.highlightEl.style.left = offset + 'px'; | ||
|
||
| } | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -171,25 +171,30 @@ | |
| --plugin-icon-size: 75%; | ||
| --score-container-width: 60px; | ||
| --score-number-font-size: 13px; | ||
| position: fixed; | ||
| position: sticky; | ||
| left: 0; | ||
| right: 0; | ||
| top: var(--topbar-height); | ||
| font-weight: 700; | ||
| display: flex; | ||
| display: none; | ||
brendankenny marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| justify-content: center; | ||
| background-color: var(--color-sticky-header-bg); | ||
| border-bottom: 1px solid var(--color-black-200); | ||
| padding-top: var(--score-container-padding); | ||
| padding-bottom: 4px; | ||
| z-index: 1; | ||
| pointer-events: none; | ||
| opacity: 0; | ||
| } | ||
|
|
||
| .lh-sticky-header--visible { | ||
| display: flex; | ||
| pointer-events: auto; | ||
| opacity: 1; | ||
| } | ||
|
|
||
| /* Disable the gauge arc animation for the sticky header, so toggling display: none | ||
| does not play the animation. */ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. evidence number 25 that these different uses of the score gauges should share less than they do :) |
||
| .lh-sticky-header .lh-gauge-arc { | ||
| animation: none; | ||
| } | ||
|
|
||
| .lh-sticky-header .lh-gauge__label { | ||
|
|
@@ -219,7 +224,7 @@ | |
| <template id="tmpl-lh-topbar"> | ||
| <style> | ||
| .lh-topbar { | ||
| position: fixed; | ||
| position: sticky; | ||
| top: 0; | ||
| left: 0; | ||
| right: 0; | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.