v158 · resource timing · rum practice

Third-party weight audit

The question every RUM dashboard eventually asks: how many bytes is that third-party script costing my users? Whether your analytics can answer depends entirely on the response headers the third party sends — and on which gating rule the visitor's browser applies. This auditor fetches any URL you give it and reports what a measurement tool running on this page could actually see, plus the exact header that would unlock the rest.

All fetches happen from your browser, live. A cross-origin URL fetched with mode: "cors" only succeeds if the resource sends Access-Control-Allow-Origin; the auditor falls back to an opaque no-cors fetch when CORS is refused, exactly as a script tag without crossorigin would load it. Failures are reported as failures. From Chrome 158 the body-size gate is CORS (ResourceTimingUseCORSForBodySizes, no chrome://flags entry on older builds — only --enable-blink-features=…).

Audit a resource

fetched as
—
decodedBodySize
—
encodedBodySize
—
transferSize
—
responseStatus
—
duration
—

the practical rules of thumb

If you control the third party's headers (or can file a ticket with the vendor): Access-Control-Allow-Origin (plus loading with crossorigin) is what unlocks body sizes from Chrome 158 on, and it already unlocks responseStatus, contentType and contentEncoding everywhere those shipped. Timing-Allow-Origin remains the opt-in for the detailed timing breakdown (DNS, connect, TTFB) — and from 158 it is also still required, together with CORS, for transferSize. A vendor that sends both, like the CDN preset above, gives your telemetry the full picture in every browser generation; a vendor that sends neither is a byte-count black hole, and no client-side trick changes that — by design.

code path

// Audit as a RUM library would: prefer CORS (full visibility from 158),
// fall back to the opaque load a plain <script src> would produce.
let res, mode = "cors";
try {
  res = await fetch(url, { mode: "cors", cache: "no-store" });
} catch {
  mode = "no-cors";
  res = await fetch(url, { mode: "no-cors", cache: "no-store" });  // opaque
}
await res.blob();
const entry = performance.getEntriesByName(url).at(-1);
// entry.decodedBodySize / encodedBodySize / transferSize — 0 means "gated"

see also