CRC details renderer
This commit is contained in:
Родитель
74ae236a81
Коммит
66941e56e8
|
@ -21,6 +21,10 @@ brace-expansion@^1.1.7:
|
|||
balanced-match "^0.4.1"
|
||||
concat-map "0.0.1"
|
||||
|
||||
browser-stdout@1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f"
|
||||
|
||||
clang-format@^1.0.50:
|
||||
version "1.0.50"
|
||||
resolved "https://registry.yarnpkg.com/clang-format/-/clang-format-1.0.50.tgz#b40926fd5c8573f7d37ed074a32da9a370dbdbcf"
|
||||
|
@ -29,15 +33,35 @@ clang-format@^1.0.50:
|
|||
glob "^7.0.0"
|
||||
resolve "^1.1.6"
|
||||
|
||||
commander@2.9.0:
|
||||
version "2.9.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4"
|
||||
dependencies:
|
||||
graceful-readlink ">= 1.0.0"
|
||||
|
||||
concat-map@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||
|
||||
debug@2.6.0:
|
||||
version "2.6.0"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.0.tgz#bc596bcabe7617f11d9fa15361eded5608b8499b"
|
||||
dependencies:
|
||||
ms "0.7.2"
|
||||
|
||||
diff@3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9"
|
||||
|
||||
escape-string-regexp@1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
|
||||
|
||||
fs.realpath@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
||||
|
||||
glob@^7.0.0:
|
||||
glob@7.1.1, glob@^7.0.0:
|
||||
version "7.1.1"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8"
|
||||
dependencies:
|
||||
|
@ -48,6 +72,18 @@ glob@^7.0.0:
|
|||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
"graceful-readlink@>= 1.0.0":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
|
||||
|
||||
growl@1.9.2:
|
||||
version "1.9.2"
|
||||
resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f"
|
||||
|
||||
has-flag@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa"
|
||||
|
||||
inflight@^1.0.4:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
|
||||
|
@ -59,6 +95,57 @@ inherits@2:
|
|||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
||||
|
||||
json3@3.3.2:
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1"
|
||||
|
||||
lodash._baseassign@^3.0.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz#8c38a099500f215ad09e59f1722fd0c52bfe0a4e"
|
||||
dependencies:
|
||||
lodash._basecopy "^3.0.0"
|
||||
lodash.keys "^3.0.0"
|
||||
|
||||
lodash._basecopy@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36"
|
||||
|
||||
lodash._basecreate@^3.0.0:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz#1bc661614daa7fc311b7d03bf16806a0213cf821"
|
||||
|
||||
lodash._getnative@^3.0.0:
|
||||
version "3.9.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5"
|
||||
|
||||
lodash._isiterateecall@^3.0.0:
|
||||
version "3.0.9"
|
||||
resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c"
|
||||
|
||||
lodash.create@3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.create/-/lodash.create-3.1.1.tgz#d7f2849f0dbda7e04682bb8cd72ab022461debe7"
|
||||
dependencies:
|
||||
lodash._baseassign "^3.0.0"
|
||||
lodash._basecreate "^3.0.0"
|
||||
lodash._isiterateecall "^3.0.0"
|
||||
|
||||
lodash.isarguments@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a"
|
||||
|
||||
lodash.isarray@^3.0.0:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55"
|
||||
|
||||
lodash.keys@^3.0.0:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a"
|
||||
dependencies:
|
||||
lodash._getnative "^3.0.0"
|
||||
lodash.isarguments "^3.0.0"
|
||||
lodash.isarray "^3.0.0"
|
||||
|
||||
minimatch@^3.0.2:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
|
||||
|
@ -115,6 +202,12 @@ resolve@^1.1.6:
|
|||
dependencies:
|
||||
path-parse "^1.0.5"
|
||||
|
||||
supports-color@3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5"
|
||||
dependencies:
|
||||
has-flag "^1.0.0"
|
||||
|
||||
typescript@2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.2.1.tgz#4862b662b988a4c8ff691cc7969622d24db76ae9"
|
||||
|
|
|
@ -68,7 +68,7 @@ class OffscreenImages extends ByteEfficiencyAudit {
|
|||
* @return {?Object}
|
||||
*/
|
||||
static computeWaste(image, viewportDimensions) {
|
||||
const url = URL.getDisplayName(image.src, {preserveQuery: true});
|
||||
const url = URL.getURLDisplayName(image.src, {preserveQuery: true});
|
||||
const totalPixels = image.clientWidth * image.clientHeight;
|
||||
const visiblePixels = this.computeVisiblePixels(image.clientRect, viewportDimensions);
|
||||
// Treat images with 0 area as if they're offscreen. See https://github.com/GoogleChrome/lighthouse/issues/1914
|
||||
|
|
|
@ -65,7 +65,7 @@ class TotalByteWeight extends ByteEfficiencyAudit {
|
|||
if (record.scheme === 'data' || !record.finished) return;
|
||||
|
||||
const result = {
|
||||
url: URL.getDisplayName(record.url),
|
||||
url: URL.getURLDisplayName(record.url),
|
||||
totalBytes: record.transferSize,
|
||||
totalKb: this.bytesToKbString(record.transferSize),
|
||||
totalMs: this.bytesToMsString(record.transferSize, networkThroughput),
|
||||
|
|
|
@ -141,7 +141,7 @@ class UnusedCSSRules extends ByteEfficiencyAudit {
|
|||
const contentPreview = UnusedCSSRules.determineContentPreview(stylesheetInfo.content);
|
||||
url = '*inline*```' + contentPreview + '```';
|
||||
} else {
|
||||
url = URL.getDisplayName(url);
|
||||
url = URL.getURLDisplayName(url);
|
||||
}
|
||||
|
||||
// If we don't know for sure how many bytes this sheet used on the network,
|
||||
|
|
|
@ -80,7 +80,7 @@ class UsesOptimizedImages extends ByteEfficiencyAudit {
|
|||
return results;
|
||||
}
|
||||
|
||||
const url = URL.getDisplayName(image.url);
|
||||
const url = URL.getURLDisplayName(image.url);
|
||||
const webpSavings = UsesOptimizedImages.computeSavings(image, 'webp');
|
||||
|
||||
if (webpSavings.bytes > WEBP_ALREADY_OPTIMIZED_THRESHOLD_IN_BYTES) {
|
||||
|
@ -116,7 +116,7 @@ class UsesOptimizedImages extends ByteEfficiencyAudit {
|
|||
|
||||
let debugString;
|
||||
if (failedImages.length) {
|
||||
const urls = failedImages.map(image => URL.getDisplayName(image.url));
|
||||
const urls = failedImages.map(image => URL.getURLDisplayName(image.url));
|
||||
debugString = `Lighthouse was unable to decode some of your images: ${urls.join(', ')}`;
|
||||
}
|
||||
|
||||
|
|
|
@ -69,7 +69,7 @@ class ResponsesAreCompressed extends ByteEfficiencyAudit {
|
|||
}
|
||||
|
||||
// remove duplicates
|
||||
const url = URL.getDisplayName(record.url);
|
||||
const url = URL.getURLDisplayName(record.url);
|
||||
const isDuplicate = results.find(res => res.url === url &&
|
||||
res.totalBytes === record.resourceSize);
|
||||
if (isDuplicate) {
|
||||
|
|
|
@ -54,7 +54,7 @@ class UsesResponsiveImages extends ByteEfficiencyAudit {
|
|||
* @return {?Object}
|
||||
*/
|
||||
static computeWaste(image, DPR) {
|
||||
const url = URL.getDisplayName(image.src);
|
||||
const url = URL.getURLDisplayName(image.src);
|
||||
const actualPixels = image.naturalWidth * image.naturalHeight;
|
||||
const usedPixels = image.clientWidth * image.clientHeight * Math.pow(DPR, 2);
|
||||
const wastedRatio = 1 - (usedPixels / actualPixels);
|
||||
|
|
|
@ -124,6 +124,8 @@ class CriticalRequestChains extends Audit {
|
|||
walk(initialNavChildren, 0);
|
||||
}
|
||||
|
||||
const longestChain = CriticalRequestChains._getLongestChain(chains);
|
||||
|
||||
return {
|
||||
rawValue: chainCount <= this.meta.optimalValue,
|
||||
displayValue: chainCount,
|
||||
|
@ -132,8 +134,14 @@ class CriticalRequestChains extends Audit {
|
|||
formatter: Formatter.SUPPORTED_FORMATS.CRITICAL_REQUEST_CHAINS,
|
||||
value: {
|
||||
chains,
|
||||
longestChain: CriticalRequestChains._getLongestChain(chains)
|
||||
longestChain
|
||||
}
|
||||
},
|
||||
details: {
|
||||
type: 'criticalrequestchain',
|
||||
header: {type: 'text', text: 'View critical network waterfall:'},
|
||||
chains,
|
||||
longestChain
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -62,7 +62,7 @@ class Deprecations extends Audit {
|
|||
|
||||
const deprecationsV2 = entries.filter(log => log.entry.source === 'deprecation').map(log => {
|
||||
// CSS deprecations can have missing URLs and lineNumbers. See https://crbug.com/680832.
|
||||
const url = URL.isValid(log.entry.url) ? URL.getDisplayName(log.entry.url) : '';
|
||||
const url = URL.isValid(log.entry.url) ? URL.getURLDisplayName(log.entry.url) : '';
|
||||
return {
|
||||
type: 'code',
|
||||
text: log.entry.text,
|
||||
|
|
|
@ -77,7 +77,7 @@ class LinkBlockingFirstPaintAudit extends Audit {
|
|||
endTime = Math.max(item.endTime, endTime);
|
||||
|
||||
return {
|
||||
url: URL.getDisplayName(item.tag.url),
|
||||
url: URL.getURLDisplayName(item.tag.url),
|
||||
totalKb: `${Math.round(item.transferSize / 1024)} KB`,
|
||||
totalMs: `${Math.round((item.endTime - startTime) * 1000)}ms`
|
||||
};
|
||||
|
|
|
@ -68,7 +68,7 @@ class NoOldFlexboxAudit extends Audit {
|
|||
if (!URL.isValid(url) || url === pageUrl) {
|
||||
url = 'inline';
|
||||
} else {
|
||||
url = URL.getDisplayName(url);
|
||||
url = URL.getURLDisplayName(url);
|
||||
}
|
||||
|
||||
urlList.push({
|
||||
|
|
|
@ -58,7 +58,7 @@ class HTTPS extends Audit {
|
|||
return artifacts.requestNetworkRecords(devtoolsLogs).then(networkRecords => {
|
||||
const insecureRecords = networkRecords
|
||||
.filter(record => !HTTPS.isSecureRecord(record))
|
||||
.map(record => ({url: URL.getDisplayName(record.url, {preserveHost: true})}));
|
||||
.map(record => ({url: URL.getURLDisplayName(record.url, {preserveHost: true})}));
|
||||
|
||||
let displayValue = '';
|
||||
if (insecureRecords.length > 1) {
|
||||
|
|
|
@ -64,7 +64,7 @@ class TTFBMetric extends Audit {
|
|||
if (networkRecord) {
|
||||
const ttfb = TTFBMetric.caclulateTTFB(networkRecord);
|
||||
results.push({
|
||||
url: URL.getDisplayName(networkRecord._url),
|
||||
url: URL.getURLDisplayName(networkRecord._url),
|
||||
ttfb: `${Math.round(ttfb).toLocaleString()} ms`,
|
||||
rawTTFB: ttfb
|
||||
});
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
|
||||
/* global self */
|
||||
|
||||
const ELLIPSIS = '\u2026';
|
||||
const Util = require('../report/v2/renderer/util.js');
|
||||
|
||||
// TODO: Add back node require('url').URL parsing when bug is resolved:
|
||||
// https://github.com/GoogleChrome/lighthouse/issues/1186
|
||||
|
@ -77,63 +77,8 @@ URL.originsMatch = function originsMatch(urlA, urlB) {
|
|||
* @param {{numPathParts: number, preserveQuery: boolean, preserveHost: boolean}=} options
|
||||
* @return {string}
|
||||
*/
|
||||
URL.getDisplayName = function getDisplayName(url, options) {
|
||||
options = Object.assign({
|
||||
numPathParts: 2,
|
||||
preserveQuery: true,
|
||||
preserveHost: false,
|
||||
}, options);
|
||||
|
||||
const parsed = new URL(url);
|
||||
|
||||
let name;
|
||||
|
||||
if (parsed.protocol === 'about:' || parsed.protocol === 'data:') {
|
||||
// Handle 'about:*' and 'data:*' URLs specially since they have no path.
|
||||
name = parsed.href;
|
||||
} else {
|
||||
name = parsed.pathname;
|
||||
const parts = name.split('/').filter(part => part.length);
|
||||
if (options.numPathParts && parts.length > options.numPathParts) {
|
||||
name = ELLIPSIS + parts.slice(-1 * options.numPathParts).join('/');
|
||||
}
|
||||
|
||||
if (options.preserveHost) {
|
||||
name = `${parsed.host}/${name.replace(/^\//, '')}`;
|
||||
}
|
||||
if (options.preserveQuery) {
|
||||
name = `${name}${parsed.search}`;
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_LENGTH = 64;
|
||||
// Always elide hash
|
||||
name = name.replace(/([a-f0-9]{7})[a-f0-9]{13}[a-f0-9]*/g, `$1${ELLIPSIS}`);
|
||||
|
||||
// Elide query params first
|
||||
if (name.length > MAX_LENGTH && name.includes('?')) {
|
||||
// Try to leave the first query parameter intact
|
||||
name = name.replace(/\?([^=]*)(=)?.*/, `?$1$2${ELLIPSIS}`);
|
||||
|
||||
// Remove it all if it's still too long
|
||||
if (name.length > MAX_LENGTH) {
|
||||
name = name.replace(/\?.*/, `?${ELLIPSIS}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Elide too long names next
|
||||
if (name.length > MAX_LENGTH) {
|
||||
const dotIndex = name.lastIndexOf('.');
|
||||
if (dotIndex >= 0) {
|
||||
name = name.slice(0, MAX_LENGTH - 1 - (name.length - dotIndex)) +
|
||||
// Show file extension
|
||||
`${ELLIPSIS}${name.slice(dotIndex)}`;
|
||||
} else {
|
||||
name = name.slice(0, MAX_LENGTH - 1) + ELLIPSIS;
|
||||
}
|
||||
}
|
||||
|
||||
return name;
|
||||
URL.getURLDisplayName = function getURLDisplayName(url, options) {
|
||||
return Util.getURLDisplayName(new URL(url), options);
|
||||
};
|
||||
|
||||
// There is fancy URL rewriting logic for the chrome://settings page that we need to work around.
|
||||
|
|
|
@ -387,7 +387,7 @@ const handlebarHelpers = {
|
|||
*/
|
||||
parseURL: (resourceURL, opts) => {
|
||||
const parsedURL = {
|
||||
file: URL.getDisplayName(resourceURL),
|
||||
file: URL.getURLDisplayName(resourceURL),
|
||||
hostname: new URL(resourceURL).hostname
|
||||
};
|
||||
|
||||
|
|
|
@ -29,6 +29,8 @@ class CategoryRenderer {
|
|||
this._detailsRenderer = detailsRenderer;
|
||||
/** @private {!Document|!Element} */
|
||||
this._templateContext = this._dom.document();
|
||||
|
||||
this._detailsRenderer.setTemplateContext(this._templateContext);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -216,6 +218,7 @@ class CategoryRenderer {
|
|||
*/
|
||||
setTemplateContext(context) {
|
||||
this._templateContext = context;
|
||||
this._detailsRenderer.setTemplateContext(context);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,244 @@
|
|||
/**
|
||||
* Copyright 2017 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';
|
||||
|
||||
/**
|
||||
* @fileoverview This file contains helpers for constructing and rendering the
|
||||
* critical request chains network tree.
|
||||
*/
|
||||
|
||||
/* globals self Util */
|
||||
|
||||
class CriticalRequestChainRenderer {
|
||||
|
||||
/**
|
||||
* @param {!DOM} dom
|
||||
* @param {!Node} templateContext
|
||||
*/
|
||||
constructor(dom, templateContext) {
|
||||
/** @private {!DOM} */
|
||||
this._dom = dom;
|
||||
/** @private {!DocumentFragment} */
|
||||
this._tmpl = this._dom.cloneTemplate('#tmpl-lh-crc', templateContext);
|
||||
/** @private {!CriticalRequestChainRenderer.CRCDetailsJSON} */
|
||||
this._details; // eslint-disable-line no-unused-expressions
|
||||
}
|
||||
|
||||
/**
|
||||
* Create render context for critical-request-chain tree display.
|
||||
* @param {!Object<string, !CriticalRequestChainRenderer.CRCNode>} tree
|
||||
* @return {{tree: !Object<string, !CriticalRequestChainRenderer.CRCNode>, startTime: number, transferSize: number}}
|
||||
*/
|
||||
initTree(tree) {
|
||||
let startTime = 0;
|
||||
const rootNodes = Object.keys(tree);
|
||||
if (rootNodes.length > 0) {
|
||||
const node = tree[rootNodes[0]];
|
||||
startTime = node.request.startTime;
|
||||
}
|
||||
|
||||
return {tree, startTime, transferSize: 0};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to create context for each critical-request-chain node based on its
|
||||
* parent. Calculates if this node is the last child, whether it has any
|
||||
* children itself and what the tree looks like all the way back up to the root,
|
||||
* so the tree markers can be drawn correctly.
|
||||
* @param {!Object<string, !CriticalRequestChainRenderer.CRCNode>} parent
|
||||
* @param {string} id
|
||||
* @param {number} startTime
|
||||
* @param {number} transferSize
|
||||
* @param {!Array<boolean>=} treeMarkers
|
||||
* @param {boolean=} parentIsLastChild
|
||||
* @return {!CriticalRequestChainRenderer.CRCSegment}
|
||||
*/
|
||||
createSegment(parent, id, startTime, transferSize, treeMarkers, parentIsLastChild) {
|
||||
const node = parent[id];
|
||||
const siblings = Object.keys(parent);
|
||||
const isLastChild = siblings.indexOf(id) === (siblings.length - 1);
|
||||
const hasChildren = Object.keys(node.children).length > 0;
|
||||
|
||||
// Copy the tree markers so that we don't change by reference.
|
||||
const newTreeMarkers = Array.isArray(treeMarkers) ? treeMarkers.slice(0) : [];
|
||||
|
||||
// Add on the new entry.
|
||||
if (typeof parentIsLastChild !== 'undefined') {
|
||||
newTreeMarkers.push(!parentIsLastChild);
|
||||
}
|
||||
|
||||
return {
|
||||
node,
|
||||
isLastChild,
|
||||
hasChildren,
|
||||
startTime,
|
||||
transferSize: transferSize + node.request.transferSize,
|
||||
treeMarkers: newTreeMarkers
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the DOM for a tree segment.
|
||||
* @param {!CriticalRequestChainRenderer.CRCSegment} segment
|
||||
* @return {!Node}
|
||||
*/
|
||||
createChainNode(segment) {
|
||||
const chainsEl = this._dom.cloneTemplate('#tmpl-lh-crc__chains', this._tmpl);
|
||||
|
||||
// Hovering over request shows full URL.
|
||||
this._dom.find('.crc-node', chainsEl).setAttribute('title', segment.node.request.url);
|
||||
|
||||
const treeMarkeEl = this._dom.find('.crc-node__tree-marker', chainsEl);
|
||||
|
||||
// Construct lines and add spacers for sub requests.
|
||||
segment.treeMarkers.forEach(separator => {
|
||||
if (separator) {
|
||||
treeMarkeEl.appendChild(this._dom.createElement('span', 'tree-marker vert'));
|
||||
treeMarkeEl.appendChild(this._dom.createElement('span', 'tree-marker'));
|
||||
} else {
|
||||
treeMarkeEl.appendChild(this._dom.createElement('span', 'tree-marker'));
|
||||
treeMarkeEl.appendChild(this._dom.createElement('span', 'tree-marker'));
|
||||
}
|
||||
});
|
||||
|
||||
if (segment.isLastChild) {
|
||||
treeMarkeEl.appendChild(this._dom.createElement('span', 'tree-marker up-right'));
|
||||
treeMarkeEl.appendChild(this._dom.createElement('span', 'tree-marker right'));
|
||||
} else {
|
||||
treeMarkeEl.appendChild(this._dom.createElement('span', 'tree-marker vert-right'));
|
||||
treeMarkeEl.appendChild(this._dom.createElement('span', 'tree-marker right'));
|
||||
}
|
||||
|
||||
if (segment.hasChildren) {
|
||||
treeMarkeEl.appendChild(this._dom.createElement('span', 'tree-marker horiz-down'));
|
||||
} else {
|
||||
treeMarkeEl.appendChild(this._dom.createElement('span', 'tree-marker right'));
|
||||
}
|
||||
|
||||
// Fill in url, host, and request size information.
|
||||
const {file, hostname} = Util.parseURL(segment.node.request.url);
|
||||
const treevalEl = this._dom.find('.crc-node__tree-value', chainsEl);
|
||||
this._dom.find('.crc-node__tree-file', treevalEl).textContent = `${file}`;
|
||||
this._dom.find('.crc-node__tree-hostname', treevalEl).textContent = `(${hostname})`;
|
||||
|
||||
if (!segment.hasChildren) {
|
||||
const span = this._dom.createElement('span', 'crc-node__chain-duration');
|
||||
span.textContent = ' - ' + Util.chainDuration(
|
||||
segment.node.request.startTime, segment.node.request.endTime) + 'ms, ';
|
||||
const span2 = this._dom.createElement('span', 'crc-node__chain-duration');
|
||||
span2.textContent = Util.formateBytesToKB(this._details.longestChain.transferSize) + 'KB';
|
||||
|
||||
treevalEl.appendChild(span);
|
||||
treevalEl.appendChild(span2);
|
||||
}
|
||||
|
||||
return chainsEl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively builds a tree from segments.
|
||||
* @param {!CriticalRequestChainRenderer.CRCSegment} segment
|
||||
* @param {!Element} detailsEl Parent details element.
|
||||
*/
|
||||
buildTree(segment, detailsEl) {
|
||||
detailsEl.appendChild(this.createChainNode(segment));
|
||||
|
||||
for (const key of Object.keys(segment.node.children)) {
|
||||
const childSegment = this.createSegment(segment.node.children, key,
|
||||
segment.startTime, segment.transferSize, segment.treeMarkers, segment.isLastChild);
|
||||
this.buildTree(childSegment, detailsEl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!CriticalRequestChainRenderer.CRCDetailsJSON} details
|
||||
* @return {!Node}
|
||||
*/
|
||||
render(details) {
|
||||
this._details = details;
|
||||
|
||||
// Fill in top summary.
|
||||
this._dom.find('.lh-crc__longest_duration', this._tmpl).textContent =
|
||||
Util.formatNumber(details.longestChain.duration) + 'ms';
|
||||
this._dom.find('.lh-crc__longest_length', this._tmpl).textContent = details.longestChain.length;
|
||||
this._dom.find('.lh-crc__longest_transfersize', this._tmpl).textContent =
|
||||
Util.formateBytesToKB(details.longestChain.transferSize) + 'KB';
|
||||
|
||||
const detailsEl = this._dom.find('.lh-details', this._tmpl);
|
||||
|
||||
this._dom.find('.lh-details > summary', this._tmpl).textContent = details.header.text;
|
||||
|
||||
// Construct visual tree.
|
||||
const root = this.initTree(details.chains);
|
||||
for (const key of Object.keys(root.tree)) {
|
||||
const segment = this.createSegment(root.tree, key, root.startTime, root.transferSize);
|
||||
this.buildTree(segment, detailsEl);
|
||||
}
|
||||
|
||||
return this._tmpl;
|
||||
}
|
||||
}
|
||||
|
||||
// Allow Node require()'ing.
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = CriticalRequestChainRenderer;
|
||||
} else {
|
||||
self.CriticalRequestChainRenderer = CriticalRequestChainRenderer;
|
||||
}
|
||||
|
||||
/** @typedef {{
|
||||
* type: string,
|
||||
* header: ({text: string}|undefined),
|
||||
* longestChain: {duration: number, length: number, transferSize: number},
|
||||
* chains: !Object<string, !CriticalRequestChainRenderer.CRCNode>
|
||||
* }}
|
||||
*/
|
||||
CriticalRequestChainRenderer.CRCDetailsJSON; // eslint-disable-line no-unused-expressions
|
||||
|
||||
/** @typedef {{
|
||||
* endTime: number,
|
||||
* responseReceivedTime: number,
|
||||
* startTime: number,
|
||||
* transferSize: number,
|
||||
* url: string
|
||||
* }}
|
||||
*/
|
||||
CriticalRequestChainRenderer.CRCRequest; // eslint-disable-line no-unused-expressions
|
||||
|
||||
/**
|
||||
* Record type so children can circularly have CRCNode values.
|
||||
* @struct
|
||||
* @record
|
||||
*/
|
||||
CriticalRequestChainRenderer.CRCNode = function() {};
|
||||
|
||||
/** @type {!Object<string, !CriticalRequestChainRenderer.CRCNode>} */
|
||||
CriticalRequestChainRenderer.CRCNode.prototype.children; // eslint-disable-line no-unused-expressions
|
||||
|
||||
/** @type {!CriticalRequestChainRenderer.CRCRequest} */
|
||||
CriticalRequestChainRenderer.CRCNode.prototype.request; // eslint-disable-line no-unused-expressions
|
||||
|
||||
/** @typedef {{
|
||||
* node: !CriticalRequestChainRenderer.CRCNode,
|
||||
* isLastChild: boolean,
|
||||
* hasChildren: boolean,
|
||||
* startTime: number,
|
||||
* transferSize: number,
|
||||
* treeMarkers: !Array<boolean>
|
||||
* }}
|
||||
*/
|
||||
CriticalRequestChainRenderer.CRCSegment; // eslint-disable-line no-unused-expressions
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
'use strict';
|
||||
|
||||
/* globals self */
|
||||
/* globals self CriticalRequestChainRenderer */
|
||||
|
||||
class DetailsRenderer {
|
||||
/**
|
||||
|
@ -24,11 +24,20 @@ class DetailsRenderer {
|
|||
constructor(dom) {
|
||||
/** @private {!DOM} */
|
||||
this._dom = dom;
|
||||
/** @private {!Document|!Element} */
|
||||
this._templateContext; // eslint-disable-line no-unused-expressions
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Document|!Element} context
|
||||
*/
|
||||
setTemplateContext(context) {
|
||||
this._templateContext = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!DetailsRenderer.DetailsJSON} details
|
||||
* @return {!Element}
|
||||
* @return {!Node}
|
||||
*/
|
||||
render(details) {
|
||||
switch (details.type) {
|
||||
|
@ -46,6 +55,10 @@ class DetailsRenderer {
|
|||
return this._renderCode(details);
|
||||
case 'node':
|
||||
return this.renderNode(/** @type {!DetailsRenderer.NodeDetailsJSON} */(details));
|
||||
case 'criticalrequestchain': // eslint-disable-line no-case-declarations
|
||||
const crcRenderer = new CriticalRequestChainRenderer(this._dom, this._templateContext);
|
||||
return crcRenderer.render(
|
||||
/** @type {!CriticalRequestChainRenderer.CRCDetailsJSON} */ (details));
|
||||
case 'list':
|
||||
return this._renderList(/** @type {!DetailsRenderer.ListDetailsJSON} */ (details));
|
||||
default:
|
||||
|
@ -111,7 +124,6 @@ class DetailsRenderer {
|
|||
return element;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {!DetailsRenderer.TableDetailsJSON} details
|
||||
* @return {!Element}
|
||||
|
@ -255,7 +267,6 @@ DetailsRenderer.NodeDetailsJSON; // eslint-disable-line no-unused-expressions
|
|||
*/
|
||||
DetailsRenderer.TableDetailsJSON; // eslint-disable-line no-unused-expressions
|
||||
|
||||
|
||||
/** @typedef {{
|
||||
* type: string,
|
||||
* url: ({text: string}|undefined),
|
||||
|
|
|
@ -15,7 +15,9 @@
|
|||
*/
|
||||
'use strict';
|
||||
|
||||
/* globals self */
|
||||
/* globals self URL */
|
||||
|
||||
const ELLIPSIS = '\u2026';
|
||||
|
||||
const RATINGS = {
|
||||
PASS: {label: 'pass', minScore: 75},
|
||||
|
@ -48,6 +50,15 @@ class Util {
|
|||
return number.toLocaleString(undefined, {maximumFractionDigits: 1});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} size
|
||||
* @param {number=} decimalPlaces Number of decimal places to include. Defaults to 2.
|
||||
* @return {string}
|
||||
*/
|
||||
static formateBytesToKB(size, decimalPlaces = 2) {
|
||||
return (size / 1024).toLocaleString(undefined, {maximumFractionDigits: decimalPlaces});
|
||||
}
|
||||
|
||||
/**
|
||||
* Format time.
|
||||
* @param {string} date
|
||||
|
@ -69,6 +80,82 @@ class Util {
|
|||
}
|
||||
return formatter.format(new Date(date));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!URL} parsedUrl
|
||||
* @param {{numPathParts: (number|undefined), preserveQuery: (boolean|undefined), preserveHost: (boolean|undefined)}=} options
|
||||
* @return {string}
|
||||
*/
|
||||
static getURLDisplayName(parsedUrl,
|
||||
{numPathParts = 2, preserveQuery = true, preserveHost = false} = {}) {
|
||||
let name;
|
||||
|
||||
if (parsedUrl.protocol === 'about:' || parsedUrl.protocol === 'data:') {
|
||||
// Handle 'about:*' and 'data:*' URLs specially since they have no path.
|
||||
name = parsedUrl.href;
|
||||
} else {
|
||||
name = parsedUrl.pathname;
|
||||
const parts = name.split('/').filter(part => part.length);
|
||||
if (numPathParts && parts.length > numPathParts) {
|
||||
name = ELLIPSIS + parts.slice(-1 * numPathParts).join('/');
|
||||
}
|
||||
|
||||
if (preserveHost) {
|
||||
name = `${parsedUrl.host}/${name.replace(/^\//, '')}`;
|
||||
}
|
||||
if (preserveQuery) {
|
||||
name = `${name}${parsedUrl.search}`;
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_LENGTH = 64;
|
||||
// Always elide hash
|
||||
name = name.replace(/([a-f0-9]{7})[a-f0-9]{13}[a-f0-9]*/g, `$1${ELLIPSIS}`);
|
||||
|
||||
// Elide query params first
|
||||
if (name.length > MAX_LENGTH && name.includes('?')) {
|
||||
// Try to leave the first query parameter intact
|
||||
name = name.replace(/\?([^=]*)(=)?.*/, `?$1$2${ELLIPSIS}`);
|
||||
|
||||
// Remove it all if it's still too long
|
||||
if (name.length > MAX_LENGTH) {
|
||||
name = name.replace(/\?.*/, `?${ELLIPSIS}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Elide too long names next
|
||||
if (name.length > MAX_LENGTH) {
|
||||
const dotIndex = name.lastIndexOf('.');
|
||||
if (dotIndex >= 0) {
|
||||
name = name.slice(0, MAX_LENGTH - 1 - (name.length - dotIndex)) +
|
||||
// Show file extension
|
||||
`${ELLIPSIS}${name.slice(dotIndex)}`;
|
||||
} else {
|
||||
name = name.slice(0, MAX_LENGTH - 1) + ELLIPSIS;
|
||||
}
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Split a URL into a file and hostname for easy display.
|
||||
* @param {string} url
|
||||
* @return {{file: string, hostname: string}}
|
||||
*/
|
||||
static parseURL(url) {
|
||||
const parsedUrl = new URL(url);
|
||||
return {file: Util.getURLDisplayName(parsedUrl), hostname: parsedUrl.hostname};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} startTime
|
||||
* @param {number} endTime
|
||||
* @return {string}
|
||||
*/
|
||||
static chainDuration(startTime, endTime) {
|
||||
return Util.formatNumber((endTime - startTime) * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
|
|
|
@ -23,6 +23,7 @@ const REPORT_JAVASCRIPT = [
|
|||
fs.readFileSync(__dirname + '/renderer/util.js', 'utf8'),
|
||||
fs.readFileSync(__dirname + '/renderer/dom.js', 'utf8'),
|
||||
fs.readFileSync(__dirname + '/renderer/details-renderer.js', 'utf8'),
|
||||
fs.readFileSync(__dirname + '/renderer/crc-details-renderer.js', 'utf8'),
|
||||
fs.readFileSync(__dirname + '/../../lib/file-namer.js', 'utf8'),
|
||||
fs.readFileSync(__dirname + '/renderer/logger.js', 'utf8'),
|
||||
fs.readFileSync(__dirname + '/renderer/report-ui-features.js', 'utf8'),
|
||||
|
|
|
@ -435,3 +435,78 @@
|
|||
<div class="lh-gauge__label"><!-- fill me --></div>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<!-- Lighthouse crtiical request chains component -->
|
||||
<template id="tmpl-lh-crc">
|
||||
<style>
|
||||
.lh-crc .tree-marker {
|
||||
width: 12px;
|
||||
height: 26px;
|
||||
display: block;
|
||||
float: left;
|
||||
background-position: top left;
|
||||
}
|
||||
.lh-crc .horiz-down {
|
||||
background: url('data:image/svg+xml;utf8,<svg width="16" height="26" viewBox="0 0 16 26" xmlns="http://www.w3.org/2000/svg"><g fill="#D8D8D8" fill-rule="evenodd"><path d="M16 12v2H-2v-2z"/><path d="M9 12v14H7V12z"/></g></svg>');
|
||||
}
|
||||
.lh-crc .right {
|
||||
background: url('data:image/svg+xml;utf8,<svg width="16" height="26" viewBox="0 0 16 26" xmlns="http://www.w3.org/2000/svg"><path d="M16 12v2H0v-2z" fill="#D8D8D8" fill-rule="evenodd"/></svg>');
|
||||
}
|
||||
.lh-crc .up-right {
|
||||
background: url('data:image/svg+xml;utf8,<svg width="16" height="26" viewBox="0 0 16 26" xmlns="http://www.w3.org/2000/svg"><path d="M7 0h2v14H7zm2 12h7v2H9z" fill="#D8D8D8" fill-rule="evenodd"/></svg>');
|
||||
}
|
||||
.lh-crc .vert-right {
|
||||
background: url('data:image/svg+xml;utf8,<svg width="16" height="26" viewBox="0 0 16 26" xmlns="http://www.w3.org/2000/svg"><path d="M7 0h2v27H7zm2 12h7v2H9z" fill="#D8D8D8" fill-rule="evenodd"/></svg>');
|
||||
}
|
||||
.lh-crc .vert {
|
||||
background: url('data:image/svg+xml;utf8,<svg width="16" height="26" viewBox="0 0 16 26" xmlns="http://www.w3.org/2000/svg"><path d="M7 0h2v26H7z" fill="#D8D8D8" fill-rule="evenodd"/></svg>');
|
||||
}
|
||||
.lh-crc .crc-tree {
|
||||
font-size: 14px;
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.lh-crc .crc-node {
|
||||
height: 26px;
|
||||
line-height: 26px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.lh-crc .crc-node__tree-value {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.lh-crc .crc-node__chain-duration {
|
||||
font-weight: 700;
|
||||
}
|
||||
.lh-crc .crc-node__tree-hostname {
|
||||
color: #595959;
|
||||
}
|
||||
.lh-crc .crc-initial-nav {
|
||||
color: #595959;
|
||||
font-style: italic;
|
||||
}
|
||||
</style>
|
||||
<div class="lh-score__description">
|
||||
Longest chain: <b class="lh-crc__longest_duration"><!-- fill me: longestChain.duration --></b>
|
||||
over <b class="lh-crc__longest_length"><!-- fill me: longestChain.length --></b> requests, totalling
|
||||
<b class="lh-crc__longest_transfersize"><!-- fill me: longestChain.length --></b>
|
||||
</div>
|
||||
<div class="lh-crc">
|
||||
<details class="lh-details">
|
||||
<summary><!-- fill me --></summary>
|
||||
<div class="crc-initial-nav">Initial Navigation</div>
|
||||
<!-- stamp for each chain -->
|
||||
<template id="tmpl-lh-crc__chains">
|
||||
<div class="crc-node">
|
||||
<span class="crc-node__tree-marker">
|
||||
<!-- fill me -->
|
||||
</span>
|
||||
<span class="crc-node__tree-value">
|
||||
<span class="crc-node__tree-file"><!-- fill me: node.request.url.file --></span>
|
||||
<span class="crc-node__tree-hostname">(<!-- fill me: node.request.url.host -->)</span>
|
||||
<!-- fill me -->
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</details>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
# https://github.com/ChromeDevTools/devtools-frontend/blob/master/front_end/audits2/module.json#L20
|
||||
|
||||
# (Currently this doesnt include logger or report-features)
|
||||
files_to_include="\"lighthouse\/renderer\/util.js\", \"lighthouse\/renderer\/dom.js\", \"lighthouse\/renderer\/category-renderer.js\", \"lighthouse\/renderer\/details-renderer.js\", \"lighthouse\/renderer\/report-renderer.js\","
|
||||
files_to_include="\"lighthouse\/renderer\/util.js\", \"lighthouse\/renderer\/dom.js\", \"lighthouse\/renderer\/category-renderer.js\", \"lighthouse\/renderer\/crc-details-renderer.js\", \"lighthouse\/renderer\/details-renderer.js\", \"lighthouse\/renderer\/report-renderer.js\","
|
||||
|
||||
# -----------------------------
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
*/
|
||||
'use strict';
|
||||
|
||||
/* eslint-env mocha */
|
||||
|
||||
const Audit = require('../../audits/critical-request-chains.js');
|
||||
const assert = require('assert');
|
||||
|
||||
|
@ -91,17 +93,18 @@ const mockArtifacts = (mockChain) => {
|
|||
};
|
||||
};
|
||||
|
||||
/* eslint-env mocha */
|
||||
describe('Performance: critical-request-chains audit', () => {
|
||||
it('calculates the correct chain result for failing example', () => {
|
||||
return Audit.audit(mockArtifacts(FAILING_REQUEST_CHAIN)).then(output => {
|
||||
assert.equal(output.displayValue, 2);
|
||||
assert.equal(output.rawValue, false);
|
||||
assert.ok(output.details);
|
||||
});
|
||||
});
|
||||
|
||||
it('calculates the correct chain result for passing example', () => {
|
||||
return Audit.audit(mockArtifacts(PASSING_REQUEST_CHAIN)).then(output => {
|
||||
assert.equal(output.details.longestChain.duration, 1000);
|
||||
assert.equal(output.displayValue, 0);
|
||||
assert.equal(output.rawValue, true);
|
||||
});
|
||||
|
|
|
@ -82,51 +82,51 @@ describe('URL Shim', () => {
|
|||
assert.equal(URL.originsMatch(urlA, urlB), false);
|
||||
});
|
||||
|
||||
describe('getDisplayName', () => {
|
||||
describe('getURLDisplayName', () => {
|
||||
it('respects numPathParts option', () => {
|
||||
const url = 'http://example.com/a/deep/nested/file.css';
|
||||
const result = URL.getDisplayName(url, {numPathParts: 3});
|
||||
const result = URL.getURLDisplayName(url, {numPathParts: 3});
|
||||
assert.equal(result, '\u2026deep/nested/file.css');
|
||||
});
|
||||
|
||||
it('respects preserveQuery option', () => {
|
||||
const url = 'http://example.com/file.css?aQueryString=true';
|
||||
const result = URL.getDisplayName(url, {preserveQuery: false});
|
||||
const result = URL.getURLDisplayName(url, {preserveQuery: false});
|
||||
assert.equal(result, '/file.css');
|
||||
});
|
||||
|
||||
it('respects preserveHost option', () => {
|
||||
const url = 'http://example.com/file.css';
|
||||
const result = URL.getDisplayName(url, {preserveHost: true});
|
||||
const result = URL.getURLDisplayName(url, {preserveHost: true});
|
||||
assert.equal(result, 'example.com/file.css');
|
||||
});
|
||||
|
||||
it('Elides hashes', () => {
|
||||
const url = 'http://example.com/file-f303dec6eec305a4fab8025577db3c2feb418148ac75ba378281399fb1ba670b.css';
|
||||
const result = URL.getDisplayName(url);
|
||||
const result = URL.getURLDisplayName(url);
|
||||
assert.equal(result, '/file-f303dec\u2026.css');
|
||||
});
|
||||
|
||||
it('Elides hashes in the middle', () => {
|
||||
const url = 'http://example.com/file-f303dec6eec305a4fab80378281399fb1ba670b-somethingmore.css';
|
||||
const result = URL.getDisplayName(url);
|
||||
const result = URL.getURLDisplayName(url);
|
||||
assert.equal(result, '/file-f303dec\u2026-somethingmore.css');
|
||||
});
|
||||
|
||||
it('Elides query strings when can first parameter', () => {
|
||||
const url = 'http://example.com/file.css?aQueryString=true&other_long_query_stuff=false&some_other_super_long_query';
|
||||
const result = URL.getDisplayName(url);
|
||||
const result = URL.getURLDisplayName(url);
|
||||
assert.equal(result, '/file.css?aQueryString=\u2026');
|
||||
});
|
||||
|
||||
it('Elides query strings when cannot preserve first parameter', () => {
|
||||
const url = 'http://example.com/file.css?superDuperNoGoodVeryLongExtraSpecialOnlyTheBestEnourmousQueryString=true';
|
||||
const result = URL.getDisplayName(url);
|
||||
const result = URL.getURLDisplayName(url);
|
||||
assert.equal(result, '/file.css?\u2026');
|
||||
});
|
||||
|
||||
it('Elides long names', () => {
|
||||
const result = URL.getDisplayName(superLongName);
|
||||
const result = URL.getURLDisplayName(superLongName);
|
||||
const expected = '/thisIsASuperLongURLThatWillTriggerFilenameTruncationWhichWe\u2026.js';
|
||||
assert.equal(result, expected);
|
||||
});
|
||||
|
@ -134,20 +134,20 @@ describe('URL Shim', () => {
|
|||
it('Elides long names with hash', () => {
|
||||
const url = superLongName.slice(0, -3) +
|
||||
'-f303dec6eec305a4fab8025577db3c2feb418148ac75ba378281399fb1ba670b.css';
|
||||
const result = URL.getDisplayName(url);
|
||||
const result = URL.getURLDisplayName(url);
|
||||
const expected = '/thisIsASuperLongURLThatWillTriggerFilenameTruncationWhichW\u2026.css';
|
||||
assert.equal(result, expected);
|
||||
});
|
||||
|
||||
it('Elides path parts properly', () => {
|
||||
assert.equal(URL.getDisplayName('http://example.com/file.css'), '/file.css');
|
||||
assert.equal(URL.getDisplayName('http://t.co//file.css'), '//file.css');
|
||||
assert.equal(URL.getDisplayName('http://t.co/a/file.css'), '/a/file.css');
|
||||
assert.equal(URL.getDisplayName('http://t.co/a/b/file.css'), '\u2026b/file.css');
|
||||
assert.equal(URL.getURLDisplayName('http://example.com/file.css'), '/file.css');
|
||||
assert.equal(URL.getURLDisplayName('http://t.co//file.css'), '//file.css');
|
||||
assert.equal(URL.getURLDisplayName('http://t.co/a/file.css'), '/a/file.css');
|
||||
assert.equal(URL.getURLDisplayName('http://t.co/a/b/file.css'), '\u2026b/file.css');
|
||||
});
|
||||
|
||||
it('Elides path parts properly when used with preserveHost', () => {
|
||||
const getResult = path => URL.getDisplayName(`http://g.co${path}`, {preserveHost: true});
|
||||
const getResult = path => URL.getURLDisplayName(`http://g.co${path}`, {preserveHost: true});
|
||||
assert.equal(getResult('/file.css'), 'g.co/file.css');
|
||||
assert.equal(getResult('/img/logo.jpg'), 'g.co/img/logo.jpg');
|
||||
assert.equal(getResult('//logo.jpg'), 'g.co//logo.jpg');
|
||||
|
|
|
@ -88,7 +88,7 @@ describe('CRC partial generation', () => {
|
|||
Handlebars.registerHelper(handlebarHelpers);
|
||||
const template = Handlebars.compile(partialHtml);
|
||||
const output = template(extendedInfo).split('\n').join('');
|
||||
const filename = URL.getDisplayName(superLongName);
|
||||
const filename = URL.getURLDisplayName(superLongName);
|
||||
const filenameRegExp = new RegExp(filename, 'im');
|
||||
const expectedTreeOne = new RegExp([
|
||||
'<div class="cnc-node" title="https://example.com/">',
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
/**
|
||||
* Copyright 2017 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';
|
||||
|
||||
/* eslint-env mocha */
|
||||
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const jsdom = require('jsdom');
|
||||
const URL = require('../../../../lib/url-shim');
|
||||
const Util = require('../../../../report/v2/renderer/util.js');
|
||||
const DOM = require('../../../../report/v2/renderer/dom.js');
|
||||
const CriticalRequestChainRenderer =
|
||||
require('../../../../report/v2/renderer/crc-details-renderer.js');
|
||||
|
||||
const TEMPLATE_FILE = fs.readFileSync(__dirname + '/../../../../report/v2/templates.html', 'utf8');
|
||||
|
||||
const superLongURL =
|
||||
'https://example.com/thisIsASuperLongURLThatWillTriggerFilenameTruncationWhichWeWantToTest.js';
|
||||
const DETAILS = {
|
||||
type: 'criticalrequestchain',
|
||||
header: {type: 'text', text: 'CRC Header'},
|
||||
chains: {
|
||||
0: {
|
||||
request: {
|
||||
endTime: 1,
|
||||
responseReceivedTime: 5,
|
||||
startTime: 0,
|
||||
url: 'https://example.com/',
|
||||
transferSize: 1
|
||||
},
|
||||
children: {
|
||||
1: {
|
||||
request: {
|
||||
endTime: 16,
|
||||
responseReceivedTime: 14,
|
||||
startTime: 11,
|
||||
url: 'https://example.com/b.js',
|
||||
transferSize: 1
|
||||
},
|
||||
children: {}
|
||||
},
|
||||
2: {
|
||||
request: {
|
||||
endTime: 17.123456789,
|
||||
responseReceivedTime: 15,
|
||||
startTime: 12,
|
||||
url: superLongURL,
|
||||
transferSize: 1
|
||||
},
|
||||
children: {}
|
||||
},
|
||||
3: {
|
||||
request: {
|
||||
endTime: 18,
|
||||
responseReceivedTime: 16,
|
||||
startTime: 13,
|
||||
url: 'about:blank',
|
||||
transferSize: 1
|
||||
},
|
||||
children: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
longestChain: {
|
||||
duration: 7000,
|
||||
length: 2,
|
||||
transferSize: 1
|
||||
}
|
||||
};
|
||||
|
||||
describe('DetailsRenderer', () => {
|
||||
let renderer;
|
||||
let dom;
|
||||
|
||||
before(() => {
|
||||
global.URL = URL;
|
||||
global.Util = Util;
|
||||
const document = jsdom.jsdom(TEMPLATE_FILE);
|
||||
dom = new DOM(document);
|
||||
renderer = new CriticalRequestChainRenderer(dom, dom.document());
|
||||
});
|
||||
|
||||
after(() => {
|
||||
global.URL = undefined;
|
||||
global.Util = undefined;
|
||||
});
|
||||
|
||||
it('renders tree structure', () => {
|
||||
const el = renderer.render(DETAILS);
|
||||
const details = el.querySelector('.lh-details');
|
||||
const chains = details.querySelectorAll('.crc-node');
|
||||
assert.equal(chains.length, 4, 'generates correct number of chain nodes');
|
||||
|
||||
const div = dom.createElement('div');
|
||||
div.innerHTML = `<span class="crc-node__tree-marker">
|
||||
<!-- fill me -->
|
||||
<span class="tree-marker up-right"></span>
|
||||
<span class="tree-marker right"></span>
|
||||
<span class="tree-marker horiz-down"></span>
|
||||
</span>
|
||||
<span class="crc-node__tree-value">
|
||||
<span class="crc-node__tree-file">/</span>
|
||||
<span class="crc-node__tree-hostname">(example.com)</span>
|
||||
<!-- fill me -->
|
||||
</span>`;
|
||||
assert.ok(chains[0].innerHTML, div.innerHTML);
|
||||
|
||||
div.innerHTML = `<span class="crc-node__tree-marker">
|
||||
<!-- fill me -->
|
||||
<span class="tree-marker"></span>
|
||||
<span class="tree-marker"></span>
|
||||
<span class="tree-marker vert-right"></span>
|
||||
<span class="tree-marker right"></span>
|
||||
<span class="tree-marker right"></span>
|
||||
</span>
|
||||
<span class="crc-node__tree-value">
|
||||
<span class="crc-node__tree-file">/b.js</span>
|
||||
<span class="crc-node__tree-hostname">(example.com)</span>
|
||||
<!-- fill me -->
|
||||
<span class="crc-node__chain-duration"> - 5,000ms, </span>
|
||||
<span class="crc-node__chain-duration">0KB</span>
|
||||
</span>`;
|
||||
assert.ok(chains[1].innerHTML, div.innerHTML);
|
||||
});
|
||||
});
|
Загрузка…
Ссылка в новой задаче