browsertime 27.0.4 → 27.2.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.
Files changed (31) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/lib/chrome/parseCpuTrace.js +38 -2
  3. package/lib/chrome/trace/forced-reflows.js +78 -0
  4. package/lib/chrome/trace/index.js +41 -0
  5. package/lib/chrome/trace/main-thread-tasks.js +196 -0
  6. package/lib/chrome/trace/non-composited-animations.js +55 -0
  7. package/lib/chrome/trace/script-costs.js +88 -0
  8. package/lib/chrome/trace/task-groups.js +196 -0
  9. package/lib/chrome/trace/trace-of-tab.js +168 -0
  10. package/lib/chrome/trace/tracing-processor.js +95 -0
  11. package/lib/chrome/webdriver/chromium.js +13 -0
  12. package/lib/support/har/index.js +22 -0
  13. package/package.json +3 -4
  14. package/types/chrome/parseCpuTrace.d.ts +6 -0
  15. package/types/chrome/parseCpuTrace.d.ts.map +1 -1
  16. package/types/chrome/trace/forced-reflows.d.ts +8 -0
  17. package/types/chrome/trace/forced-reflows.d.ts.map +1 -0
  18. package/types/chrome/trace/index.d.ts +19 -0
  19. package/types/chrome/trace/index.d.ts.map +1 -0
  20. package/types/chrome/trace/main-thread-tasks.d.ts +31 -0
  21. package/types/chrome/trace/main-thread-tasks.d.ts.map +1 -0
  22. package/types/chrome/trace/non-composited-animations.d.ts +33 -0
  23. package/types/chrome/trace/non-composited-animations.d.ts.map +1 -0
  24. package/types/chrome/trace/script-costs.d.ts +14 -0
  25. package/types/chrome/trace/script-costs.d.ts.map +1 -0
  26. package/types/chrome/trace/task-groups.d.ts +65 -0
  27. package/types/chrome/trace/task-groups.d.ts.map +1 -0
  28. package/types/chrome/trace/trace-of-tab.d.ts +32 -0
  29. package/types/chrome/trace/trace-of-tab.d.ts.map +1 -0
  30. package/types/chrome/trace/tracing-processor.d.ts +24 -0
  31. package/types/chrome/trace/tracing-processor.d.ts.map +1 -0
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Trace-event → group classifier.
3
+ *
4
+ * The original list (from @sitespeed.io/tracium 0.3.3, extracted from
5
+ * Lighthouse circa 2017) is too narrow for modern Chrome traces — it
6
+ * only knew about ~30 event names, so half the trace fell through to
7
+ * "other" on a busy 2026-era page. This expanded list adds the
8
+ * events we actually see today, drawing from:
9
+ *
10
+ * - Modern Lighthouse `core/lib/tracehouse/task-groups.js`
11
+ * - WebPageTest's `MAIN_THREAD_CATEGORY_MAP` in waterfall-tools
12
+ * (`src/core/mainthread-categories.js`, mirroring the reference
13
+ * PHP implementation at Sample/Implementations/webpagetest/
14
+ * www/waterfall.inc#L437-L491)
15
+ * - Direct sampling of trace.json from real cnet / theverge runs
16
+ *
17
+ * The seven-bucket structure is unchanged so the existing UI
18
+ * categories keep working. Append-only when adding events for
19
+ * future Chrome versions — don't move events between groups
20
+ * without checking how it affects the rollups in
21
+ * `parseCpuTrace.js`.
22
+ */
23
+
24
+ export const taskGroups = {
25
+ parseHTML: {
26
+ id: 'parseHTML',
27
+ label: 'Parse HTML & CSS',
28
+ traceEventNames: [
29
+ 'ParseHTML',
30
+ 'ParseAuthorStyleSheet',
31
+ // Document parsing / navigation pipeline — these fire during
32
+ // the initial HTML/CSS parse phase, even though they're not
33
+ // strictly "parse" events. Counting them as parse-time gives
34
+ // a more honest "time spent loading the document" signal.
35
+ 'CommitLoad',
36
+ 'DocumentLoader::CommitNavigation',
37
+ 'DecodedDataDocumentParser::AppendBytes',
38
+ // Resource lifecycle events emitted on the main thread for
39
+ // the document and its sub-resources. waterfall-tools groups
40
+ // them with parsing for the same reason — they're part of the
41
+ // page-load critical path, not arbitrary "other" work.
42
+ 'ResourceSendRequest',
43
+ 'ResourceReceiveResponse',
44
+ 'ResourceReceivedData',
45
+ 'ResourceReceivedResponse',
46
+ 'ResourceFinish'
47
+ ]
48
+ },
49
+ styleLayout: {
50
+ id: 'styleLayout',
51
+ label: 'Style & Layout',
52
+ traceEventNames: [
53
+ 'ScheduleStyleRecalculation',
54
+ 'UpdateLayoutTree', // previously RecalculateStyles
55
+ 'RecalculateStyles', // older Chrome name
56
+ 'InvalidateLayout',
57
+ 'Layout',
58
+ // IntersectionObserver callbacks query layout to compute
59
+ // which observed elements have crossed their thresholds; on
60
+ // ad-heavy pages this can be hundreds of ms.
61
+ 'IntersectionObserverController::computeIntersections'
62
+ ]
63
+ },
64
+ paintCompositeRender: {
65
+ id: 'paintCompositeRender',
66
+ label: 'Rendering',
67
+ traceEventNames: [
68
+ 'Animation',
69
+ 'HitTest',
70
+ 'PaintSetup',
71
+ 'Paint',
72
+ 'PaintImage',
73
+ 'RasterTask', // Previously Rasterize
74
+ 'Rasterize',
75
+ 'ScrollLayer',
76
+ 'UpdateLayer',
77
+ 'UpdateLayerTree',
78
+ 'CompositeLayers',
79
+ // Modern compositor pipeline phases (Chrome ~M100+) that
80
+ // didn't exist when the original list was written.
81
+ 'PrePaint',
82
+ 'Commit',
83
+ 'Layerize',
84
+ 'BeginFrame',
85
+ 'BeginMainThreadFrame',
86
+ 'DrawFrame',
87
+ // Image decode work happens on the main thread when the
88
+ // off-thread decoder can't keep up.
89
+ 'DecodeImage',
90
+ 'Decode Image',
91
+ 'ImageDecodeTask',
92
+ 'GPUTask',
93
+ 'SetLayerTreeId'
94
+ ]
95
+ },
96
+ scriptParseCompile: {
97
+ id: 'scriptParseCompile',
98
+ label: 'Script Parsing & Compilation',
99
+ traceEventNames: [
100
+ 'v8.compile',
101
+ 'v8.compileModule',
102
+ 'v8.parseOnBackground',
103
+ 'v8.parseFunction',
104
+ // Context creation + V8 snapshot deserialization — these are
105
+ // the V8 startup costs that fire before any user JS runs.
106
+ 'V8.DeserializeContext',
107
+ 'LocalWindowProxy::CreateContext'
108
+ ]
109
+ },
110
+ scriptEvaluation: {
111
+ id: 'scriptEvaluation',
112
+ label: 'Script Evaluation',
113
+ traceEventNames: [
114
+ 'EventDispatch',
115
+ 'EvaluateScript',
116
+ 'v8.evaluateModule',
117
+ 'FunctionCall',
118
+ 'TimerFire',
119
+ 'TimerInstall',
120
+ 'TimerRemove',
121
+ 'FireIdleCallback',
122
+ 'FireAnimationFrame',
123
+ 'RunMicrotasks',
124
+ 'V8.Execute',
125
+ // Modern V8 entry points (Chrome ~M115+) — `v8.run` and
126
+ // `v8.callFunction` are the wrappers Chrome added when V8
127
+ // reorganized its tracing categories.
128
+ 'v8.run',
129
+ 'v8.callFunction',
130
+ 'v8.callModuleMethod',
131
+ 'XHRLoad',
132
+ 'XHRReadyStateChange'
133
+ ]
134
+ },
135
+ garbageCollection: {
136
+ id: 'garbageCollection',
137
+ label: 'Garbage Collection',
138
+ traceEventNames: [
139
+ 'MinorGC', // Previously GCEvent
140
+ 'MajorGC',
141
+ 'BlinkGC.AtomicPhase',
142
+ 'BlinkGCMarking',
143
+ 'ThreadState::performIdleLazySweep',
144
+ 'ThreadState::completeSweep',
145
+ 'Heap::collectGarbage',
146
+ // V8 GC events — there are many specific phase names but they
147
+ // all share the `V8.GC` prefix (V8.GCScavenger, V8.GCFinalizeMC,
148
+ // V8.GC_SCAVENGER_SCAVENGE_PARALLEL_PHASE, etc.). Specific
149
+ // names are listed for fast exact-match; the prefix fallback
150
+ // in groupForEvent() catches the rest.
151
+ 'V8.GCScavenger',
152
+ 'V8.GCFinalizeMC',
153
+ 'V8.GCMarkCompact',
154
+ 'V8.GCIncrementalMarking',
155
+ 'V8.GCCompactor',
156
+ 'V8.GC_SCAVENGER_SCAVENGE_PARALLEL_PHASE'
157
+ ]
158
+ },
159
+ other: {
160
+ id: 'other',
161
+ label: 'Other',
162
+ traceEventNames: [
163
+ 'MessageLoop::RunTask',
164
+ 'TaskQueueManager::ProcessTaskFromWorkQueue',
165
+ 'ThreadControllerImpl::DoWork',
166
+ // The top-level scheduler wrapper. Its self-time is the
167
+ // residual after children run — the "Chrome scheduling
168
+ // overhead" portion. Was missing from the old list and
169
+ // dominated the "other" bucket on busy pages.
170
+ 'RunTask',
171
+ 'ThreadControllerImpl::RunTask'
172
+ ]
173
+ }
174
+ };
175
+
176
+ export const taskNameToGroup = {};
177
+ for (const group of Object.values(taskGroups)) {
178
+ for (const traceEventName of group.traceEventNames) {
179
+ taskNameToGroup[traceEventName] = group;
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Look up the group for a trace event by name. Falls through to
185
+ * prefix matching for event-name families that share a structural
186
+ * pattern (`V8.GC*` for any V8 GC phase) so we don't have to
187
+ * enumerate every phase name Chrome ships. Returns undefined when
188
+ * no group matches; callers should fall back to `taskGroups.other`.
189
+ */
190
+ export function groupForEvent(name) {
191
+ const exact = taskNameToGroup[name];
192
+ if (exact) return exact;
193
+ if (typeof name === 'string' && name.startsWith('V8.GC')) {
194
+ return taskGroups.garbageCollection;
195
+ }
196
+ }
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Trace → tab-of-interest extractor. Ported from @sitespeed.io/tracium
3
+ * 0.3.3. Picks the renderer pid/tid/frameId, locates navigation/paint
4
+ * landmark events, and returns the chronologically-stable subset of
5
+ * events that belong to the inspected tab plus its main-thread
6
+ * subset that the task-builder consumes.
7
+ */
8
+
9
+ import { getLogger } from '@sitespeed.io/log';
10
+ import { findMainFrameIds } from './tracing-processor.js';
11
+
12
+ const log = getLogger('browsertime.chrome.trace');
13
+
14
+ const ACCEPTABLE_NAVIGATION_URL_REGEX = /^(chrome|https?|file):/;
15
+
16
+ function getTimestamp(event) {
17
+ return event && event.ts;
18
+ }
19
+
20
+ function isNavigationStartOfInterest(event) {
21
+ return (
22
+ event.name === 'navigationStart' &&
23
+ (!event.args.data ||
24
+ !event.args.data.documentLoaderURL ||
25
+ ACCEPTABLE_NAVIGATION_URL_REGEX.test(event.args.data.documentLoaderURL))
26
+ );
27
+ }
28
+
29
+ /**
30
+ * Stable sort by `ts`. JavaScript's Array.prototype.sort isn't
31
+ * guaranteed stable across engines historically, and trace event
32
+ * order matters when ts collides (B/E pairing breaks otherwise).
33
+ * The implementation sorts an indices array first, breaking ties by
34
+ * source index, so equal-ts events keep their original ordering.
35
+ */
36
+ function filteredStableSort(traceEvents, filter) {
37
+ const indices = [];
38
+ for (const [srcIndex, traceEvent] of traceEvents.entries()) {
39
+ if (filter(traceEvent)) {
40
+ indices.push(srcIndex);
41
+ }
42
+ }
43
+ indices.sort((indexA, indexB) => {
44
+ const result = traceEvents[indexA].ts - traceEvents[indexB].ts;
45
+ return result || indexA - indexB;
46
+ });
47
+ const sorted = [];
48
+ for (const index of indices) {
49
+ sorted.push(traceEvents[index]);
50
+ }
51
+ return sorted;
52
+ }
53
+
54
+ export function computeTraceOfTab(trace) {
55
+ // Parse the trace for our key events and sort them by timestamp.
56
+ // The sort *must* be stable to keep events correctly nested.
57
+ const keyEvents = filteredStableSort(trace.traceEvents, e => {
58
+ return (
59
+ e.cat.includes('blink.user_timing') ||
60
+ e.cat.includes('loading') ||
61
+ e.cat.includes('devtools.timeline') ||
62
+ e.cat === '__metadata'
63
+ );
64
+ });
65
+
66
+ const mainFrameIds = findMainFrameIds(keyEvents);
67
+
68
+ // Filter to just events matching the frame ID for sanity.
69
+ const frameEvents = keyEvents.filter(
70
+ e => e.args.frame === mainFrameIds.frameId
71
+ );
72
+
73
+ // Our navStart will be the last frame navigation in the trace.
74
+ const navigationStart = frameEvents.findLast(e =>
75
+ isNavigationStartOfInterest(e)
76
+ );
77
+ if (!navigationStart) throw new Error('NO_NAVSTART');
78
+
79
+ // Find our first paint of this frame.
80
+ const firstPaint = frameEvents.find(
81
+ e => e.name === 'firstPaint' && e.ts > navigationStart.ts
82
+ );
83
+
84
+ // fMP follows at/after the FP.
85
+ let firstMeaningfulPaint = frameEvents.find(
86
+ e => e.name === 'firstMeaningfulPaint' && e.ts > navigationStart.ts
87
+ );
88
+ let fmpFellBack = false;
89
+
90
+ // If there was no firstMeaningfulPaint event found in the trace,
91
+ // network-idle detection may have not been triggered before the
92
+ // capture finished. Use the last firstMeaningfulPaintCandidate.
93
+ if (!firstMeaningfulPaint) {
94
+ const fmpCand = 'firstMeaningfulPaintCandidate';
95
+ fmpFellBack = true;
96
+ log.debug(
97
+ 'trace-of-tab',
98
+ `No firstMeaningfulPaint found, falling back to last ${fmpCand}`
99
+ );
100
+ const lastCandidate = frameEvents.findLast(e => e.name === fmpCand);
101
+ if (!lastCandidate) {
102
+ log.debug(
103
+ 'trace-of-tab',
104
+ 'No `firstMeaningfulPaintCandidate` events found in trace'
105
+ );
106
+ }
107
+ firstMeaningfulPaint = lastCandidate;
108
+ }
109
+
110
+ const load = frameEvents.find(
111
+ e => e.name === 'loadEventEnd' && e.ts > navigationStart.ts
112
+ );
113
+ const domContentLoaded = frameEvents.find(
114
+ e => e.name === 'domContentLoadedEventEnd' && e.ts > navigationStart.ts
115
+ );
116
+
117
+ // Subset all trace events to just our tab's process (incl threads
118
+ // other than main). Stable-sort to keep events correctly nested.
119
+ const processEvents = filteredStableSort(
120
+ trace.traceEvents,
121
+ e => e.pid === mainFrameIds.pid
122
+ );
123
+
124
+ const mainThreadEvents = processEvents.filter(
125
+ e => e.tid === mainFrameIds.tid
126
+ );
127
+
128
+ // traceEnd must exist since at least navigationStart was verified.
129
+ let traceEnd = trace.traceEvents[0];
130
+ for (const evt of trace.traceEvents) {
131
+ if (evt.ts > traceEnd.ts) traceEnd = evt;
132
+ }
133
+ const fakeEndOfTraceEvt = { ts: traceEnd.ts + (traceEnd.dur || 0) };
134
+
135
+ const timestamps = {
136
+ navigationStart: navigationStart.ts,
137
+ firstPaint: getTimestamp(firstPaint),
138
+ firstMeaningfulPaint: getTimestamp(firstMeaningfulPaint),
139
+ traceEnd: fakeEndOfTraceEvt.ts,
140
+ load: getTimestamp(load),
141
+ domContentLoaded: getTimestamp(domContentLoaded)
142
+ };
143
+
144
+ const getTiming = ts => (ts - navigationStart.ts) / 1000;
145
+ const maybeGetTiming = ts => (ts === undefined ? undefined : getTiming(ts));
146
+ const timings = {
147
+ navigationStart: 0,
148
+ firstPaint: maybeGetTiming(timestamps.firstPaint),
149
+ firstMeaningfulPaint: maybeGetTiming(timestamps.firstMeaningfulPaint),
150
+ traceEnd: getTiming(timestamps.traceEnd),
151
+ load: maybeGetTiming(timestamps.load),
152
+ domContentLoaded: maybeGetTiming(timestamps.domContentLoaded)
153
+ };
154
+
155
+ return {
156
+ timings,
157
+ timestamps,
158
+ processEvents,
159
+ mainThreadEvents,
160
+ mainFrameIds,
161
+ navigationStartEvt: navigationStart,
162
+ firstPaintEvt: firstPaint,
163
+ firstMeaningfulPaintEvt: firstMeaningfulPaint,
164
+ loadEvt: load,
165
+ domContentLoadedEvt: domContentLoaded,
166
+ fmpFellBack
167
+ };
168
+ }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Trace pid/tid/frameId resolver, ported from @sitespeed.io/tracium
3
+ * 0.3.3 (Lighthouse 2018 lineage). Only `findMainFrameIds` is kept —
4
+ * the original module also exposed `getRiskToResponsiveness` and
5
+ * the `_riskPercentiles` math used by Lighthouse's TBT, but those
6
+ * aren't called from Browsertime's pipeline so they're dropped to
7
+ * keep the surface tight.
8
+ */
9
+
10
+ /**
11
+ * Locate the main renderer's pid/tid/frameId via three fallbacks, in
12
+ * order of how recent the trace events are: the modern
13
+ * TracingStartedInBrowser event (with a frames[] payload), the legacy
14
+ * TracingStartedInPage event, and finally pairing the first
15
+ * navigationStart with the first ResourceSendRequest. Throws when
16
+ * none of the three apply — which means the trace doesn't contain
17
+ * a renderable tab and the caller (parseCpuTrace) will degrade
18
+ * gracefully via its outer try/catch.
19
+ */
20
+ export function findMainFrameIds(events) {
21
+ // Prefer the newer TracingStartedInBrowser event first, if it exists.
22
+ const startedInBrowserEvt = events.find(
23
+ e => e.name === 'TracingStartedInBrowser'
24
+ );
25
+ if (
26
+ startedInBrowserEvt &&
27
+ startedInBrowserEvt.args.data &&
28
+ startedInBrowserEvt.args.data.frames
29
+ ) {
30
+ const mainFrame = startedInBrowserEvt.args.data.frames.find(
31
+ frame => !frame.parent
32
+ );
33
+ const frameId = mainFrame && mainFrame.frame;
34
+ const pid = mainFrame && mainFrame.processId;
35
+
36
+ const threadNameEvt = events.find(
37
+ e =>
38
+ e.pid === pid &&
39
+ e.ph === 'M' &&
40
+ e.cat === '__metadata' &&
41
+ e.name === 'thread_name' &&
42
+ e.args.name === 'CrRendererMain'
43
+ );
44
+ const tid = threadNameEvt && threadNameEvt.tid;
45
+
46
+ if (pid && tid && frameId) {
47
+ return { pid, tid, frameId };
48
+ }
49
+ }
50
+
51
+ // Support legacy browser versions that do not emit
52
+ // TracingStartedInBrowser. The first TracingStartedInPage in the
53
+ // trace is the renderer thread of interest. Beware: the
54
+ // TracingStartedInPage event can appear slightly after a
55
+ // navigationStart, so the order of fallbacks matters.
56
+ const startedInPageEvt = events.find(e => e.name === 'TracingStartedInPage');
57
+ if (startedInPageEvt && startedInPageEvt.args && startedInPageEvt.args.data) {
58
+ const frameId = startedInPageEvt.args.data.page;
59
+ if (frameId) {
60
+ return { pid: startedInPageEvt.pid, tid: startedInPageEvt.tid, frameId };
61
+ }
62
+ }
63
+
64
+ // Last resort: pair the first navigationStart that's loading the
65
+ // main frame with the first ResourceSendRequest from the same
66
+ // pid/tid. If those agree the renderer is whatever pid/tid they
67
+ // share.
68
+ const navStartEvt = events.find(e =>
69
+ Boolean(
70
+ e.name === 'navigationStart' &&
71
+ e.args &&
72
+ e.args.data &&
73
+ e.args.data.isLoadingMainFrame &&
74
+ e.args.data.documentLoaderURL
75
+ )
76
+ );
77
+ const firstResourceSendEvt = events.find(
78
+ e => e.name === 'ResourceSendRequest'
79
+ );
80
+ if (
81
+ navStartEvt &&
82
+ navStartEvt.args &&
83
+ navStartEvt.args.data &&
84
+ firstResourceSendEvt &&
85
+ firstResourceSendEvt.pid === navStartEvt.pid &&
86
+ firstResourceSendEvt.tid === navStartEvt.tid
87
+ ) {
88
+ const frameId = navStartEvt.args.frame;
89
+ if (frameId) {
90
+ return { pid: navStartEvt.pid, tid: navStartEvt.tid, frameId };
91
+ }
92
+ }
93
+
94
+ throw new Error('NO_TRACING_STARTED');
95
+ }
@@ -415,6 +415,19 @@ export class Chromium {
415
415
  render.renderBlockingInfo[harRequest.request.url];
416
416
  }
417
417
  }
418
+ // Also stamp the page-level summary (recalculate-style
419
+ // elements + duration before FCP/LCP) onto the HAR's page
420
+ // object so HAR-only consumers can pick it up without
421
+ // needing browsertime.json. The per-request map is already
422
+ // projected onto each entry above, so we keep this payload
423
+ // to just the summary fields.
424
+ const harPage =
425
+ this.hars[index - 1].log.pages && this.hars[index - 1].log.pages[0];
426
+ if (harPage && render.renderBlocking.recalculateStyle) {
427
+ harPage._renderBlocking = {
428
+ recalculateStyle: render.renderBlocking.recalculateStyle
429
+ };
430
+ }
418
431
  }
419
432
 
420
433
  result.renderBlocking.requests = render.renderBlockingInfo;
@@ -359,6 +359,17 @@ export function getEmptyHAR(url, browser) {
359
359
  }
360
360
  };
