Skip to content
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

core(dom-size): collect only body nodes for DOM stats #7241

Merged
merged 6 commits into from
Apr 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions lighthouse-cli/test/smokehouse/dobetterweb/dbw-expectations.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,17 @@ module.exports = [
}],
},
},
'dom-size': {
score: 1,
rawValue: 31,
details: {
items: [
{statistic: 'Total DOM Elements', value: '31'},
{statistic: 'Maximum DOM Depth', value: '3'},
{statistic: 'Maximum Child Elements', value: '29'},
],
},
},
},
},
];
40 changes: 20 additions & 20 deletions lighthouse-core/audits/dobetterweb/dom-size.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

/**
* @fileoverview Audits a page to see how the size of DOM it creates. Stats like
* tree depth, # children, and total nodes are returned. The score is calculated
* based solely on the total number of nodes found on the page.
* tree depth, # children, and total elements are returned. The score is calculated
* based solely on the total number of elements found on the page.
*/

'use strict';
Expand All @@ -16,35 +16,35 @@ const Audit = require('../audit');
const Util = require('../../report/html/renderer/util.js');
const i18n = require('../../lib/i18n/i18n.js');

const MAX_DOM_NODES = 1500;
const MAX_DOM_ELEMENTS = 1500;
const MAX_DOM_TREE_WIDTH = 60;
const MAX_DOM_TREE_DEPTH = 32;

const UIStrings = {
/** Title of a diagnostic audit that provides detail on the size of the web page's DOM. The size of a DOM is characterized by the total number of DOM nodes and greatest DOM depth. This descriptive title is shown to users when the amount is acceptable and no user action is required. */
/** Title of a diagnostic audit that provides detail on the size of the web page's DOM. The size of a DOM is characterized by the total number of DOM elements and greatest DOM depth. This descriptive title is shown to users when the amount is acceptable and no user action is required. */
title: 'Avoids an excessive DOM size',
/** Title of a diagnostic audit that provides detail on the size of the web page's DOM. The size of a DOM is characterized by the total number of DOM nodes and greatest DOM depth. This imperative title is shown to users when there is a significant amount of execution time that could be reduced. */
/** Title of a diagnostic audit that provides detail on the size of the web page's DOM. The size of a DOM is characterized by the total number of DOM elements and greatest DOM depth. This imperative title is shown to users when there is a significant amount of execution time that could be reduced. */
failureTitle: 'Avoid an excessive DOM size',
/** Description of a Lighthouse audit that tells the user *why* they should reduce the size of the web page's DOM. The size of a DOM is characterized by the total number of DOM nodes and greatest DOM depth. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation. */
/** Description of a Lighthouse audit that tells the user *why* they should reduce the size of the web page's DOM. The size of a DOM is characterized by the total number of DOM elements and greatest DOM depth. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation. */
description: 'Browser engineers recommend pages contain fewer than ' +
`~${MAX_DOM_NODES.toLocaleString()} DOM nodes. The sweet spot is a tree ` +
`~${MAX_DOM_ELEMENTS.toLocaleString()} DOM elements. The sweet spot is a tree ` +
`depth < ${MAX_DOM_TREE_DEPTH} elements and fewer than ${MAX_DOM_TREE_WIDTH} ` +
'children/parent element. A large DOM can increase memory usage, cause longer ' +
'[style calculations](https://developers.google.com/web/fundamentals/performance/rendering/reduce-the-scope-and-complexity-of-style-calculations), ' +
'and produce costly [layout reflows](https://developers.google.com/speed/articles/reflow). [Learn more](https://developers.google.com/web/tools/lighthouse/audits/dom-size).',
/** Table column header for the type of statistic. These statistics describe how big the DOM is (count of DOM nodes, children, depth). */
/** Table column header for the type of statistic. These statistics describe how big the DOM is (count of DOM elements, children, depth). */
columnStatistic: 'Statistic',
/** Table column header for the DOM element. Each DOM element is described with its HTML representation. */
columnElement: 'Element',
/** Table column header for the observed value of the DOM statistic. */
columnValue: 'Value',
/** [ICU Syntax] Label for an audit identifying the number of DOM nodes found in the page. */
/** [ICU Syntax] Label for an audit identifying the number of DOM elements found in the page. */
displayValue: `{itemCount, plural,
=1 {1 node}
other {# nodes}
=1 {1 element}
other {# elements}
}`,
/** Label for the total number of DOM nodes found in the page. */
statisticDOMNodes: 'Total DOM Nodes',
/** Label for the total number of DOM elements found in the page. */
statisticDOMElements: 'Total DOM Elements',
/** Label for the numeric value of the maximum depth in the page's DOM tree. */
statisticDOMDepth: 'Maximum DOM Depth',
/** Label for the numeric value of the maximum number of children any DOM element in the page has. The element described will have the most children in the page. */
Expand All @@ -55,8 +55,8 @@ const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);


