browsertime 28.0.0 → 28.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Browsertime changelog (we do [semantic versioning](https://semver.org))
2
2
 
3
+ ## 28.1.0 - 2026-07-17
4
+
5
+ ### Added
6
+ * Long Animation Frames (`pageinfo.loaf`) now ship every per-script field the LoAF API exposes (`startTime`, `duration`, `executionStart`, `pauseDuration`) plus `startTime` and `firstUIEventTimestamp` per frame — enough to attribute blocking time to individual scripts, place each frame on the page timeline and spot frames where an input was waiting [#2522](https://github.com/sitespeedio/browsertime/pull/2522) [#2523](https://github.com/sitespeedio/browsertime/pull/2523) [#2526](https://github.com/sitespeedio/browsertime/pull/2526).
7
+ * New `pageinfo.loafSummary` key (`totalFrames`, `totalBlockingDuration`, `totalDuration`): `pageinfo.loaf` only ships the 10 frames with the most blocking, so totals computed from it understate busy pages [#2524](https://github.com/sitespeedio/browsertime/pull/2524).
8
+ * LoAF script URLs get the same ResourceLoader `label`s as `cpu.urls` (`load.php[startup]` and friends) [#2525](https://github.com/sitespeedio/browsertime/pull/2525).
9
+ * Multi-module `load.php` batches are labeled with their first module name — pages lazy-load several general batches, so the bare `load.php[scripts]` label collided as soon as there was more than one [#2527](https://github.com/sitespeedio/browsertime/pull/2527).
10
+
3
11
  ## 28.0.0 - 2026-07-17
4
12
 
5
13
  ### Breaking
@@ -17,6 +17,11 @@
17
17
  // re-package
18
18
  for (let entry of longestBlockingLoAFs) {
19
19
  const info = {};
20
+ // startTime places the frame on the page timeline (before or
21
+ // after FCP/LCP/load); firstUIEventTimestamp > 0 means an input
22
+ // was waiting while this frame blocked — the INP connection.
23
+ info.startTime = entry.startTime;
24
+ info.firstUIEventTimestamp = entry.firstUIEventTimestamp;
20
25
  info.blockingDuration = entry.blockingDuration;
21
26
  info.duration = entry.duration;
22
27
  info.styleAndLayoutStart = entry.styleAndLayoutStart;
@@ -29,6 +34,16 @@
29
34
  info.scripts = [];
30
35
  for (let script of entry.scripts) {
31
36
  const s = {};
37
+ // startTime + duration make per-script attribution possible:
38
+ // without them a frame's blocking can only be credited to
39
+ // every script that ran in it.
40
+ s.startTime = script.startTime;
41
+ s.duration = script.duration;
42
+ // executionStart - startTime is queue/compile delay before the
43
+ // script ran; pauseDuration separates "computed the whole time"
44
+ // from "sat in a sync pause" (alert, sync XHR).
45
+ s.executionStart = script.executionStart;
46
+ s.pauseDuration = script.pauseDuration;
32
47
  s.forcedStyleAndLayoutDuration = script.forcedStyleAndLayoutDuration;
33
48
  s.invoker = script.invoker;
34
49
  s.invokerType = script.invokerType;
@@ -0,0 +1,24 @@
1
+ (function () {
2
+ // The loaf script ships only the 10 frames with the most blocking
3
+ // time, so its numbers understate busy pages. These totals are
4
+ // computed over every long animation frame before the cut.
5
+ if (
6
+ PerformanceObserver.supportedEntryTypes.includes('long-animation-frame')
7
+ ) {
8
+ const observer = new PerformanceObserver(list => {});
9
+ observer.observe({type: 'long-animation-frame', buffered: true});
10
+ const entries = observer.takeRecords();
11
+
12
+ let totalBlockingDuration = 0;
13
+ let totalDuration = 0;
14
+ for (let entry of entries) {
15
+ totalBlockingDuration += entry.blockingDuration;
16
+ totalDuration += entry.duration;
17
+ }
18
+ return {
19
+ totalFrames: entries.length,
20
+ totalBlockingDuration,
21
+ totalDuration
22
+ };
23
+ }
24
+ })();
@@ -96,12 +96,14 @@ export function labelForUrl(url) {
96
96
  return 'load.php[startup]';
97
97
  }
98
98
  const kind = only === 'styles' ? 'styles' : 'scripts';
99
- if (modules.length === 1) {
99
+ // Every multi-module batch carries its (alphabetically) first module
100
+ // name. Real pages lazy-load several general batches (no `only=`)
101
+ // over time, so a bare load.php[scripts] label collides as soon as
102
+ // there is more than one — and the single-module case already names
103
+ // general batches like raw ones.
104
+ if (modules.length > 0) {
100
105
  return `load.php[${kind}:${modules[0]}]`;
101
106
  }
102
- if (modules.length > 1 && only === 'scripts') {
103
- return `load.php[scripts:${modules[0]}]`;
104
- }
105
107
  return `load.php[${kind}]`;
106
108
  }
107
109
 
@@ -395,6 +395,20 @@ export class Chromium {
395
395
  // this hack fixes that
396
396
  if (result.browserScripts && result.browserScripts.pageinfo) {
397
397
  result.browserScripts.pageinfo.longTask = this.longTaskInfo;
398
+
399
+ // Resolve LoAF script URLs to ResourceLoader module labels the
400
+ // same way cpu.urls gets them — several frames all blaming one
401
+ // giant load.php URL say nothing without the module name.
402
+ if (Array.isArray(result.browserScripts.pageinfo.loaf)) {
403
+ for (const frame of result.browserScripts.pageinfo.loaf) {
404
+ for (const script of frame.scripts || []) {
405
+ const label = labelForUrl(script.sourceURL);
406
+ if (label) {
407
+ script.label = label;
408
+ }
409
+ }
410
+ }
411
+ }
398
412
  }
399
413
 
400
414
  if (this.chrome.collectNetLog && this.chrome.android) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "browsertime",
3
3
  "description": "Get performance metrics from your web page using Browsertime.",
4
- "version": "28.0.0",
4
+ "version": "28.1.0",
5
5
  "bin": "./bin/browsertime.js",
6
6
  "type": "module",
7
7
  "types": "./scripting.d.ts",
@@ -1 +1 @@
1
- {"version":3,"file":"mediawikiResourceLoader.d.ts","sourceRoot":"","sources":["../../lib/chrome/mediawikiResourceLoader.js"],"names":[],"mappings":"AAmBA,mEAEC;AA6DD,8CAuBC;AAcD,8DAUC;AAwBD;;;;;EAkCC;AAOD,6EA4BC"}
1
+ {"version":3,"file":"mediawikiResourceLoader.d.ts","sourceRoot":"","sources":["../../lib/chrome/mediawikiResourceLoader.js"],"names":[],"mappings":"AAmBA,mEAEC;AA6DD,8CAyBC;AAcD,8DAUC;AAwBD;;;;;EAkCC;AAOD,6EA4BC"}