diff --git a/lighthouse-core/audits/seo/canonical.js b/lighthouse-core/audits/seo/canonical.js index 3b5d32960379..75ad89955737 100644 --- a/lighthouse-core/audits/seo/canonical.js +++ b/lighthouse-core/audits/seo/canonical.js @@ -35,19 +35,6 @@ const UIStrings = { const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings); -/** - * Returns a primary domain for provided URL (e.g. http://www.example.com -> example.com). - * Note that it does not take second-level domains into account (.co.uk). - * @param {URL} url - * @returns {string} - */ -function getPrimaryDomain(url) { - return url.hostname - .split('.') - .slice(-2) - .join('.'); -} - /** * @typedef CanonicalURLData * @property {Set} uniqueCanonicalURLs @@ -173,7 +160,7 @@ class Canonical extends Audit { // bing and yahoo don't allow canonical URLs pointing to different domains, it's also // a common mistake to publish a page with canonical pointing to e.g. a test domain or localhost - if (getPrimaryDomain(canonicalURL) !== getPrimaryDomain(baseURL)) { + if (!URL.rootDomainsMatch(canonicalURL, baseURL)) { return { score: 0, explanation: str_(UIStrings.explanationDifferentDomain, {url: canonicalURL}), diff --git a/lighthouse-core/lib/i18n/en-US.json b/lighthouse-core/lib/i18n/en-US.json index 3c37fd85c737..33ac0c219ed0 100644 --- a/lighthouse-core/lib/i18n/en-US.json +++ b/lighthouse-core/lib/i18n/en-US.json @@ -1343,6 +1343,10 @@ "message": "Expand snippet", "description": "Label for button that shows all lines of the snippet when clicked" }, + "lighthouse-core/report/html/renderer/util.js | thirdPartyResourcesLabel": { + "message": "Show 3rd-party resources", + "description": "This label is for a checkbox above a table of items loaded by a web page. The checkbox is used to show or hide third-party (or \"3rd-party\") resources in the table, where \"third-party resources\" refers to items loaded by a web page from URLs that aren't controlled by the owner of the web page." + }, "lighthouse-core/report/html/renderer/util.js | toplevelWarningsMessage": { "message": "There were issues affecting this run of Lighthouse:", "description": "Label shown preceding any important warnings that may have invalidated the entire report. For example, if the user has Chrome extensions installed, they may add enough performance overhead that Lighthouse's performance metrics are unreliable. If shown, this will be displayed at the top of the report UI." diff --git a/lighthouse-core/lib/url-shim.js b/lighthouse-core/lib/url-shim.js index 52691ef4bb2f..cb8d2333959b 100644 --- a/lighthouse-core/lib/url-shim.js +++ b/lighthouse-core/lib/url-shim.js @@ -9,22 +9,10 @@ * URL shim so we keep our code DRY */ -/* global self */ +/* global URL */ const Util = require('../report/html/renderer/util.js'); -// Type cast so tsc sees window.URL and require('url').URL as sufficiently equivalent. -const URL = /** @type {!Window["URL"]} */ (typeof self !== 'undefined' && self.URL) || - require('url').URL; - -// 25 most used tld plus one domains (aka public suffixes) from http archive. -// @see https://github.com/GoogleChrome/lighthouse/pull/5065#discussion_r191926212 -// The canonical list is https://publicsuffix.org/learn/ but we're only using subset to conserve bytes -const listOfTlds = [ - 'com', 'co', 'gov', 'edu', 'ac', 'org', 'go', 'gob', 'or', 'net', 'in', 'ne', 'nic', 'gouv', - 'web', 'spb', 'blog', 'jus', 'kiev', 'mil', 'wi', 'qc', 'ca', 'bel', 'on', -]; - const allowedProtocols = [ 'https:', 'http:', 'chrome:', 'chrome-extension:', ]; @@ -99,34 +87,18 @@ class URLShim extends URL { } } - /** - * Gets the tld of a domain - * - * @param {string} hostname - * @return {string} tld - */ - static getTld(hostname) { - const tlds = hostname.split('.').slice(-2); - - if (!listOfTlds.includes(tlds[0])) { - return `.${tlds[tlds.length - 1]}`; - } - - return `.${tlds.join('.')}`; - } - /** * Check if rootDomains matches * - * @param {string} urlA - * @param {string} urlB + * @param {string|URL} urlA + * @param {string|URL} urlB */ static rootDomainsMatch(urlA, urlB) { let urlAInfo; let urlBInfo; try { - urlAInfo = new URL(urlA); - urlBInfo = new URL(urlB); + urlAInfo = Util.createOrReturnURL(urlA); + urlBInfo = Util.createOrReturnURL(urlB); } catch (err) { return false; } @@ -135,14 +107,9 @@ class URLShim extends URL { return false; } - const tldA = URLShim.getTld(urlAInfo.hostname); - const tldB = URLShim.getTld(urlBInfo.hostname); - // get the string before the tld - const urlARootDomain = urlAInfo.hostname.replace(new RegExp(`${tldA}$`), '') - .split('.').splice(-1)[0]; - const urlBRootDomain = urlBInfo.hostname.replace(new RegExp(`${tldB}$`), '') - .split('.').splice(-1)[0]; + const urlARootDomain = Util.getRootDomain(urlAInfo); + const urlBRootDomain = Util.getRootDomain(urlBInfo); return urlARootDomain === urlBRootDomain; } diff --git a/lighthouse-core/report/html/renderer/details-renderer.js b/lighthouse-core/report/html/renderer/details-renderer.js index 2240e62e5fde..184250ca5d03 100644 --- a/lighthouse-core/report/html/renderer/details-renderer.js +++ b/lighthouse-core/report/html/renderer/details-renderer.js @@ -124,7 +124,11 @@ class DetailsRenderer { element.appendChild(hostElem); } - if (title) element.title = url; + if (title) { + element.title = url; + // set the url on the element's dataset which we use to check 3rd party origins + element.dataset.url = url; + } return element; } diff --git a/lighthouse-core/report/html/renderer/report-ui-features.js b/lighthouse-core/report/html/renderer/report-ui-features.js index 883bb54adf30..3c8e29b3e1cb 100644 --- a/lighthouse-core/report/html/renderer/report-ui-features.js +++ b/lighthouse-core/report/html/renderer/report-ui-features.js @@ -16,15 +16,25 @@ */ 'use strict'; +/* eslint-env browser */ + /** * @fileoverview Adds export button, print, and other dynamic functionality to * the report. */ -/* globals self URL Blob CustomEvent getFilenamePrefix window */ +/* globals getFilenamePrefix Util */ /** @typedef {import('./dom.js')} DOM */ +/** + * @param {HTMLTableElement} tableEl + * @return {Array} + */ +function getTableRows(tableEl) { + return Array.from(tableEl.tBodies[0].rows); +} + class ReportUIFeatures { /** * @param {DOM} dom @@ -73,6 +83,7 @@ class ReportUIFeatures { this.json = report; this._setupMediaQueryListeners(); this._setupExportButton(); + this._setupThirdPartyFilter(); this._setupStickyHeaderElements(); this._setUpCollapseDetailsAfterPrinting(); this._resetUIState(); @@ -119,6 +130,92 @@ class ReportUIFeatures { dropdown.addEventListener('click', this.onExport); } + _setupThirdPartyFilter() { + // Some audits should not display the third party filter option. + const thirdPartyFilterAuditExclusions = [ + // This audit deals explicitly with third party resources. + 'uses-rel-preconnect', + ]; + + // Get all tables with a text url column. + /** @type {Array} */ + const tables = Array.from(this._document.querySelectorAll('.lh-table')); + const tablesWithUrls = tables + .filter(el => el.querySelector('td.lh-table-column--url')) + .filter(el => { + const containingAudit = el.closest('.lh-audit'); + if (!containingAudit) throw new Error('.lh-table not within audit'); + return !thirdPartyFilterAuditExclusions.includes(containingAudit.id); + }); + + tablesWithUrls.forEach((tableEl, index) => { + const thirdPartyRows = this._getThirdPartyRows(tableEl, this.json.finalUrl); + // No 3rd parties, no checkbox! + if (!thirdPartyRows.size) return; + + // create input box + const filterTemplate = this._dom.cloneTemplate('#tmpl-lh-3p-filter', this._document); + 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. + // 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()) { + row.remove(); + } + } else { + // Add row elements back to original positions. + for (const [position, row] of thirdPartyRows.entries()) { + const childrenArr = getTableRows(tableEl); + tableEl.tBodies[0].insertBefore(row, childrenArr[position]); + } + } + }); + + this._dom.find('label', filterTemplate).setAttribute('for', id); + this._dom.find('.lh-3p-filter-count', filterTemplate).textContent = + `${thirdPartyRows.size}`; + this._dom.find('.lh-3p-ui-string', filterTemplate).textContent = + Util.UIStrings.thirdPartyResourcesLabel; + + // Finally, add checkbox to the DOM. + if (!tableEl.parentNode) return; // Keep tsc happy. + tableEl.parentNode.insertBefore(filterTemplate, tableEl); + }); + } + + /** + * From a table with URL entries, finds the rows containing third-party URLs + * and returns a Map of those rows, mapping from row index to row Element. + * @param {HTMLTableElement} el + * @param {string} finalUrl + * @return {Map} + */ + _getThirdPartyRows(el, finalUrl) { + const urlItems = this._dom.findAll('.lh-text__url', el); + const finalUrlRootDomain = Util.getRootDomain(finalUrl); + + /** @type {Map} */ + const thirdPartyRows = new Map(); + for (const urlItem of urlItems) { + const datasetUrl = urlItem.dataset.url; + if (!datasetUrl) continue; + const isThirdParty = Util.getRootDomain(datasetUrl) !== finalUrlRootDomain; + if (!isThirdParty) continue; + + const urlRowEl = urlItem.closest('tr'); + if (urlRowEl) { + const rowPosition = getTableRows(el).indexOf(urlRowEl); + thirdPartyRows.set(rowPosition, urlRowEl); + } + } + + return thirdPartyRows; + } + _setupStickyHeaderElements() { this.topbarEl = this._dom.find('.lh-topbar', this._document); this.scoreScaleEl = this._dom.find('.lh-scorescale', this._document); diff --git a/lighthouse-core/report/html/renderer/util.js b/lighthouse-core/report/html/renderer/util.js index f0dd4e25c472..9b4036e9594a 100644 --- a/lighthouse-core/report/html/renderer/util.js +++ b/lighthouse-core/report/html/renderer/util.js @@ -16,7 +16,7 @@ */ 'use strict'; -/* globals self URL */ +/* globals self, URL */ const ELLIPSIS = '\u2026'; const NBSP = '\xa0'; @@ -29,6 +29,14 @@ const RATINGS = { ERROR: {label: 'error'}, }; +// 25 most used tld plus one domains (aka public suffixes) from http archive. +// @see https://github.com/GoogleChrome/lighthouse/pull/5065#discussion_r191926212 +// The canonical list is https://publicsuffix.org/learn/ but we're only using subset to conserve bytes +const listOfTlds = [ + 'com', 'co', 'gov', 'edu', 'ac', 'org', 'go', 'gob', 'or', 'net', 'in', 'ne', 'nic', 'gouv', + 'web', 'spb', 'blog', 'jus', 'kiev', 'mil', 'wi', 'qc', 'ca', 'bel', 'on', +]; + class Util { static get PASS_THRESHOLD() { return PASS_THRESHOLD; @@ -336,6 +344,51 @@ class Util { }; } + /** + * @param {string|URL} value + * @return {URL} + */ + static createOrReturnURL(value) { + if (value instanceof URL) { + return value; + } + + return new URL(value); + } + + /** + * Gets the tld of a domain + * + * @param {string} hostname + * @return {string} tld + */ + static getTld(hostname) { + const tlds = hostname.split('.').slice(-2); + + if (!listOfTlds.includes(tlds[0])) { + return `.${tlds[tlds.length - 1]}`; + } + + return `.${tlds.join('.')}`; + } + + /** + * Returns a primary domain for provided hostname (e.g. www.example.com -> example.com). + * @param {string|URL} url hostname or URL object + * @returns {string} + */ + static getRootDomain(url) { + const hostname = Util.createOrReturnURL(url).hostname; + const tld = Util.getTld(hostname); + + // tld is .com or .co.uk which means we means that length is 1 to big + // .com => 2 & .co.uk => 3 + const splitTld = tld.split('.'); + + // get TLD + root domain + return hostname.split('.').slice(-splitTld.length).join('.'); + } + /** * @param {LH.Config.Settings} settings * @return {Array<{name: string, description: string}>} @@ -524,6 +577,9 @@ Util.UIStrings = { lsPerformanceCategoryDescription: '[Lighthouse](https://developers.google.com/web/tools/lighthouse/) analysis of the current page on an emulated mobile network. Values are estimated and may vary.', /** Title of the lab data section of the Performance category. Within this section are various speed metrics which quantify the pageload performance into values presented in seconds and milliseconds. "Lab" is an abbreviated form of "laboratory", and refers to the fact that the data is from a controlled test of a website, not measurements from real users visiting that site. */ labDataTitle: 'Lab Data', + + /** This label is for a checkbox above a table of items loaded by a web page. The checkbox is used to show or hide third-party (or "3rd-party") resources in the table, where "third-party resources" refers to items loaded by a web page from URLs that aren't controlled by the owner of the web page. */ + thirdPartyResourcesLabel: 'Show 3rd-party resources', }; if (typeof module !== 'undefined' && module.exports) { diff --git a/lighthouse-core/report/html/templates.html b/lighthouse-core/report/html/templates.html index e3e3753c203f..e485033b7463 100644 --- a/lighthouse-core/report/html/templates.html +++ b/lighthouse-core/report/html/templates.html @@ -426,7 +426,6 @@ } -
@@ -601,7 +600,7 @@ align-items: center; flex-direction: column; text-decoration: none; - + --circle-border-width: 8; --transition-length: 1s; @@ -848,6 +847,27 @@ + + - + diff --git a/lighthouse-core/test/lib/url-shim-test.js b/lighthouse-core/test/lib/url-shim-test.js index 2247011ea296..320841dcedaa 100644 --- a/lighthouse-core/test/lib/url-shim-test.js +++ b/lighthouse-core/test/lib/url-shim-test.js @@ -105,15 +105,6 @@ describe('URL Shim', () => { assert.equal(URL.getOrigin(urlD), null); }); - describe('getTld', () => { - it('returns the correct tld', () => { - assert.equal(URL.getTld('example.com'), '.com'); - assert.equal(URL.getTld('example.co.uk'), '.co.uk'); - assert.equal(URL.getTld('example.com.br'), '.com.br'); - assert.equal(URL.getTld('example.tokyo.jp'), '.jp'); - }); - }); - describe('rootDomainsMatch', () => { it('matches a subdomain and a root domain', () => { const urlA = 'http://example.com/js/test.js'; diff --git a/lighthouse-core/test/report/html/renderer/details-renderer-test.js b/lighthouse-core/test/report/html/renderer/details-renderer-test.js index edc5e93bb25b..96b63171ace5 100644 --- a/lighthouse-core/test/report/html/renderer/details-renderer-test.js +++ b/lighthouse-core/test/report/html/renderer/details-renderer-test.js @@ -393,6 +393,7 @@ describe('DetailsRenderer', () => { assert.equal(urlEl.localName, 'div'); assert.equal(urlEl.title, urlText); + assert.equal(urlEl.dataset.url, urlText); assert.ok(urlEl.firstChild.classList.contains('lh-text')); assert.equal(urlEl.textContent, displayUrlText); }); @@ -417,6 +418,7 @@ describe('DetailsRenderer', () => { assert.equal(urlEl.localName, 'div'); assert.equal(urlEl.title, urlText); + assert.equal(urlEl.dataset.url, urlText); assert.ok(urlEl.firstChild.classList.contains('lh-text')); assert.equal(urlEl.textContent, displayUrlText); }); diff --git a/lighthouse-core/test/report/html/renderer/report-ui-features-test.js b/lighthouse-core/test/report/html/renderer/report-ui-features-test.js index 4db93baebe49..bf47b04d467c 100644 --- a/lighthouse-core/test/report/html/renderer/report-ui-features-test.js +++ b/lighthouse-core/test/report/html/renderer/report-ui-features-test.js @@ -11,7 +11,6 @@ const assert = require('assert'); const fs = require('fs'); const jsdom = require('jsdom'); const Util = require('../../../../report/html/renderer/util.js'); -const URL = require('../../../../lib/url-shim'); const DOM = require('../../../../report/html/renderer/dom.js'); const DetailsRenderer = require('../../../../report/html/renderer/details-renderer.js'); const ReportUIFeatures = require('../../../../report/html/renderer/report-ui-features.js'); @@ -19,7 +18,7 @@ const CategoryRenderer = require('../../../../report/html/renderer/category-rend const CriticalRequestChainRenderer = require( '../../../../report/html/renderer/crc-details-renderer.js'); const ReportRenderer = require('../../../../report/html/renderer/report-renderer.js'); -const sampleResults = require('../../../results/sample_v2.json'); +const sampleResultsOrig = require('../../../results/sample_v2.json'); const TEMPLATE_FILE = fs.readFileSync(__dirname + '/../../../../report/html/templates.html', 'utf8'); @@ -29,9 +28,10 @@ const TEMPLATE_FILE_REPORT = fs.readFileSync(__dirname + describe('ReportUIFeatures', () => { let renderer; let reportUIFeatures; + let sampleResults; + let dom; beforeAll(() => { - global.URL = URL; global.Util = Util; global.ReportUIFeatures = ReportUIFeatures; global.CriticalRequestChainRenderer = CriticalRequestChainRenderer; @@ -51,8 +51,9 @@ describe('ReportUIFeatures', () => { }; }; - const document = new jsdom.JSDOM(TEMPLATE_FILE); - const documentReport = new jsdom.JSDOM(TEMPLATE_FILE_REPORT); + const reportWithTemplates = TEMPLATE_FILE_REPORT + .replace('%%LIGHTHOUSE_TEMPLATES%%', TEMPLATE_FILE); + const document = new jsdom.JSDOM(reportWithTemplates); global.self = document.window; global.self.matchMedia = function() { return { @@ -60,6 +61,8 @@ describe('ReportUIFeatures', () => { }; }; + global.HTMLInputElement = document.window.HTMLInputElement; + global.window = document.window; global.window.getComputedStyle = function() { return { @@ -68,17 +71,16 @@ describe('ReportUIFeatures', () => { }; }; - const dom = new DOM(document.window.document); - const dom2 = new DOM(documentReport.window.document); + dom = new DOM(document.window.document); const detailsRenderer = new DetailsRenderer(dom); const categoryRenderer = new CategoryRenderer(dom, detailsRenderer); renderer = new ReportRenderer(dom, categoryRenderer); - reportUIFeatures = new ReportUIFeatures(dom2); + sampleResults = Util.prepareReportResult(sampleResultsOrig); + reportUIFeatures = new ReportUIFeatures(dom); }); afterAll(() => { global.self = undefined; - global.URL = undefined; global.Util = undefined; global.ReportUIFeatures = undefined; global.matchMedia = undefined; @@ -89,17 +91,74 @@ describe('ReportUIFeatures', () => { global.PerformanceCategoryRenderer = undefined; global.PwaCategoryRenderer = undefined; global.window = undefined; + global.HTMLInputElement = undefined; }); - describe('initFeature', () => { + describe('initFeatures', () => { it('should init a report', () => { // render a report onto the UIFeature dom - const container = reportUIFeatures._dom._document.body; + const container = reportUIFeatures._dom._document.querySelector('main'); renderer.renderReport(sampleResults, container); assert.equal(reportUIFeatures.json, undefined); reportUIFeatures.initFeatures(sampleResults); assert.ok(reportUIFeatures.json); }); + + describe('thrid-party filtering', () => { + let container; + + beforeAll(() => { + const lhr = JSON.parse(JSON.stringify(sampleResults)); + lhr.requestedUrl = lhr.finalUrl = 'http://www.example.com'; + const webpAuditItemTemplate = sampleResults.audits['uses-webp-images'].details.items[0]; + // Interleave first/third party URLs to test restoring order. + lhr.audits['uses-webp-images'].details.items = [ + { + ...webpAuditItemTemplate, + url: 'http://www.cdn.com/img1.jpg', // Third party, will be filtered. + }, + { + ...webpAuditItemTemplate, + url: 'http://www.example.com/img2.jpg', // First party, not filtered. + }, + { + ...webpAuditItemTemplate, + url: 'http://www.notexample.com/img3.jpg', // Third party, will be filtered. + }, + ]; + + // render a report onto the UIFeature dom + container = dom.find('main', dom._document); + renderer.renderReport(lhr, container); + reportUIFeatures.initFeatures(lhr); + }); + + it('filters out third party resources in details tables when checkbox is clicked', () => { + const filterCheckbox = dom.find('#uses-webp-images .lh-3p-filter-input', container); + + function getUrlsInTable() { + return dom + .findAll('#uses-webp-images .lh-details .lh-text__url .lh-text:first-child', container) + .map(el => el.textContent); + } + + expect(getUrlsInTable()).toEqual(['/img1.jpg', '/img2.jpg', '/img3.jpg']); + filterCheckbox.click(); + expect(getUrlsInTable()).toEqual(['/img2.jpg']); + filterCheckbox.click(); + expect(getUrlsInTable()).toEqual(['/img1.jpg', '/img2.jpg', '/img3.jpg']); + }); + + it('adds no filter for audits that do not need them', () => { + const checkboxClassName = 'lh-3p-filter-input'; + + const yesCheckbox = dom.find(`#uses-webp-images .${checkboxClassName}`, container); + expect(yesCheckbox).toBeTruthy(); + + expect(() => dom.find(`#uses-rel-preconnect .${checkboxClassName}`, container)) + .toThrowError('query #uses-rel-preconnect .lh-3p-filter-input not found'); + }); + }); }); }); diff --git a/lighthouse-core/test/report/html/renderer/util-test.js b/lighthouse-core/test/report/html/renderer/util-test.js index d59a7e7b3fdc..aa5872ce6a22 100644 --- a/lighthouse-core/test/report/html/renderer/util-test.js +++ b/lighthouse-core/test/report/html/renderer/util-test.js @@ -13,6 +13,7 @@ const NBSP = '\xa0'; /* eslint-env jest */ /* eslint-disable no-console */ +/* global URL */ describe('util helpers', () => { let origConsoleWarn; @@ -182,4 +183,39 @@ describe('util helpers', () => { assert.strictEqual(interactiveRef.stackPacks, undefined); }); }); + + describe('getTld', () => { + it('returns the correct tld', () => { + assert.equal(Util.getTld('example.com'), '.com'); + assert.equal(Util.getTld('example.co.uk'), '.co.uk'); + assert.equal(Util.getTld('example.com.br'), '.com.br'); + assert.equal(Util.getTld('example.tokyo.jp'), '.jp'); + }); + }); + + describe('getRootDomain', () => { + it('returns the correct rootDomain from a string', () => { + assert.equal(Util.getRootDomain('https://www.example.com/index.html'), 'example.com'); + assert.equal(Util.getRootDomain('https://example.com'), 'example.com'); + assert.equal(Util.getRootDomain('https://www.example.co.uk'), 'example.co.uk'); + assert.equal(Util.getRootDomain('https://example.com.br/app/'), 'example.com.br'); + assert.equal(Util.getRootDomain('https://example.tokyo.jp'), 'tokyo.jp'); + assert.equal(Util.getRootDomain('https://sub.example.com'), 'example.com'); + assert.equal(Util.getRootDomain('https://sub.example.tokyo.jp'), 'tokyo.jp'); + assert.equal(Util.getRootDomain('http://localhost'), 'localhost'); + assert.equal(Util.getRootDomain('http://localhost:8080'), 'localhost'); + }); + + it('returns the correct rootDomain from an URL object', () => { + assert.equal(Util.getRootDomain(new URL('https://www.example.com/index.html')), 'example.com'); + assert.equal(Util.getRootDomain(new URL('https://example.com')), 'example.com'); + assert.equal(Util.getRootDomain(new URL('https://www.example.co.uk')), 'example.co.uk'); + assert.equal(Util.getRootDomain(new URL('https://example.com.br/app/')), 'example.com.br'); + assert.equal(Util.getRootDomain(new URL('https://example.tokyo.jp')), 'tokyo.jp'); + assert.equal(Util.getRootDomain(new URL('https://sub.example.com')), 'example.com'); + assert.equal(Util.getRootDomain(new URL('https://sub.example.tokyo.jp')), 'tokyo.jp'); + assert.equal(Util.getRootDomain(new URL('http://localhost')), 'localhost'); + assert.equal(Util.getRootDomain(new URL('http://localhost:8080')), 'localhost'); + }); + }); }); diff --git a/lighthouse-core/test/results/sample_v2.json b/lighthouse-core/test/results/sample_v2.json index 0d28987cbb71..ff65636ee0be 100644 --- a/lighthouse-core/test/results/sample_v2.json +++ b/lighthouse-core/test/results/sample_v2.json @@ -4631,6 +4631,7 @@ "passedAuditsGroupTitle": "Passed audits", "snippetCollapseButtonLabel": "Collapse snippet", "snippetExpandButtonLabel": "Expand snippet", + "thirdPartyResourcesLabel": "Show 3rd-party resources", "toplevelWarningsMessage": "There were issues affecting this run of Lighthouse:", "varianceDisclaimer": "Values are estimated and may vary.", "warningAuditsGroupTitle": "Passed audits but with warnings", diff --git a/proto/lighthouse-result.proto b/proto/lighthouse-result.proto index 5eaf33a87f64..d3d485782d2e 100644 --- a/proto/lighthouse-result.proto +++ b/proto/lighthouse-result.proto @@ -356,6 +356,9 @@ message I18n { // The label for the button to show only a few lines of a snippet string snippet_collapse_button_label = 19; + + // This label is for a filter checkbox above a table of items + string third_party_resources_label = 20; } // The message holding all formatted strings diff --git a/proto/sample_v2_round_trip.json b/proto/sample_v2_round_trip.json index 84813c3df6a2..212d6b1c7022 100644 --- a/proto/sample_v2_round_trip.json +++ b/proto/sample_v2_round_trip.json @@ -3650,6 +3650,7 @@ "passedAuditsGroupTitle": "Passed audits", "snippetCollapseButtonLabel": "Collapse snippet", "snippetExpandButtonLabel": "Expand snippet", + "thirdPartyResourcesLabel": "Show 3rd-party resources", "toplevelWarningsMessage": "There were issues affecting this run of Lighthouse:", "varianceDisclaimer": "Values are estimated and may vary.", "warningAuditsGroupTitle": "Passed audits but with warnings",