diff --git a/README.md b/README.md
index 64e486a..a26e414 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
# Compare HAR files
Make it easier to find regressions by comparing your [HAR](http://www.softwareishard.com/blog/har-12-spec/) files. Test it out https://compare.sitespeed.io.
-
+
## First: Shout out!
We couldn't have built compare without the support or inspiration from the following people:
@@ -19,9 +19,9 @@ As long as your HAR files follow the [HAR specification](http://www.softwareisha
The result page is the same for every HAR — sections that don't apply silently stay hidden, so a plain Chrome HAR doesn't show a half-empty "filmstrip" or "CPU" block.
### What you get for every HAR
-* A **stacked waterfall** rendered by [waterfall-tools](https://github.com/pmeenan/waterfall-tools). Both HARs share the same time axis so widths are comparable at a glance. A **blend slider** fades HAR2 over HAR1, and a per-result toggle switches between **side-by-side** and **overlay**. Hovering a request highlights its peer in the other waterfall.
+* A **stacked waterfall** rendered by [waterfall-tools](https://github.com/pmeenan/waterfall-tools). Both HARs share the same time axis so widths are comparable at a glance. A **blend slider** fades HAR2 over HAR1, and a per-result toggle switches between **side-by-side** and **overlay**. Hovering a request shows its URL.
* A **Page X-ray** table (powered by [PageXray](https://github.com/sitespeedio/pagexray)) grouped into sections — Content, Render blocking, Visual metrics, Core Web Vitals, CPU and First/Third party. A **Δ column** shows the HAR2−HAR1 delta with regressions in red and improvements in green. An **"Only differences"** toggle hides rows where the two HARs match.
-* A **request/response diff** when both HARs are for the same URL — added requests, removed requests, and (per-request) size, status and timing changes. Tick **"Strip version parameters"** on the start page (or set `"stripVersion": true` in your config) to ignore cache-busting query strings.
+* A **request/response diff** when both HARs are for the same URL — new requests, removed requests, and requests whose transfer size changed by more than 1 KB. Tick **"Strip version parameters"** on the start page (or set `"stripVersion": true` in your config) to ignore cache-busting query strings.
* A **domains** breakdown.
* Chrome HARs with priority hints also produce a **Render blocking** row (blocking / potentially blocking / in-body parser blocking).
* `.har` and `.har.gz` are both accepted on drag/drop and via URL.
@@ -43,7 +43,7 @@ PageXray auto-detects first-party hosts from the page URL. If you want to overri

## How to use it
-You can either upload two HAR files (drag/drop) or give the URL to two URLs hosted online. If your HAR got multiple pages/runs, you can use just one HAR file.
+You can either upload two HAR files (drag/drop) or give the URL to two HAR files hosted online. If your HAR got multiple pages/runs, you can use just one HAR file.
Or you can just copy/paste your HAR file into the start page of [compare.sitespeed.io](https://compare.sitespeed.io/).
diff --git a/index.html b/index.html
index 9419f79..10bcfb9 100644
--- a/index.html
+++ b/index.html
@@ -77,7 +77,7 @@
diff --git a/public/js/compare/filmstripNav.js b/public/js/compare/filmstripNav.js
index 560c094..1431326 100644
--- a/public/js/compare/filmstripNav.js
+++ b/public/js/compare/filmstripNav.js
@@ -28,11 +28,9 @@ function initFilmstripNav() {
// Ignore mousedown on the actual zoom button — let the lightbox
// handle that click. Drag-scroll triggers on mousedown anywhere
// else inside the rail (the padding, the column gap, etc.).
- if (e.target.closest('.lightbox-trigger')) {
- // Still want to allow drag if user moves before releasing; we
- // arm a "candidate" state and promote to dragging only after
- // crossing the threshold.
- }
+ // Still want to allow drag if user moves before releasing; we
+ // arm a "candidate" state and promote to dragging only after
+ // crossing the threshold.
if (e.button !== 0) return;
const startX = e.pageX;
diff --git a/public/js/compare/generate.js b/public/js/compare/generate.js
index f876efa..7b5a201 100644
--- a/public/js/compare/generate.js
+++ b/public/js/compare/generate.js
@@ -1,4 +1,4 @@
-/* global getLastTiming, removeAndHide, createUpload, getAllDomains, hideUpload, objectPropertiesToArray, registerTemplateHelpers, parseTemplate, getTotalDiff, generateVisualProgress, formatDate, getUniqueRequests, getFilmstrip, compareWaterfall, renderShareControls, blendWaterfalls, applyWaterfallLayoutPreference */
+/* global getLastTiming, removeAndHide, createUpload, getAllDomains, hideUpload, objectPropertiesToArray, parseTemplate, getTotalDiff, generateVisualProgress, formatDate, getUniqueRequests, getFilmstrip, compareWaterfall, renderShareControls, blendWaterfalls, applyWaterfallLayoutPreference */
/* exported showUpload, formatDate, generate, toggleRow, regenerate, formatTime, showLoading*/
/**
@@ -168,25 +168,24 @@ function generate(config) {
});
}
- // It's time to start to parse content
- // make sure we have the helpers ready
- registerTemplateHelpers();
+ const filmstrip = getFilmstrip(
+ config.har1.har,
+ config.har1.run,
+ config.har2.har,
+ config.har2.run
+ );
+ // It's time to start to parse content
parseTemplate(
'resultHeaderTemplate',
{
p1: pageXray1,
- p2: pageXray2
+ p2: pageXray2,
+ filmstrip: filmstrip
},
'resultHeaderContent'
);
- const filmstrip = getFilmstrip(
- config.har1.har,
- config.har1.run,
- config.har2.har,
- config.har2.run
- );
if (filmstrip) {
parseTemplate(
'filmstripTemplate',
diff --git a/public/js/compare/start.js b/public/js/compare/start.js
index d698fb7..8c34215 100644
--- a/public/js/compare/start.js
+++ b/public/js/compare/start.js
@@ -96,6 +96,7 @@ const actions = {
};
const actionElements = {
+ submit: 'form[data-action]',
click: 'button[data-action]',
change: 'select[data-action]',
input: 'input[data-action]'
@@ -104,6 +105,8 @@ const actionElements = {
for (const [type, selector] of Object.entries(actionElements)) {
document.addEventListener(type, e => {
const el = e.target instanceof Element && e.target.closest(selector);
- if (el) actions[el.dataset.action](el);
+ if (!el) return;
+ if (type === 'submit') e.preventDefault();
+ actions[el.dataset.action](el);
});
}
diff --git a/public/js/compare/templates.js b/public/js/compare/templates.js
index 84e9c47..f6c1842 100644
--- a/public/js/compare/templates.js
+++ b/public/js/compare/templates.js
@@ -1,14 +1,12 @@
/* global formatBytes, formatTime, formatDate, formatURL */
-/* exported parseTemplate, registerTemplateHelpers */
+/* exported parseTemplate */
//
// Templates. Replaces the Template7 setup that used to live here —
// each template is now a plain JS function that returns an HTML
-// string built with template literals.
+// string.
//
-// parseTemplate(templateId, data, divId) is the public entry point;
-// callers in generate.js stay unchanged. registerTemplateHelpers is
-// kept as a no-op so the existing call site doesn't need touching.
+// parseTemplate(templateId, data, divId) is the public entry point.
//
// HTML-escape user-supplied or pulled-from-HAR strings.
@@ -34,15 +32,6 @@ function each(collection, fn) {
.join('');
}
-// Safe property lookup for nested keys that might be missing on
-// either of the two HARs (e.g. p2.firstParty.contentTypes['css'] may
-// be undefined if HAR2 has no CSS at all).
-// eslint-disable-next-line no-unused-vars -- not used now
-function getValue(obj, key, name) {
- if (!obj || !obj[key]) return 0;
- return obj[key][name] != null ? obj[key][name] : 0;
-}
-
//
// visualProgressTemplate
//
@@ -111,6 +100,13 @@ function pageXrayTemplate(d) {
// (URLs, dates, captures).
const emptyDiff = '
';
+ function metricRow(label, a, b, formatter) {
+ return '
' + h(label) + '
' +
+ '
' + (formatter ? formatter(a) : a) + '
' +
+ '
' + (formatter ? formatter(b) : b) + '
' +
+ diffCell(a, b, formatter) + '
';
+ }
+
// "Only show differences" toggle — read the user's last choice from
// localStorage so the preference survives reload. The button itself
// is rendered below; this just controls the initial class on the
@@ -232,22 +228,16 @@ function pageXrayTemplate(d) {
const rs1 = p1.renderBlocking.recalculateStyle;
const rs2 = p2.renderBlocking.recalculateStyle;
if (rs1 && rs2) {
- function recalcRow(label, a, b, formatter) {
- return '
' + h(label) + '
' +
- '
' + (formatter ? formatter(a) : a) + '
' +
- '
' + (formatter ? formatter(b) : b) + '
' +
- diffCell(a, b, formatter) + '
';
- }
if (rs1.beforeFCP && rs2.beforeFCP) {
- html += recalcRow('Style recalc elements (FCP)',
+ html += metricRow('Style recalc elements (FCP)',
rs1.beforeFCP.elements, rs2.beforeFCP.elements);
- html += recalcRow('Style recalc time (FCP)',
+ html += metricRow('Style recalc time (FCP)',
rs1.beforeFCP.durationInMillis, rs2.beforeFCP.durationInMillis, formatTime);
}
if (rs1.beforeLCP && rs2.beforeLCP) {
- html += recalcRow('Style recalc elements (LCP)',
+ html += metricRow('Style recalc elements (LCP)',
rs1.beforeLCP.elements, rs2.beforeLCP.elements);
- html += recalcRow('Style recalc time (LCP)',
+ html += metricRow('Style recalc time (LCP)',
rs1.beforeLCP.durationInMillis, rs2.beforeLCP.durationInMillis, formatTime);
}
}
@@ -256,18 +246,12 @@ function pageXrayTemplate(d) {
// resources fall into each render-blocking class. The "requests"
// suffix makes that explicit so the numbers aren't mistaken for
// milliseconds or sizes.
- html += '