Skip to content

Commit

Permalink
Merge 9e7644e into 3c44137
Browse files Browse the repository at this point in the history
  • Loading branch information
connorjclark committed Nov 10, 2020
2 parents 3c44137 + 9e7644e commit 6a355c4
Show file tree
Hide file tree
Showing 9 changed files with 215 additions and 56 deletions.
24 changes: 24 additions & 0 deletions lighthouse-core/gather/driver.js
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,7 @@ class Driver {
}

/**
* Note: Prefer `evaluate` instead.
* Evaluate an expression in the context of the current page. If useIsolation is true, the expression
* will be evaluated in a content script that has access to the page's DOM but whose JavaScript state
* is completely separate.
Expand All @@ -464,6 +465,29 @@ class Driver {
}
}

/**
* @typedef {any[]} TExtendsArray
*/

/**
* Evaluate an expression (optionally defined in a structured manner, see `createEvalCode`
* in `page-functions.js`).
* See `evaluateAsync`.
* @template {TExtendsArray} T, R
* @param {string | ((...args: T) => R)} expressionOrMainFn
* @param {{useIsolation?: boolean, args?: T, deps?: Array<Function|string>}=} options
* @return {Promise<R>}
*/
async evaluate(expressionOrMainFn, options = {}) {
if (typeof expressionOrMainFn !== 'string') {
expressionOrMainFn = pageFunctions.createEvalCode(expressionOrMainFn, {
mode: 'iife',
...options,
});
}
return this.evaluateAsync(expressionOrMainFn, options);
}

/**
* Evaluate an expression in the given execution context; an undefined contextId implies the main
* page without isolation.
Expand Down
3 changes: 2 additions & 1 deletion lighthouse-core/gather/fetcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,8 @@ class Fetcher {
requestInterceptionPromise,
]).finally(() => clearTimeout(timeoutHandle));

const injectionPromise = this.driver.evaluateAsync(`${injectIframe}(${JSON.stringify(url)})`, {
const injectionPromise = this.driver.evaluate(injectIframe, {
args: [url],
useIsolation: true,
});

Expand Down
16 changes: 8 additions & 8 deletions lighthouse-core/gather/gatherers/link-elements.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const LinkHeader = require('http-link-header');
const Gatherer = require('./gatherer.js');
const {URL} = require('../../lib/url-shim.js');
const NetworkAnalyzer = require('../../lib/dependency-graph/simulator/network-analyzer.js');
const {getElementsInDocumentString, getNodeDetailsString} = require('../../lib/page-functions.js');
const pageFunctions = require('../../lib/page-functions.js');

/* globals HTMLLinkElement getNodeDetails */

