Skip to content

Commit

Permalink
report: add Stack Packs and first pack for wordpress (GoogleChrome#7243)
Browse files Browse the repository at this point in the history
  • Loading branch information
wardpeet authored and brendankenny committed Apr 11, 2019
1 parent 45d6911 commit b374ea4
Show file tree
Hide file tree
Showing 29 changed files with 368 additions and 92 deletions.
3 changes: 0 additions & 3 deletions lighthouse-cli/test/cli/__snapshots__/index-test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -1108,9 +1108,6 @@ Object {
Object {
"path": "dobetterweb/domstats",
},
Object {
"path": "dobetterweb/js-libraries",
},
Object {
"path": "dobetterweb/optimized-images",
},
Expand Down
14 changes: 8 additions & 6 deletions lighthouse-core/audits/dobetterweb/js-libraries.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class JsLibrariesAudit extends Audit {
id: 'js-libraries',
title: 'Detected JavaScript libraries',
description: 'All front-end JavaScript libraries detected on the page.',
requiredArtifacts: ['JSLibraries'],
requiredArtifacts: ['Stacks'],
};
}

Expand All @@ -30,11 +30,13 @@ class JsLibrariesAudit extends Audit {
* @return {LH.Audit.Product}
*/
static audit(artifacts) {
const libDetails = artifacts.JSLibraries.map(lib => ({
name: lib.name,
version: lib.version || undefined, // null if not detected
npm: lib.npmPkgName || undefined, // ~70% of libs come with this field
}));
const libDetails = (artifacts.Stacks || [])
.filter(stack => stack.detector === 'js')
.map(stack => ({
name: stack.name,
version: stack.version || undefined, // null if not detected
npm: stack.npm || undefined, // ~70% of libs come with this field
}));

/** @type {LH.Audit.Details.Table['headings']} */
const headings = [
Expand Down
16 changes: 8 additions & 8 deletions lighthouse-core/audits/dobetterweb/no-vulnerable-libraries.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class NoVulnerableLibrariesAudit extends Audit {
description: 'Some third-party scripts may contain known security vulnerabilities ' +
'that are easily identified and exploited by attackers. ' +
'[Learn more](https://developers.google.com/web/tools/lighthouse/audits/vulnerabilities).',
requiredArtifacts: ['JSLibraries'],
requiredArtifacts: ['Stacks'],
};
}

Expand Down Expand Up @@ -78,27 +78,27 @@ class NoVulnerableLibrariesAudit extends Audit {

/**
* @param {string} normalizedVersion
* @param {{name: string, version: string, npmPkgName: string|undefined}} lib
* @param {{name: string, version: string, npm?: string}} lib
* @param {SnykDB} snykDB
* @return {Array<Vulnerability>}
*/
static getVulnerabilities(normalizedVersion, lib, snykDB) {
if (!lib.npmPkgName || !snykDB.npm[lib.npmPkgName]) {
if (!lib.npm || !snykDB.npm[lib.npm]) {
return [];
}

// Verify the version is well-formed first
try {
semver.satisfies(normalizedVersion, '*');
} catch (err) {
err.pkgName = lib.npmPkgName;
err.pkgName = lib.npm;
// Report the failure and skip this library if the version was ill-specified
Sentry.captureException(err, {level: 'warning'});
return [];
}

// Match the vulnerability candidates from snyk against the version we see in the page
const vulnCandidatesForLib = snykDB.npm[lib.npmPkgName];
const vulnCandidatesForLib = snykDB.npm[lib.npm];
const matchingVulns = vulnCandidatesForLib.filter(vulnCandidate => {
// Each snyk vulnerability comes with an array of semver ranges
// The page is vulnerable if any of the ranges match.
Expand Down Expand Up @@ -135,7 +135,7 @@ class NoVulnerableLibrariesAudit extends Audit {
* @return {LH.Audit.Product}
*/
static audit(artifacts) {
const foundLibraries = artifacts.JSLibraries;
const foundLibraries = (artifacts.Stacks || []).filter(stack => stack.detector === 'js');
const snykDB = NoVulnerableLibrariesAudit.snykDB;

if (!foundLibraries.length) {
Expand Down Expand Up @@ -163,15 +163,15 @@ class NoVulnerableLibrariesAudit extends Audit {
vulnCount,
detectedLib: {
text: lib.name + '@' + version,
url: `https://snyk.io/vuln/npm:${lib.npmPkgName}?lh=${version}&utm_source=lighthouse&utm_medium=ref&utm_campaign=audit`,
url: `https://snyk.io/vuln/npm:${lib.npm}?lh=${version}&utm_source=lighthouse&utm_medium=ref&utm_campaign=audit`,
type: 'link',
},
});
}

return {
name: lib.name,
npmPkgName: lib.npmPkgName,
npmPkgName: lib.npm,
version,
vulns,
highestSeverity,
Expand Down
1 change: 0 additions & 1 deletion lighthouse-core/config/default-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@ const defaultConfig = {
'dobetterweb/appcache',
'dobetterweb/doctype',
'dobetterweb/domstats',
'dobetterweb/js-libraries',
'dobetterweb/optimized-images',
'dobetterweb/password-inputs-with-prevented-paste',
'dobetterweb/response-compression',
Expand Down
3 changes: 3 additions & 0 deletions lighthouse-core/gather/gather-runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

const log = require('lighthouse-logger');
const manifestParser = require('../lib/manifest-parser.js');
const stacksGatherer = require('../lib/gatherer-stacks.js');
const LHError = require('../lib/lh-error.js');
const URL = require('../lib/url-shim.js');
const NetworkRecorder = require('../lib/network-recorder.js');
Expand Down Expand Up @@ -411,6 +412,7 @@ class GatherRunner {
NetworkUserAgent: '', // updated later
BenchmarkIndex: 0, // updated later
WebAppManifest: null, // updated later
Stacks: null, // updated later
traces: {},
devtoolsLogs: {},
settings: options.settings,
Expand Down Expand Up @@ -480,6 +482,7 @@ class GatherRunner {
await GatherRunner.pass(passContext, gathererResults);
if (isFirstPass) {
baseArtifacts.WebAppManifest = await GatherRunner.getWebAppManifest(passContext);
baseArtifacts.Stacks = await stacksGatherer(passContext);
}
const passData = await GatherRunner.afterPass(passContext, gathererResults);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* @license Copyright 2018 Google Inc. All Rights Reserved.
* @license Copyright 2019 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/
Expand All @@ -13,51 +13,72 @@

'use strict';

const Gatherer = require('../gatherer');
const fs = require('fs');
const libDetectorSource = fs.readFileSync(
require.resolve('js-library-detector/library/libraries.js'), 'utf8');
require.resolve('js-library-detector/library/libraries.js'),
'utf8'
);

/**
* @typedef JSLibrary
* @property {string} name
* @property {string} version
* @property {string} npm
* @property {string} iconName
*/

/**
* Obtains a list of detected JS libraries and their versions.
*/
/* istanbul ignore next */
function detectLibraries() {
/** @type {LH.Artifacts['JSLibraries']} */
/** @type {JSLibrary[]} */
const libraries = [];

// d41d8cd98f00b204e9800998ecf8427e_ is a consistent prefix used by the detect libraries
// see https://github.com/HTTPArchive/httparchive/issues/77#issuecomment-291320900
// @ts-ignore - injected libDetectorSource var
Object.entries(d41d8cd98f00b204e9800998ecf8427e_LibraryDetectorTests).forEach(async ([name, lib]) => { // eslint-disable-line max-len
try {
const result = await lib.test(window);
if (result) {
libraries.push({
name: name,
version: result.version,
npmPkgName: lib.npm,
});
}
} catch (e) {}
});
Object.entries(d41d8cd98f00b204e9800998ecf8427e_LibraryDetectorTests).forEach(
async ([name, lib]) => {
// eslint-disable-line max-len
try {
const result = await lib.test(window);
if (result) {
libraries.push({
name: name,
version: result.version,
npm: lib.npm,
iconName: lib.icon,
});
}
} catch (e) {}
}
);

return libraries;
}

class JSLibraries extends Gatherer {
/**
* @param {LH.Gatherer.PassContext} passContext
* @return {Promise<LH.Artifacts['JSLibraries']>}
*/
afterPass(passContext) {
const expression = `(function () {
${libDetectorSource};
return (${detectLibraries.toString()}());
})()`;

return passContext.driver.evaluateAsync(expression);
}
/**
* @param {LH.Gatherer.PassContext} passContext
* @return {Promise<LH.Artifacts['Stacks']>}
*/
async function getStacks(passContext) {
const expression = `(function () {
${libDetectorSource};
return (${detectLibraries.toString()}());
})()`;

const jsLibraries = /** @type {JSLibrary[]} */ (await passContext.driver.evaluateAsync(
expression
));

return jsLibraries.map(lib => ({
detector: /** @type {'js'} */ ('js'),
id: lib.npm || lib.iconName,
name: lib.name,
version: lib.version,
npm: lib.npm,
}));
}

module.exports = JSLibraries;
module.exports = getStacks;
52 changes: 52 additions & 0 deletions lighthouse-core/lib/stack-packs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* @license Copyright 2019 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/
'use strict';

const stackPacks = require('@lighthouse/stack-packs');

const stackPacksToInclude = [{
packId: 'wordpress',
requiredStacks: ['js:wordpress'],
}];

/**
* @param {LH.Artifacts} artifacts
* @return {Array<LH.Result.StackPack>}
*/
function getStackPacks(artifacts) {
/** @type {Array<LH.Result.StackPack>} */
const packs = [];

if (artifacts.Stacks) {
for (const pageStack of artifacts.Stacks) {
const stackPackToIncl = stackPacksToInclude.find(stackPackToIncl =>
stackPackToIncl.requiredStacks.includes(`${pageStack.detector}:${pageStack.id}`));
if (!stackPackToIncl) {
continue;
}

// Grab the full pack definition
const matchedPack = stackPacks.find(pack => pack.id === stackPackToIncl.packId);
if (!matchedPack) {
// we couldn't find a pack that's in our inclusion list, this is weird.
continue;
}

packs.push({
id: matchedPack.id,
title: matchedPack.title,
iconDataURL: matchedPack.iconDataURL,
descriptions: matchedPack.descriptions,
});
}
}

return packs;
}

module.exports = {
getStackPacks,
};
18 changes: 18 additions & 0 deletions lighthouse-core/report/html/renderer/category-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,24 @@ class CategoryRenderer {
this.dom.find('.lh-audit__description', auditEl)
.appendChild(this.dom.convertMarkdownLinkSnippets(audit.result.description));

if (audit.result.stackPacks) {
audit.result.stackPacks.forEach(pack => {
const packElm = this.dom.createElement('div');
packElm.classList.add('lh-audit__stackpack');

const packElmImg = this.dom.createElement('img');
packElmImg.classList.add('lh-audit__stackpack__img');
packElmImg.src = pack.iconDataURL;
packElmImg.alt = pack.title;
packElm.appendChild(packElmImg);

packElm.appendChild(this.dom.convertMarkdownLinkSnippets(pack.description));

this.dom.find('.lh-audit__stackpacks', auditEl)
.appendChild(packElm);
});
}

const header = /** @type {HTMLDetailsElement} */ (this.dom.find('details', auditEl));
if (audit.result.details) {
const elem = this.detailsRenderer.render(audit.result.details);
Expand Down
14 changes: 14 additions & 0 deletions lighthouse-core/report/html/renderer/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,20 @@ class Util {
category.auditRefs.forEach(auditMeta => {
const result = clone.audits[auditMeta.id];
auditMeta.result = result;

// attach the stackpacks to the auditRef object
if (clone.stackPacks) {
clone.stackPacks.forEach(pack => {
if (pack.descriptions[auditMeta.id]) {
auditMeta.result.stackPacks = auditMeta.result.stackPacks || [];
auditMeta.result.stackPacks.push({
title: pack.title,
iconDataURL: pack.iconDataURL,
description: pack.descriptions[auditMeta.id],
});
}
});
}
});
}

Expand Down
16 changes: 13 additions & 3 deletions lighthouse-core/report/html/report-styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@
color: #0c50c7;
}

.lh-audit__description {
.lh-audit__description, .lh-audit__stackpack {
--inner-audit-left-padding: calc(var(--text-indent) + var(--lh-audit-index-width) + 2 * var(--audit-item-gap));
--inner-audit-right-padding: calc(var(--text-indent) + 2px);
padding-left: var(--inner-audit-left-padding);
Expand All @@ -192,6 +192,16 @@
max-width: 70%;
}

.lh-audit__stackpack {
display: flex;
align-items: center;
}

.lh-audit__stackpack__img {
max-width: 50px;
margin-right: var(--default-padding)
}

/* Report header */

.report-icon {
Expand Down Expand Up @@ -276,10 +286,10 @@
}

.lh-category-header__description,
.lh-audit__description {
.lh-audit__description,
.lh-audit__stackpack {
color: var(--secondary-text-color);
}

.lh-category-header__description {
font-size: var(--body-font-size);
margin: calc(var(--default-padding) / 2) 0 var(--default-padding);
Expand Down
Loading

0 comments on commit b374ea4

Please sign in to comment.