class DOMSize extends Audit {
static get MAX_DOM_NODES() {
return MAX_DOM_NODES;
static get MAX_DOM_ELEMENTS() {
return MAX_DOM_ELEMENTS;
}

/**
Expand Down Expand Up @@ -96,7 +96,7 @@ class DOMSize extends Audit {
const stats = artifacts.DOMStats;

const score = Audit.computeLogNormalScore(
stats.totalDOMNodes,
stats.totalBodyElements,
context.options.scorePODR,
context.options.scoreMedian
);
Expand All @@ -111,9 +111,9 @@ class DOMSize extends Audit {
/** @type {LH.Audit.Details.Table['items']} */
const items = [
{
statistic: str_(UIStrings.statisticDOMNodes),
statistic: str_(UIStrings.statisticDOMElements),
element: '',
value: Util.formatNumber(stats.totalDOMNodes),
value: Util.formatNumber(stats.totalBodyElements),
},
{
statistic: str_(UIStrings.statisticDOMDepth),
Expand All @@ -135,8 +135,8 @@ class DOMSize extends Audit {

return {
score,
rawValue: stats.totalDOMNodes,
displayValue: str_(UIStrings.displayValue, {itemCount: stats.totalDOMNodes}),
rawValue: stats.totalBodyElements,
displayValue: str_(UIStrings.displayValue, {itemCount: stats.totalBodyElements}),
extendedInfo: {
value: items,
},
Expand Down
33 changes: 18 additions & 15 deletions lighthouse-core/gather/gatherers/dobetterweb/domstats.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// @ts-nocheck
/**
* @fileoverview Gathers stats about the max height and width of the DOM tree
* and total number of nodes used on the page.
* and total number of elements used on the page.
*/

/* global ShadowRoot, getOuterHTMLSnippet */
Expand Down Expand Up @@ -85,9 +85,10 @@ function elementPathInDOM(element) {
*/
/* istanbul ignore next */
function getDOMStats(element, deep = true) {
let deepestNode = null;
let deepestElement = null;
let maxDepth = 0;
let maxWidth = 0;
let numElements = 0;
let parentWithMostChildren = null;

/**
Expand All @@ -96,7 +97,7 @@ function getDOMStats(element, deep = true) {
*/
const _calcDOMWidthAndHeight = function(element, depth = 1) {
if (depth > maxDepth) {
deepestNode = element;
deepestElement = element;
maxDepth = depth;
}
if (element.children.length > maxWidth) {
Expand All @@ -107,30 +108,32 @@ function getDOMStats(element, deep = true) {
let child = element.firstElementChild;
while (child) {
_calcDOMWidthAndHeight(child, depth + 1);
// If node has shadow dom, traverse into that tree.
// If element has shadow dom, traverse into that tree.
if (deep && child.shadowRoot) {
_calcDOMWidthAndHeight(child.shadowRoot, depth + 1);
}
child = child.nextElementSibling;
numElements++;
}

return {maxDepth, maxWidth};
return {maxDepth, maxWidth, numElements};
};

const result = _calcDOMWidthAndHeight(element);

return {
depth: {
max: result.maxDepth,
pathToElement: elementPathInDOM(deepestNode),
pathToElement: elementPathInDOM(deepestElement),
// ignore style since it will provide no additional context, and is often long
snippet: getOuterHTMLSnippet(deepestNode, ['style']),
snippet: getOuterHTMLSnippet(deepestElement, ['style']),
},
width: {
max: result.maxWidth,
pathToElement: elementPathInDOM(parentWithMostChildren),
snippet: getOuterHTMLSnippet(parentWithMostChildren, ['style']),
},
totalBodyElements: result.numElements,
};
}

Expand All @@ -139,19 +142,19 @@ class DOMStats extends Gatherer {
* @param {LH.Gatherer.PassContext} passContext
* @return {Promise<LH.Artifacts['DOMStats']>}
*/
afterPass(passContext) {
async afterPass(passContext) {
const driver = passContext.driver;

const expression = `(function() {
${pageFunctions.getOuterHTMLSnippetString};
${createSelectorsLabel.toString()};
${elementPathInDOM.toString()};
return (${getDOMStats.toString()}(document.documentElement));
return (${getDOMStats.toString()}(document.body));
})()`;
return passContext.driver.sendCommand('DOM.enable')
.then(() => passContext.driver.evaluateAsync(expression, {useIsolation: true}))
.then(results => passContext.driver.getElementsInDocument().then(allNodes => {
results.totalDOMNodes = allNodes.length;
return passContext.driver.sendCommand('DOM.disable').then(() => results);
}));
await driver.sendCommand('DOM.enable');
const results = await driver.evaluateAsync(expression, {useIsolation: true});
await driver.sendCommand('DOM.disable');
return results;
}
}

Expand Down
16 changes: 8 additions & 8 deletions lighthouse-core/lib/i18n/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,7 @@
},
"lighthouse-core/audits/dobetterweb/dom-size.js | columnStatistic": {
"message": "Statistic",
"description": "Table column header for the type of statistic. These statistics describe how big the DOM is (count of DOM nodes, children, depth)."
"description": "Table column header for the type of statistic. These statistics describe how big the DOM is (count of DOM elements, children, depth)."
},
"lighthouse-core/audits/dobetterweb/dom-size.js | columnValue": {
"message": "Value",
Expand All @@ -596,28 +596,28 @@
"description": "Description of a Lighthouse audit that tells the user *why* they should reduce the size of the web page's DOM. The size of a DOM is characterized by the total number of DOM nodes and greatest DOM depth. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation."
},
"lighthouse-core/audits/dobetterweb/dom-size.js | displayValue": {
"message": "{itemCount, plural,\n =1 {1 node}\n other {# nodes}\n }",
"description": "[ICU Syntax] Label for an audit identifying the number of DOM nodes found in the page."
"message": "{itemCount, plural,\n =1 {1 element}\n other {# elements}\n }",
"description": "[ICU Syntax] Label for an audit identifying the number of DOM elements found in the page."
},
"lighthouse-core/audits/dobetterweb/dom-size.js | failureTitle": {
"message": "Avoid an excessive DOM size",
"description": "Title of a diagnostic audit that provides detail on the size of the web page's DOM. The size of a DOM is characterized by the total number of DOM nodes and greatest DOM depth. This imperative title is shown to users when there is a significant amount of execution time that could be reduced."
"description": "Title of a diagnostic audit that provides detail on the size of the web page's DOM. The size of a DOM is characterized by the total number of DOM elements and greatest DOM depth. This imperative title is shown to users when there is a significant amount of execution time that could be reduced."
},
"lighthouse-core/audits/dobetterweb/dom-size.js | statisticDOMDepth": {
"message": "Maximum DOM Depth",
"description": "Label for the numeric value of the maximum depth in the page's DOM tree."
},
"lighthouse-core/audits/dobetterweb/dom-size.js | statisticDOMNodes": {
"message": "Total DOM Nodes",
"description": "Label for the total number of DOM nodes found in the page."
"lighthouse-core/audits/dobetterweb/dom-size.js | statisticDOMElements": {
"message": "Total DOM Elements",
"description": "Label for the total number of DOM elements found in the page."
},
"lighthouse-core/audits/dobetterweb/dom-size.js | statisticDOMWidth": {
"message": "Maximum Child Elements",
"description": "Label for the numeric value of the maximum number of children any DOM element in the page has. The element described will have the most children in the page."
},
"lighthouse-core/audits/dobetterweb/dom-size.js | title": {
"message": "Avoids an excessive DOM size",
"description": "Title of a diagnostic audit that provides detail on the size of the web page's DOM. The size of a DOM is characterized by the total number of DOM nodes and greatest DOM depth. This descriptive title is shown to users when the amount is acceptable and no user action is required."
"description": "Title of a diagnostic audit that provides detail on the size of the web page's DOM. The size of a DOM is characterized by the total number of DOM elements and greatest DOM depth. This descriptive title is shown to users when the amount is acceptable and no user action is required."
},
"lighthouse-core/audits/font-display.js | description": {
"message": "Leverage the font-display CSS feature to ensure text is user-visible while webfonts are loading. [Learn more](https://developers.google.com/web/updates/2016/02/font-display).",
Expand Down
16 changes: 8 additions & 8 deletions lighthouse-core/test/audits/dobetterweb/dom-size-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ const options = DOMSize.defaultOptions;

/* eslint-env jest */

describe('Num DOM nodes audit', () => {
const numNodes = DOMSize.MAX_DOM_NODES;
describe('DOMSize audit', () => {
const numElements = DOMSize.MAX_DOM_ELEMENTS;
const artifact = {
DOMStats: {
totalDOMNodes: numNodes,
totalBodyElements: numElements,
depth: {max: 1, pathToElement: ['html', 'body', 'div', 'span']},
width: {max: 2, pathToElement: ['html', 'body']},
},
Expand All @@ -24,20 +24,20 @@ describe('Num DOM nodes audit', () => {
it('calculates score hitting mid distribution', () => {
const auditResult = DOMSize.audit(artifact, {options});
assert.equal(auditResult.score, 0.43);
assert.equal(auditResult.rawValue, numNodes);
expect(auditResult.displayValue).toBeDisplayString('1,500 nodes');
assert.equal(auditResult.details.items[0].value, numNodes.toLocaleString());
assert.equal(auditResult.rawValue, numElements);
expect(auditResult.displayValue).toBeDisplayString('1,500 elements');
assert.equal(auditResult.details.items[0].value, numElements.toLocaleString());
assert.equal(auditResult.details.items[1].value, '1');
assert.equal(auditResult.details.items[2].value, '2');
});

it('calculates score hitting top distribution', () => {
artifact.DOMStats.totalDOMNodes = 400;
artifact.DOMStats.totalBodyElements = 400;
assert.equal(DOMSize.audit(artifact, {options}).score, 1);
});

it('calculates score hitting bottom of distribution', () => {
artifact.DOMStats.totalDOMNodes = 5970;
artifact.DOMStats.totalBodyElements = 5970;
assert.equal(DOMSize.audit(artifact, {options}).score, 0);
});
});
2 changes: 1 addition & 1 deletion lighthouse-core/test/results/artifacts/artifacts.json
Original file line number Diff line number Diff line change
Expand Up @@ -2295,7 +2295,7 @@
"body"
]
},
"totalDOMNodes": 53
"totalBodyElements": 31
},
"JSLibraries": [
{
Expand Down
12 changes: 6 additions & 6 deletions lighthouse-core/test/results/sample_v2.json
Original file line number Diff line number Diff line change
Expand Up @@ -2586,8 +2586,8 @@
"description": "Browser engineers recommend pages contain fewer than ~1,500 DOM nodes. The sweet spot is a tree depth < 32 elements and fewer than 60 children/parent element. A large DOM can increase memory usage, cause longer [style calculations](https://developers.google.com/web/fundamentals/performance/rendering/reduce-the-scope-and-complexity-of-style-calculations), and produce costly [layout reflows](https://developers.google.com/speed/articles/reflow). [Learn more](https://developers.google.com/web/tools/lighthouse/audits/dom-size).",
"score": 1,
"scoreDisplayMode": "numeric",
"rawValue": 53,
"displayValue": "53 nodes",
"rawValue": 31,
"displayValue": "31 elements",
"details": {
"type": "table",
"headings": [
Expand All @@ -2609,9 +2609,9 @@
],
"items": [
{
"statistic": "Total DOM Nodes",
"statistic": "Total DOM Elements",
"element": "",
"value": "53"
"value": "31"
},
{
"statistic": "Maximum DOM Depth",
Expand Down Expand Up @@ -5414,7 +5414,7 @@
"lighthouse-core/audits/dobetterweb/dom-size.js | displayValue": [
{
"values": {
"itemCount": 53
"itemCount": 31
},
"path": "audits[dom-size].displayValue"
}
Expand All @@ -5428,7 +5428,7 @@
"lighthouse-core/audits/dobetterweb/dom-size.js | columnValue": [
"audits[dom-size].details.headings[2].text"
],
"lighthouse-core/audits/dobetterweb/dom-size.js | statisticDOMNodes": [
"lighthouse-core/audits/dobetterweb/dom-size.js | statisticDOMElements": [
"audits[dom-size].details.items[0].statistic"
],
"lighthouse-core/audits/dobetterweb/dom-size.js | statisticDOMDepth": [
Expand Down
6 changes: 3 additions & 3 deletions proto/sample_v2_round_trip.json
Original file line number Diff line number Diff line change
Expand Up @@ -501,8 +501,8 @@
],
"items": [
{
"statistic": "Total DOM Nodes",
"value": "53"
"statistic": "Total DOM Elements",
"value": "31"
},
{
"element": {
Expand All @@ -521,7 +521,7 @@
],
"type": "table"
},
"displayValue": "53 nodes",
"displayValue": "31 elements",
"id": "dom-size",
"score": 1.0,
"scoreDisplayMode": "numeric",
Expand Down
3 changes: 2 additions & 1 deletion types/artifacts.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,8 @@ declare global {
}

export interface DOMStats {
totalDOMNodes: number;
/** The total number of elements found within the page's body. */
totalBodyElements: number;
width: {max: number, pathToElement: Array<string>, snippet: string};
depth: {max: number, pathToElement: Array<string>, snippet: string};
}
Expand Down