361
361
  }
362
+ // Tag the first entry of a page with the document URL it navigated to.
363
+ // Downstream consumers (waterfall-tools) read `entries[0]._documentURL`
364
+ // to flag cross-origin requests in the rendered waterfall — the first
365
+ // entry's own URL would also work for the no-redirect case, but breaks
366
+ // the moment the document follows a redirect chain to a different host.
367
+ function addDocumentURLToFirstEntry(harPage, entries, url) {
368
+ if (!harPage || !entries || !url) return;
369
+ const first = entries.find(entry => entry.pageref === harPage.id);
370
+ if (first) first._documentURL = url;
371
+ }
372
+
362
373
  export function addExtraFieldsToHar(totalResults, har, options) {
363
374
  if (har) {
364
375
  let harPageNumber = 0;
@@ -384,6 +395,12 @@ export function addExtraFieldsToHar(totalResults, har, options) {
384
395
  options
385
396
  );
386
397
 
398
+ addDocumentURLToFirstEntry(
399
+ harPage,
400
+ har.log.entries,
401
+ totalResults[pageNumber].info.url
402
+ );
403
+
387
404
  if (browserScript) {
388
405
  addExtrasToHAR(
389
406
  harPage,
@@ -417,6 +434,11 @@ export function addExtraFieldsToHar(totalResults, har, options) {
417
434
  browserScript,
418
435
  options
419
436
  );
437
+ addDocumentURLToFirstEntry(
438
+ harPage,
439
+ har.log.entries,
440
+ totalResults[page].info.url
441
+ );
420
442
  } else {
421
443
  log.error(
422
444
  'Could not add meta data to the HAR, miss page ' + harIndex
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": "27.0.4",
4
+ "version": "27.2.0",
5
5
  "bin": "./bin/browsertime.js",
6
6
  "type": "module",
7
7
  "types": "./types/scripting.d.ts",
@@ -11,9 +11,8 @@
11
11
  "@sitespeed.io/edgedriver": "143.0.3650",
12
12
  "@sitespeed.io/geckodriver": "0.36.0",
13
13
  "@sitespeed.io/log": "1.0.0",
14
- "@sitespeed.io/throttle": "5.0.1",
15
- "@sitespeed.io/tracium": "0.3.3",
16
- "chrome-har": "1.2.1",
14
+ "@sitespeed.io/throttle": "6.0.0",
15
+ "chrome-har": "1.3.0",
17
16
  "chrome-remote-interface": "0.33.3",
18
17
  "execa": "9.6.1",
19
18
  "fast-stats": "0.0.7",
@@ -13,9 +13,15 @@ export function parseCPUTrace(tracelog: any, url: any): Promise<{
13
13
  url: string;
14
14
  value: number;
15
15
  }[];
16
+ scriptCosts: any[];
17
+ forcedReflows: any[];
18
+ nonCompositedAnimations: any[];
16
19
  } | {
17
20
  categories?: undefined;
18
21
  events?: undefined;
19
22
  urls?: undefined;
23
+ scriptCosts?: undefined;
24
+ forcedReflows?: undefined;
25
+ nonCompositedAnimations?: undefined;
20
26
  }>;
21
27
  //# sourceMappingURL=parseCpuTrace.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"parseCpuTrace.d.ts","sourceRoot":"","sources":["../../lib/chrome/parseCpuTrace.js"],"names":[],"mappings":"AAaA;;;;;;;;;;;;;;;;;;;GA4EC"}
1
+ {"version":3,"file":"parseCpuTrace.d.ts","sourceRoot":"","sources":["../../lib/chrome/parseCpuTrace.js"],"names":[],"mappings":"AAkBA;;;;;;;;;;;;;;;;;;;;;;;;;GA2GC"}
@@ -0,0 +1,8 @@
1
+ export function computeForcedReflows(trace: any): {
2
+ eventName: any;
3
+ duration: number;
4
+ startTime: number;
5
+ triggeredBy: any;
6
+ triggeredByUrl: any;
7
+ }[];
8
+ //# sourceMappingURL=forced-reflows.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forced-reflows.d.ts","sourceRoot":"","sources":["../../../lib/chrome/trace/forced-reflows.js"],"names":[],"mappings":"AAgDA;;;;;;IA6BC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Compute the hierarchical main-thread task tree for a trace.
3
+ * Each task has its kind classified into one of:
4
+ * parseHTML | styleLayout | paintCompositeRender |
5
+ * scriptParseCompile | scriptEvaluation | garbageCollection | other
6
+ *
7
+ * @param {Object} trace Parsed Chrome trace.json (with .traceEvents).
8
+ * @param {Object} [options]
9
+ * @param {boolean} [options.flatten=false] When true, return all
10
+ * tasks flat (parents and children); when false, only top-level.
11
+ * @returns {Array<Object>}
12
+ */
13
+ export function computeMainThreadTasks(trace: any, options?: {
14
+ flatten?: boolean;
15
+ }): Array<any>;
16
+ export { computeScriptCosts } from "./script-costs.js";
17
+ export { computeForcedReflows } from "./forced-reflows.js";
18
+ export { computeNonCompositedAnimations } from "./non-composited-animations.js";
19
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../lib/chrome/trace/index.js"],"names":[],"mappings":"AAkBA;;;;;;;;;;;GAWG;AACH,6DAJG;IAA0B,OAAO,GAAzB,OAAO;CAEf,GAAU,KAAK,KAAQ,CAYzB"}
@@ -0,0 +1,31 @@
1
+ export function getMainThreadTasks(traceEvents: any, traceEndTs: any): {
2
+ event: any;
3
+ startTime: any;
4
+ endTime: any;
5
+ parent: any;
6
+ children: any[];
7
+ attributableURLs: any[];
8
+ group: {
9
+ id: string;
10
+ label: string;
11
+ traceEventNames: string[];
12
+ };
13
+ duration: number;
14
+ selfTime: number;
15
+ }[];
16
+ export function compute(trace: any): {
17
+ event: any;
18
+ startTime: any;
19
+ endTime: any;
20
+ parent: any;
21
+ children: any[];
22
+ attributableURLs: any[];
23
+ group: {
24
+ id: string;
25
+ label: string;
26
+ traceEventNames: string[];
27
+ };
28
+ duration: number;
29
+ selfTime: number;
30
+ }[];
31
+ //# sourceMappingURL=main-thread-tasks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main-thread-tasks.d.ts","sourceRoot":"","sources":["../../../lib/chrome/trace/main-thread-tasks.js"],"names":[],"mappings":"AAkKA;;;;;;;;;;;;;;IA4BC;AAED;;;;;;;;;;;;;;IAGC"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Non-composited animations — animations that triggered Layout or
3
+ * Paint instead of running entirely on the compositor thread.
4
+ *
5
+ * Compositor-only animations (transform, opacity) hand off to the
6
+ * GPU and don't block the main thread. Anything that touches
7
+ * `top` / `left` / `width` / `box-shadow` / non-transformable
8
+ * filters / etc. forces a per-frame layout or paint, which means
9
+ * jank on busy main threads.
10
+ *
11
+ * Chrome marks these in the trace via `Animation` events whose
12
+ * `args.data.compositeFailed` bitmask is non-zero, plus a list of
13
+ * `unsupportedProperties` strings. We surface the raw values; the
14
+ * consumer can map the bitmask to human reasons (see Lighthouse's
15
+ * non-composited-animations audit for the canonical mapping).
16
+ *
17
+ * Returns: [{ name, id, compositeFailed, unsupportedProperties,
18
+ * startTime }, …]
19
+ * name — args.data.nodeName when present (rare)
20
+ * id — args.data.id (compositor-internal)
21
+ * compositeFailed — bitmask of failure reasons
22
+ * unsupportedProperties — array of property names that blocked
23
+ * composite (e.g. ['top','box-shadow'])
24
+ * startTime — event ts in microseconds (raw trace timestamp)
25
+ */
26
+ export function computeNonCompositedAnimations(trace: any): {
27
+ name: any;
28
+ id: any;
29
+ compositeFailed: any;
30
+ unsupportedProperties: any;
31
+ startTime: any;
32
+ }[];
33
+ //# sourceMappingURL=non-composited-animations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"non-composited-animations.d.ts","sourceRoot":"","sources":["../../../lib/chrome/trace/non-composited-animations.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH;;;;;;IA4BC"}