Expand Down Expand Up @@ -86,13 +86,13 @@ class LinkElements extends Gatherer {
static getLinkElementsInDOM(passContext) {
// We'll use evaluateAsync because the `node.getAttribute` method doesn't actually normalize
// the values like access from JavaScript does.
return passContext.driver.evaluateAsync(`(() => {
${getElementsInDocumentString};
${getLinkElementsInDOM};
${getNodeDetailsString};
return getLinkElementsInDOM();
})()`, {useIsolation: true});
return passContext.driver.evaluate(getLinkElementsInDOM, {
useIsolation: true,
deps: [
pageFunctions.getNodeDetailsString,
pageFunctions.getElementsInDocument,
],
});
}

/**
Expand Down
42 changes: 28 additions & 14 deletions lighthouse-core/gather/gatherers/meta-elements.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,30 @@
'use strict';

const Gatherer = require('./gatherer.js');
const getElementsInDocumentString = require('../../lib/page-functions.js').getElementsInDocumentString; // eslint-disable-line max-len
const pageFunctions = require('../../lib/page-functions.js');

/* globals getElementsInDocument */

/* istanbul ignore next */
function collectMetaElements() {
// @ts-expect-error - getElementsInDocument put into scope via stringification
const metas = /** @type {HTMLMetaElement[]} */ (getElementsInDocument('head meta'));
return metas.map(meta => {
/** @param {string} name */
const getAttribute = name => {
const attr = meta.attributes.getNamedItem(name);
if (!attr) return;
return attr.value;
};
return {
name: meta.name.toLowerCase(),
content: meta.content,
property: getAttribute('property'),
httpEquiv: meta.httpEquiv ? meta.httpEquiv.toLowerCase() : undefined,
charset: getAttribute('charset'),
};
});
}

class MetaElements extends Gatherer {
/**
Expand All @@ -18,19 +41,10 @@ class MetaElements extends Gatherer {

// We'll use evaluateAsync because the `node.getAttribute` method doesn't actually normalize
// the values like access from JavaScript does.
return driver.evaluateAsync(`(() => {
${getElementsInDocumentString};
return getElementsInDocument('head meta').map(meta => {
return {
name: meta.name.toLowerCase(),
content: meta.content,
property: meta.attributes.property ? meta.attributes.property.value : undefined,
httpEquiv: meta.httpEquiv ? meta.httpEquiv.toLowerCase() : undefined,
charset: meta.attributes.charset ? meta.attributes.charset.value : undefined,
};
});
})()`, {useIsolation: true});
return driver.evaluate(collectMetaElements, {
useIsolation: true,
deps: [pageFunctions.getElementsInDocument],
});
}
}

Expand Down
14 changes: 7 additions & 7 deletions lighthouse-core/gather/gatherers/script-elements.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
const Gatherer = require('./gatherer.js');
const NetworkAnalyzer = require('../../lib/dependency-graph/simulator/network-analyzer.js');
const NetworkRequest = require('../../lib/network-request.js');
const getElementsInDocumentString = require('../../lib/page-functions.js').getElementsInDocumentString; // eslint-disable-line max-len
const pageFunctions = require('../../lib/page-functions.js');

/* global getNodeDetails */
Expand Down Expand Up @@ -72,12 +71,13 @@ class ScriptElements extends Gatherer {
const driver = passContext.driver;
const mainResource = NetworkAnalyzer.findMainDocument(loadData.networkRecords, passContext.url);

/** @type {LH.Artifacts['ScriptElements']} */
const scripts = await driver.evaluateAsync(`(() => {
${getElementsInDocumentString}
${pageFunctions.getNodeDetailsString};
return (${collectAllScriptElements.toString()})();
})()`, {useIsolation: true});
const scripts = await driver.evaluate(collectAllScriptElements, {
useIsolation: true,
deps: [
pageFunctions.getNodeDetailsString,
pageFunctions.getElementsInDocument,
],
});

for (const script of scripts) {
if (script.content) script.requestId = mainResource.requestId;
Expand Down
48 changes: 25 additions & 23 deletions lighthouse-core/gather/gatherers/seo/tap-targets.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const tapTargetsSelector = TARGET_SELECTORS.join(',');

/**
* @param {HTMLElement} element
* @returns {boolean}
* @return {boolean}
*/
/* istanbul ignore next */
function elementIsVisible(element) {
Expand All @@ -42,7 +42,7 @@ function elementIsVisible(element) {

/**
* @param {Element} element
* @returns {LH.Artifacts.Rect[]}
* @return {LH.Artifacts.Rect[]}
*/
/* istanbul ignore next */
function getClientRects(element) {
Expand All @@ -65,7 +65,7 @@ function getClientRects(element) {
/**
* @param {Element} element
* @param {string} tapTargetsSelector
* @returns {boolean}
* @return {boolean}
*/
/* istanbul ignore next */
function elementHasAncestorTapTarget(element, tapTargetsSelector) {
Expand Down Expand Up @@ -119,7 +119,7 @@ function hasTextNodeSiblingsFormingTextBlock(element) {
* Makes a reasonable guess, but for example gets it wrong if the element is surrounded by other
* HTML elements instead of direct text nodes.
* @param {Element} element
* @returns {boolean}
* @return {boolean}
*/
/* istanbul ignore next */
function elementIsInTextBlock(element) {
Expand Down Expand Up @@ -160,7 +160,7 @@ function elementCenterIsAtZAxisTop(el, elCenterPoint) {
/**
* Finds all position sticky/absolute elements on the page and adds a class
* that disables pointer events on them.
* @returns {() => void} - undo function to re-enable pointer events
* @return {() => void} - undo function to re-enable pointer events
*/
/* istanbul ignore next */
function disableFixedAndStickyElementPointerEvents() {
Expand All @@ -186,7 +186,7 @@ function disableFixedAndStickyElementPointerEvents() {

/**
* @param {string} tapTargetsSelector
* @returns {LH.Artifacts.TapTarget[]}
* @return {LH.Artifacts.TapTarget[]}
*/
/* istanbul ignore next */
function gatherTapTargets(tapTargetsSelector) {
Expand Down Expand Up @@ -285,23 +285,25 @@ class TapTargets extends Gatherer {
* @return {Promise<LH.Artifacts.TapTarget[]>} All visible tap targets with their positions and sizes
*/
afterPass(passContext) {
const expression = `(function() {
${pageFunctions.getElementsInDocumentString};
${disableFixedAndStickyElementPointerEvents.toString()};
${elementIsVisible.toString()};
${elementHasAncestorTapTarget.toString()};
${elementCenterIsAtZAxisTop.toString()}
${getClientRects.toString()};
${hasTextNodeSiblingsFormingTextBlock.toString()};
${elementIsInTextBlock.toString()};
${RectHelpers.getRectCenterPoint.toString()};
${pageFunctions.getNodeDetailsString};
${gatherTapTargets.toString()};
return gatherTapTargets("${tapTargetsSelector}");
})()`;

return passContext.driver.evaluateAsync(expression, {useIsolation: true});
return passContext.driver.evaluate(gatherTapTargets, {
args: [tapTargetsSelector],
useIsolation: true,
deps: [
pageFunctions.getNodeDetailsString,
pageFunctions.getElementsInDocument,
disableFixedAndStickyElementPointerEvents,
elementIsVisible,
elementHasAncestorTapTarget,
elementCenterIsAtZAxisTop,
getClientRects,
hasTextNodeSiblingsFormingTextBlock,
elementIsInTextBlock,
RectHelpers.getRectCenterPoint,
pageFunctions.getNodePath,
pageFunctions.getNodeSelector,
pageFunctions.getNodeLabel,
],
});
}
}

Expand Down
42 changes: 40 additions & 2 deletions lighthouse-core/lib/page-functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,43 @@
// @ts-nocheck
'use strict';

/** @typedef {HTMLElementTagNameMap & {[id: string]: HTMLElement}} HTMLElementByTagName */

/* global window document Node ShadowRoot */

/**
* @typedef {any[]} TExtendsArray
*/

/**
* Creates valid JavaScript code given functions, strings of valid code, and arguments.
* @template {TExtendsArray} T, R
* @param {(...args: T) => R} mainFn The main function to call. It's return value will be the return value
* of `createEvalCode`, wrapped in a Promise.
* @param {{mode?: 'iife'|'function', args?: T, deps?: Array<Function|string>}} _ Set mode to `iife` to
* create a self-executing function expression, set to `function` to create just a function
* declaration statement. Args should match the args of `mainFn`, and can be any serializable
* value. `deps` are functions that must be defined for `mainFn` to work.
*/
function createEvalCode(mainFn, {mode, args, deps} = {}) {
const argsSerialized = args ? args.map(arg => JSON.stringify(arg)).join(',') : '';
const depsSerialized = deps ? deps.join('\n') : '';

if (!mode || mode === 'iife') {
return `(() => {
${depsSerialized}
${mainFn}
return ${mainFn.name}(${argsSerialized});
})()`;
} else {
return `function () {
${depsSerialized}
${mainFn}
return ${mainFn.name}.call(this, ${argsSerialized});
}`;
}
}

/**
* Helper functions that are passed by `toString()` by Driver to be evaluated in target page.
*/
Expand Down Expand Up @@ -78,9 +113,10 @@ function checkTimeSinceLastLongTask() {
}

/**
* @param {string=} selector Optional simple CSS selector to filter nodes on.
* @template {string} T
* @param {T} selector Optional simple CSS selector to filter nodes on.
* Combinators are not supported.
* @return {Array<HTMLElement>}
* @return {Array<HTMLElementByTagName[T]>}
*/
/* istanbul ignore next */
function getElementsInDocument(selector) {
Expand Down Expand Up @@ -463,9 +499,11 @@ const getNodeDetailsString = `function getNodeDetails(elem) {
}`;

module.exports = {
createEvalCode,
wrapRuntimeEvalErrorInBrowserString: wrapRuntimeEvalErrorInBrowser.toString(),
registerPerformanceObserverInPageString: registerPerformanceObserverInPage.toString(),
checkTimeSinceLastLongTaskString: checkTimeSinceLastLongTask.toString(),
getElementsInDocument,
getElementsInDocumentString: getElementsInDocument.toString(),
getOuterHTMLSnippetString: getOuterHTMLSnippet.toString(),
getOuterHTMLSnippet: getOuterHTMLSnippet,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ describe('Link Elements gatherer', () => {
function getPassData({linkElementsInDOM = [], headers = []}) {
const url = 'https://example.com';
const loadData = {networkRecords: [{url, responseHeaders: headers, resourceType: 'Document'}]};
const driver = {evaluateAsync: () => Promise.resolve(linkElementsInDOM)};
const driver = {evaluate: () => Promise.resolve(linkElementsInDOM)};
const passContext = {driver, url};
return [passContext, loadData];
}
Expand Down
Loading

0 comments on commit 6a355c4

Please sign in to comment.