[go: nahoru, domu]

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(page-functions): don't try to clone a ShadowRoot #9079

Merged
merged 9 commits into from
Jun 7, 2019
12 changes: 12 additions & 0 deletions lighthouse-cli/test/fixtures/dobetterweb/dbw_tester.html
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,18 @@
<object id="5934a"></object>
<object id="5934b"></object>

<div id="shadow-root-container"></div>
<script>
// DOM Size has had trouble with very specific shadow root traversal issues.
// Make sure it can handle reporting of the widest element being a shadow root.
const shadowRootContainer = document.getElementById('shadow-root-container');
const shadowRoot = shadowRootContainer.attachShadow({mode: 'open'});
for (let i = 0; i < 100; i++) {
const span = document.createElement('span');
shadowRoot.appendChild(span);
}
</script>

<script>
// Ensure gatherers still work when individual elements override '.matches'
document.getElementById("5934a").matches = "blahblah";
Expand Down
10 changes: 7 additions & 3 deletions lighthouse-cli/test/smokehouse/dobetterweb/dbw-expectations.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,16 @@ module.exports = [
},
'dom-size': {
score: 1,
numericValue: 35,
numericValue: 137,
details: {
items: [
{statistic: 'Total DOM Elements', value: '35'},
{statistic: 'Total DOM Elements', value: '137'},
{statistic: 'Maximum DOM Depth', value: '3'},
{statistic: 'Maximum Child Elements', value: '33'},
{
statistic: 'Maximum Child Elements',
value: '100',
element: {value: '<div id="shadow-root-container">'},
},
],
},
},
Expand Down
30 changes: 19 additions & 11 deletions lighthouse-core/lib/page-functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// @ts-nocheck
'use strict';

/* global window document Node */
/* global window document Node ShadowRoot */

/**
* Helper functions that are passed by `toString()` by Driver to be evaluated in target page.
Expand Down Expand Up @@ -107,24 +107,32 @@ function getElementsInDocument(selector) {

/**
* Gets the opening tag text of the given node.
* @param {Element} element
* @param {Element|ShadowRoot} element
* @param {Array<string>=} ignoreAttrs An optional array of attribute tags to not include in the HTML snippet.
* @return {string}
*/
/* istanbul ignore next */
function getOuterHTMLSnippet(element, ignoreAttrs = []) {
const clone = element.cloneNode();

ignoreAttrs.forEach(attribute =>{
clone.removeAttribute(attribute);
});

const reOpeningTag = /^[\s\S]*?>/;
const match = clone.outerHTML.match(reOpeningTag);
try {
// ShadowRoots are sometimes passed in; use their hosts' outerHTML.
if (element instanceof ShadowRoot) {
element = element.host;
}

return (match && match[0]) || '';
const clone = element.cloneNode();
ignoreAttrs.forEach(attribute =>{
clone.removeAttribute(attribute);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the types on this whole function get kind of broken if we aren't really accepting just Element.

removeAttribute and outerHTML (and more or less localName) exist only on Element.

Maybe we should type it as @param {Element|ShadowRoot} element and (if we don't need that localName check) have the conditional be just if (element instanceof ShadowRoot) element = element.host; (with a comment somewhere explaining that this function handles the ShadowRoot case).

It's still playing a little fast and loose with types (it doesn't help that the tsc built in types don't recognize ShadowRoot as a global value), but it simplifies things a bit.

});
const reOpeningTag = /^[\s\S]*?>/;
const match = clone.outerHTML.match(reOpeningTag);
return (match && match[0]) || '';
} catch (_) {
// As a last resort, fall back to localName.
return `<${element.localName}>`;
Copy link
Collaborator
@connorjclark connorjclark Jun 3, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this entire range fail? if it's just the clone, could you reduce the try/catch to just that part?

a comment in the catch block would be useful.

}
}


/**
* Computes a memory/CPU performance benchmark index to determine rough device class.
* @see https://docs.google.com/spreadsheets/d/1E0gZwKsxegudkjJl8Fki_sOwHKpqgXwt8aBAfuUaB8A/edit?usp=sharing
Expand Down
14 changes: 13 additions & 1 deletion lighthouse-core/test/lib/page-functions-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,15 @@ describe('Page Functions', () => {
let dom;

beforeAll(() => {
const {document} = new jsdom.JSDOM().window;
const {document, ShadowRoot} = new jsdom.JSDOM().window;
global.ShadowRoot = ShadowRoot;
dom = new DOM(document);
});

afterAll(() => {
global.ShadowRoot = undefined;
});

describe('get outer HTML snippets', () => {
it('gets full HTML snippet', () => {
assert.equal(pageFunctions.getOuterHTMLSnippet(
Expand All @@ -38,6 +43,13 @@ describe('Page Functions', () => {
), '<div id="1">');
});

it('should handle dom nodes that cannot be cloned', () => {
const element = dom.createElement('div');
element.cloneNode = () => {
throw new Error('oops!');
};
assert.equal(pageFunctions.getOuterHTMLSnippet(element), '<div>');
});
it('ignores when attribute not found', () => {
assert.equal(pageFunctions.getOuterHTMLSnippet(
dom.createElement('div', '', {'id': '1', 'style': 'style', 'aria-label': 'label'}),
Expand Down