@newrelic/browser-agent 1.321.0 → 1.322.0-rc.1
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 +14 -0
- package/dist/cjs/common/constants/env.cdn.js +1 -1
- package/dist/cjs/common/constants/env.npm.js +1 -1
- package/dist/cjs/common/util/console.js +3 -0
- package/dist/cjs/common/v2/manifest.js +142 -0
- package/dist/cjs/common/v2/script-tracker-types.js +27 -0
- package/dist/cjs/common/v2/script-tracker.js +223 -25
- package/dist/cjs/common/v2/utils.js +47 -1
- package/dist/cjs/common/vitals/cumulative-layout-shift.js +6 -4
- package/dist/cjs/common/vitals/first-contentful-paint.js +5 -3
- package/dist/cjs/common/vitals/interaction-to-next-paint.js +10 -4
- package/dist/cjs/common/vitals/largest-contentful-paint.js +5 -3
- package/dist/cjs/common/vitals/register-vital.js +25 -0
- package/dist/cjs/common/vitals/time-to-first-byte.js +4 -2
- package/dist/cjs/common/vitals/vital-metric.js +3 -2
- package/dist/cjs/features/generic_events/aggregate/index.js +31 -14
- package/dist/cjs/features/generic_events/instrument/index.js +41 -1
- package/dist/cjs/interfaces/registered-iframe-entity.js +9 -3
- package/dist/cjs/loaders/api/register-api-types.js +13 -4
- package/dist/cjs/loaders/api/register.js +34 -1
- package/dist/esm/common/constants/env.cdn.js +1 -1
- package/dist/esm/common/constants/env.npm.js +1 -1
- package/dist/esm/common/util/console.js +3 -0
- package/dist/esm/common/v2/manifest.js +136 -0
- package/dist/esm/common/v2/script-tracker-types.js +24 -0
- package/dist/esm/common/v2/script-tracker.js +222 -26
- package/dist/esm/common/v2/utils.js +46 -1
- package/dist/esm/common/vitals/cumulative-layout-shift.js +6 -4
- package/dist/esm/common/vitals/first-contentful-paint.js +5 -3
- package/dist/esm/common/vitals/interaction-to-next-paint.js +10 -4
- package/dist/esm/common/vitals/largest-contentful-paint.js +5 -3
- package/dist/esm/common/vitals/register-vital.js +19 -0
- package/dist/esm/common/vitals/time-to-first-byte.js +4 -2
- package/dist/esm/common/vitals/vital-metric.js +3 -2
- package/dist/esm/features/generic_events/aggregate/index.js +32 -15
- package/dist/esm/features/generic_events/instrument/index.js +41 -1
- package/dist/esm/interfaces/registered-iframe-entity.js +9 -3
- package/dist/esm/loaders/api/register-api-types.js +14 -4
- package/dist/esm/loaders/api/register.js +34 -2
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/common/config/init-types.d.ts +1 -1
- package/dist/types/common/util/console.d.ts +3 -0
- package/dist/types/common/util/console.d.ts.map +1 -1
- package/dist/types/common/v2/manifest.d.ts +66 -0
- package/dist/types/common/v2/manifest.d.ts.map +1 -0
- package/dist/types/common/v2/script-tracker-types.d.ts +25 -0
- package/dist/types/common/v2/script-tracker-types.d.ts.map +1 -0
- package/dist/types/common/v2/script-tracker.d.ts +25 -4
- package/dist/types/common/v2/script-tracker.d.ts.map +1 -1
- package/dist/types/common/v2/utils.d.ts +12 -0
- package/dist/types/common/v2/utils.d.ts.map +1 -1
- package/dist/types/common/vitals/cumulative-layout-shift.d.ts.map +1 -1
- package/dist/types/common/vitals/first-contentful-paint.d.ts.map +1 -1
- package/dist/types/common/vitals/interaction-to-next-paint.d.ts.map +1 -1
- package/dist/types/common/vitals/largest-contentful-paint.d.ts.map +1 -1
- package/dist/types/common/vitals/register-vital.d.ts +13 -0
- package/dist/types/common/vitals/register-vital.d.ts.map +1 -0
- package/dist/types/common/vitals/time-to-first-byte.d.ts.map +1 -1
- package/dist/types/common/vitals/vital-metric.d.ts +1 -1
- package/dist/types/common/vitals/vital-metric.d.ts.map +1 -1
- package/dist/types/features/generic_events/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/generic_events/instrument/index.d.ts.map +1 -1
- package/dist/types/interfaces/registered-iframe-entity.d.ts.map +1 -1
- package/dist/types/loaders/api/register-api-types.d.ts +31 -4
- package/dist/types/loaders/api/register-api-types.d.ts.map +1 -1
- package/dist/types/loaders/api/register.d.ts +3 -0
- package/dist/types/loaders/api/register.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/common/util/console.js +3 -0
- package/src/common/v2/manifest.js +126 -0
- package/src/common/v2/script-tracker-types.js +24 -0
- package/src/common/v2/script-tracker.js +218 -26
- package/src/common/v2/utils.js +42 -1
- package/src/common/vitals/cumulative-layout-shift.js +5 -3
- package/src/common/vitals/first-contentful-paint.js +5 -3
- package/src/common/vitals/interaction-to-next-paint.js +8 -3
- package/src/common/vitals/largest-contentful-paint.js +5 -3
- package/src/common/vitals/register-vital.js +19 -0
- package/src/common/vitals/time-to-first-byte.js +4 -2
- package/src/common/vitals/vital-metric.js +3 -2
- package/src/features/generic_events/aggregate/index.js +25 -15
- package/src/features/generic_events/instrument/index.js +39 -1
- package/src/interfaces/registered-iframe-entity.js +7 -2
- package/src/loaders/api/register-api-types.js +14 -4
- package/src/loaders/api/register.js +29 -2
|
@@ -54,6 +54,7 @@ export type RegisterAPI = {
|
|
|
54
54
|
*/
|
|
55
55
|
metadata: RegisterAPIMetadata;
|
|
56
56
|
};
|
|
57
|
+
export type AssetFile = import("../../common/v2/manifest").AssetFile;
|
|
57
58
|
export type RegisterAPIConstructor = {
|
|
58
59
|
/**
|
|
59
60
|
* - The unique id for the registered entity. This will be assigned to any synthesized entities.
|
|
@@ -77,6 +78,16 @@ export type RegisterAPIConstructor = {
|
|
|
77
78
|
* - The parentId for the registered entity. If none was supplied, it will assume the entity guid from the main agent.
|
|
78
79
|
*/
|
|
79
80
|
parentId?: string | undefined;
|
|
81
|
+
/**
|
|
82
|
+
* - An optional manifest describing the MFE's known assets (scripts, stylesheets, images, fonts, etc.) as `AssetFile` entries, used to improve the accuracy of event attribution (errors, logs, ajax, websockets), attribute `BrowserPerformance` resource events (any asset type, not just scripts) and, depending on `timingMethod`, MicroFrontEndTiming values. Each `AssetFile`'s script-capability is inferred from a `.js` suffix on a string `matcher` (a RegExp `matcher` is never inferred as a script), unless overridden via `type: 'script'` or explicitly disabled via `type: 'asset'`. An `AssetFile` with an invalid `matcher` or an unrecognized `type` is discarded/ignored (with a console warning) rather than applied. If omitted, the agent falls back to its existing behavior of only evaluating the script that called `register()` (resource attribution still applies to that script).
|
|
83
|
+
*/
|
|
84
|
+
manifest?: {
|
|
85
|
+
assets?: Array<AssetFile>;
|
|
86
|
+
} | undefined;
|
|
87
|
+
/**
|
|
88
|
+
* - Controls which manifest assets are used to calculate MicroFrontEndTiming values: 'entry' (the default) leaves timing based entirely on the script that called `register()`, unaffected by the manifest; 'scripts' widens the timing window across all script assets; 'all' widens it across every asset.
|
|
89
|
+
*/
|
|
90
|
+
timingMethod?: "scripts" | "entry" | "all" | undefined;
|
|
80
91
|
};
|
|
81
92
|
export type RegisterAPIMetadata = {
|
|
82
93
|
/**
|
|
@@ -111,6 +122,14 @@ export type RegisterAPITarget = {
|
|
|
111
122
|
* - The parentId for the registered entity. If none was supplied, it will assume the entity guid from the main agent.
|
|
112
123
|
*/
|
|
113
124
|
parentId?: string | undefined;
|
|
125
|
+
/**
|
|
126
|
+
* - The parsed manifest for the registered entity, if one was supplied.
|
|
127
|
+
*/
|
|
128
|
+
manifest?: import("../../common/v2/manifest").ParsedManifest | undefined;
|
|
129
|
+
/**
|
|
130
|
+
* - The timing method supplied for the registered entity, if any.
|
|
131
|
+
*/
|
|
132
|
+
timingMethod?: "scripts" | "entry" | "all" | undefined;
|
|
114
133
|
};
|
|
115
134
|
export type RegisterAPITimings = {
|
|
116
135
|
/**
|
|
@@ -122,19 +141,19 @@ export type RegisterAPITimings = {
|
|
|
122
141
|
*/
|
|
123
142
|
reportedAt?: number | undefined;
|
|
124
143
|
/**
|
|
125
|
-
* - The timestamp when the registered entity began fetching (performance.start).
|
|
144
|
+
* - The timestamp when the registered entity began fetching (performance.start). When a manifest with timingMethod 'scripts'|'all' is supplied, this widens to the earliest fetchStart across all matched manifest assets.
|
|
126
145
|
*/
|
|
127
146
|
fetchStart: number;
|
|
128
147
|
/**
|
|
129
|
-
* - The timestamp when the registered entity finished fetching (performance.end).
|
|
148
|
+
* - The timestamp when the registered entity finished fetching (performance.end). When a manifest with timingMethod 'scripts'|'all' is supplied, this widens to the latest fetchEnd across all matched manifest assets.
|
|
130
149
|
*/
|
|
131
150
|
fetchEnd: number;
|
|
132
151
|
/**
|
|
133
|
-
* - The timestamp when script initialization began (max of dom.start or performance.end, or performance.end if no dom.start).
|
|
152
|
+
* - The timestamp when script initialization began (max of dom.start or performance.end, or performance.end if no dom.start). When a manifest with timingMethod 'scripts'|'all' is supplied, this widens to the earliest scriptStart across all matched manifest script assets (non-script assets are excluded).
|
|
134
153
|
*/
|
|
135
154
|
scriptStart: number;
|
|
136
155
|
/**
|
|
137
|
-
* - The timestamp when script loading completed (dom.end or registeredAt if no dom.end).
|
|
156
|
+
* - The timestamp when script loading completed (dom.end or registeredAt if no dom.end). When a manifest with timingMethod 'scripts'|'all' is supplied, this widens to the latest scriptEnd across all matched manifest script assets (non-script assets are excluded).
|
|
138
157
|
*/
|
|
139
158
|
scriptEnd: number;
|
|
140
159
|
/**
|
|
@@ -145,6 +164,14 @@ export type RegisterAPITimings = {
|
|
|
145
164
|
* - The type of timing associated with the registered entity, 'script' or 'link' if found with the performance resource API, 'fetch' for dynamic imports, 'inline' if found to be associated with the root document URL, or 'unknown' if no associated resource could be found.
|
|
146
165
|
*/
|
|
147
166
|
type: string;
|
|
167
|
+
/**
|
|
168
|
+
* - The sum of `transferSize` (bytes) across every asset detected for the registered entity -- the entry script, plus, when a manifest is supplied, every matched manifest asset (script or non-script). Unlike fetchStart/fetchEnd/scriptStart/scriptEnd above, this is never gated behind `timingMethod` -- manifest assets contribute their bytes even at the 'entry' default/unset. Cross-origin assets without a Timing-Allow-Origin header report 0 bytes per the Resource Timing spec.
|
|
169
|
+
*/
|
|
170
|
+
totalWeight: number;
|
|
171
|
+
/**
|
|
172
|
+
* - True if any detected asset (entry script or matched manifest asset) has a `renderBlockingStatus` of 'blocking'; false if none were 'blocking' but at least one reported 'non-blocking'; left `undefined` if no detected asset reported the attribute at all (e.g. unsupported browser). A single 'blocking' asset always wins over any number of 'non-blocking' ones, regardless of resolution order.
|
|
173
|
+
*/
|
|
174
|
+
renderBlocking?: boolean | undefined;
|
|
148
175
|
};
|
|
149
176
|
export type RegisterAPIVitals = {
|
|
150
177
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"register-api-types.d.ts","sourceRoot":"","sources":["../../../../src/loaders/api/register-api-types.js"],"names":[],"mappings":";;;;;;mBAOc,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,KAAK,IAAI;;;;SAC3C,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAA;KAAC,KAAK,IAAI;;;;iBACxH,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,EAAE,gBAAgB,CAAC,EAAE,MAAM,KAAK,IAAI;;;;gBAC1D,MAAM,IAAI;;;;uBACV,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,KAAK,IAAI;;;;aAChD,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAC,KAAK,CAAC,EAAE,MAAM,GAAC,eAAe,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,GAAC,eAAe,CAAC;QAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAC,KAAK,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAC,CAAC;;;;2BACtM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI;;;;wBAC9B,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,EAAE,OAAO,CAAC,EAAE,OAAO,KAAK,IAAI;;;;eAClF,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,YAAY,CAAC,EAAE,OAAO,KAAK,IAAI;;;;cACtD,mBAAmB
|
|
1
|
+
{"version":3,"file":"register-api-types.d.ts","sourceRoot":"","sources":["../../../../src/loaders/api/register-api-types.js"],"names":[],"mappings":";;;;;;mBAOc,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,KAAK,IAAI;;;;SAC3C,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAA;KAAC,KAAK,IAAI;;;;iBACxH,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,EAAE,gBAAgB,CAAC,EAAE,MAAM,KAAK,IAAI;;;;gBAC1D,MAAM,IAAI;;;;uBACV,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,KAAK,IAAI;;;;aAChD,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAC,KAAK,CAAC,EAAE,MAAM,GAAC,eAAe,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,GAAC,eAAe,CAAC;QAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAC,KAAK,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAC,CAAC;;;;2BACtM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI;;;;wBAC9B,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,EAAE,OAAO,CAAC,EAAE,OAAO,KAAK,IAAI;;;;eAClF,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,YAAY,CAAC,EAAE,OAAO,KAAK,IAAI;;;;cACtD,mBAAmB;;wBAIpB,OAAO,0BAA0B,EAAE,SAAS;;;;;QAK3C,MAAM;;;;UACN,MAAM;;;;;;;;;;;;;;;;;;;iBAII,KAAK,CAAC,SAAS,CAAC;;;;;;;;;;;sBAM1B,MAAM;;;;aACN,OAAO,CAAC,kBAAkB,CAAC;;;;YAC3B,OAAO,CAAC,iBAAiB,CAAC;;;;;;QAK1B,MAAM;;;;UACN,MAAM;;;;;;;;;;;;;;;;;;;;;;;;kBASN,MAAM;;;;;;;;gBAEN,MAAM;;;;cACN,MAAM;;;;iBACN,MAAM;;;;eACN,MAAM;;;;;;;;UAEN,MAAM;;;;iBACN,MAAM"}
|
|
@@ -9,6 +9,9 @@ export namespace warnings {
|
|
|
9
9
|
let disabled: Function;
|
|
10
10
|
let invalidTarget: Function;
|
|
11
11
|
let deregistered: Function;
|
|
12
|
+
let invalidTimingMethod: Function;
|
|
13
|
+
let duplicateName: Function;
|
|
14
|
+
let duplicateId: Function;
|
|
12
15
|
}
|
|
13
16
|
export type RegisterAPI = import("./register-api-types").RegisterAPI;
|
|
14
17
|
//# sourceMappingURL=register.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../../../../src/loaders/api/register.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../../../../src/loaders/api/register.js"],"names":[],"mappings":"AA2DA;;;;GAIG;AACH,mDAIC;;;;;;;;;;0BAxCY,OAAO,sBAAsB,EAAE,WAAW"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@newrelic/browser-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.322.0-rc.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"author": "New Relic Browser Agent Team <browser-agent@newrelic.com>",
|
|
6
6
|
"description": "New Relic Browser Agent",
|
|
@@ -225,7 +225,7 @@
|
|
|
225
225
|
"dependencies": {
|
|
226
226
|
"@newrelic/rrweb": "^1.1.2",
|
|
227
227
|
"fflate": "0.8.2",
|
|
228
|
-
"web-vitals": "
|
|
228
|
+
"web-vitals": "6.2.1"
|
|
229
229
|
},
|
|
230
230
|
"devDependencies": {
|
|
231
231
|
"@babel/cli": "^7.23.4",
|
|
@@ -294,4 +294,4 @@
|
|
|
294
294
|
"README.md",
|
|
295
295
|
"CHANGELOG.md"
|
|
296
296
|
]
|
|
297
|
-
}
|
|
297
|
+
}
|
|
@@ -95,6 +95,9 @@ import { dispatchGlobalEvent } from '../dispatch/global-event'
|
|
|
95
95
|
* | 77 | Agent rejected post message, could not validate origin. |
|
|
96
96
|
* | 78 | RegisteredIframeEntity could not determine parent origin and will not register, to avoid trusting messages from any origin. |
|
|
97
97
|
* | 79 | Unable to initialize Connector and/or Harvester. |
|
|
98
|
+
* | 80 | An invalid manifest option was provided to register() and will be ignored. |
|
|
99
|
+
* | 81 | Entities were detected that share a name with different IDs - This can cause multiple entities to have the same name in New Relic. |
|
|
100
|
+
* | 82 | Entities were detected that share an ID with different names - This can cause your entity's name to change unexpectedly. |
|
|
98
101
|
*
|
|
99
102
|
* @param {number} code The warning code to emit, which will be used to link to the warning code documentation
|
|
100
103
|
* @param {*} [secondary] Secondary data to include, usually an extra message, error or object
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { cleanURL } from '../url/clean-url'
|
|
7
|
+
import { warn } from '../util/console'
|
|
8
|
+
import { single } from '../util/invoke'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {Object} AssetFile
|
|
12
|
+
* @property {string|RegExp} matcher - the path/path-fragment string, or RegExp, used to match a resolved URL against this asset
|
|
13
|
+
* @property {'script'|'asset'} [type] - optional override for script-capability inference. When omitted, script-capability is inferred from a
|
|
14
|
+
* `.js` suffix on a string `matcher` (a RegExp `matcher` is never inferred as a script). Supply `type: 'script'` to explicitly flag an entry
|
|
15
|
+
* as a script regardless of its matcher shape/suffix (e.g. an extensionless URL, or a RegExp targeting a script path), or `type: 'asset'` to
|
|
16
|
+
* explicitly flag it as non-script. Any other value is invalid, logs a warning, and is ignored (falls back to inference).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** Warns at most once per page for any invalid manifest entry (bad `matcher` or bad `type`) -- mirrors the
|
|
20
|
+
* existing `invalidTimingMethod` single()-wrapped warning pattern in register.js. */
|
|
21
|
+
const warnInvalidManifestEntry = single((secondary) => warn(80, secondary))
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @typedef {Object} ParsedManifestAsset
|
|
25
|
+
* @property {string|RegExp} pattern - the original matcher supplied by the customer
|
|
26
|
+
* @property {(url: string) => boolean} test - precompiled matcher against a resolved URL
|
|
27
|
+
* @property {boolean} isScript
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @typedef {Object} ParsedManifest
|
|
32
|
+
* @property {ParsedManifestAsset[]} assets - all supplied assets
|
|
33
|
+
* @property {ParsedManifestAsset[]} scripts - the subset of assets inferred/declared as scripts (`.js`)
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Parses a raw manifest supplied to `register()` into precompiled matcher closures. Parsing happens once, at
|
|
38
|
+
* registration time, so downstream event attribution (which runs on every ajax/error/log/websocket event) never has
|
|
39
|
+
* to re-derive matching logic from the raw customer input.
|
|
40
|
+
* @param {{assets?: Array<AssetFile>}} [rawManifest]
|
|
41
|
+
* @returns {ParsedManifest|undefined} undefined if no usable assets were supplied, so callers can cheaply skip all manifest logic
|
|
42
|
+
*/
|
|
43
|
+
export function parseManifest (rawManifest) {
|
|
44
|
+
if (!Array.isArray(rawManifest?.assets) || !rawManifest.assets.length) return undefined
|
|
45
|
+
|
|
46
|
+
const assets = rawManifest.assets.map(parseAsset).filter(Boolean)
|
|
47
|
+
if (!assets.length) return undefined
|
|
48
|
+
|
|
49
|
+
return { assets, scripts: assets.filter(asset => asset.isScript) }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @param {AssetFile} entry
|
|
54
|
+
* @returns {ParsedManifestAsset|undefined}
|
|
55
|
+
*/
|
|
56
|
+
function parseAsset (entry) {
|
|
57
|
+
if (!entry || typeof entry !== 'object') return undefined
|
|
58
|
+
|
|
59
|
+
const { matcher, type } = entry
|
|
60
|
+
const inferredIsScript = typeof matcher === 'string' && isScriptPath(matcher)
|
|
61
|
+
|
|
62
|
+
let isScript = inferredIsScript
|
|
63
|
+
if (type !== undefined) {
|
|
64
|
+
if (isValidType(type)) {
|
|
65
|
+
isScript = isScriptType(type)
|
|
66
|
+
} else {
|
|
67
|
+
// Invalid `type` never overrides -- fall back to inference-only behavior rather than forcing `isScript: false`,
|
|
68
|
+
// so a typo'd `type` can't silently break stack-trace attribution for an otherwise-valid `.js` matcher.
|
|
69
|
+
warnInvalidManifestEntry(type)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (isRegExp(matcher)) {
|
|
74
|
+
// Reset lastIndex before every test -- a `g`/`y` flagged matcher otherwise carries state across calls
|
|
75
|
+
// (this closure is reused for every event), causing intermittent, input-order-dependent match failures.
|
|
76
|
+
return { pattern: matcher, test: (url) => { matcher.lastIndex = 0; return matcher.test(cleanURL(url)) }, isScript }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (typeof matcher === 'string' && matcher.length > 0) {
|
|
80
|
+
return { pattern: matcher, test: (url) => cleanURL(url).includes(matcher), isScript }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Every other matcher shape (missing, empty string, null, number, plain object, ...) is invalid -- notably an
|
|
84
|
+
// empty string must be rejected here rather than falling through to the string branch above, since
|
|
85
|
+
// ''.includes('') is always true and would otherwise silently match every URL.
|
|
86
|
+
warnInvalidManifestEntry(matcher)
|
|
87
|
+
return undefined
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function isScriptPath (path) {
|
|
91
|
+
return path.endsWith('.js')
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function isValidType (type) {
|
|
95
|
+
return type === 'script' || type === 'asset'
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Cross-realm-safe check for whether a value is a RegExp. `instanceof RegExp` only returns true when the value's
|
|
100
|
+
* prototype chain links to THIS realm's `RegExp.prototype` -- a regex literal constructed in a different realm
|
|
101
|
+
* (an iframe, a Worker via structured clone, a WebDriver sandbox such as Firefox's geckodriver executeScript
|
|
102
|
+
* context) is still a genuine RegExp, just not an instance of this realm's constructor, and would otherwise be
|
|
103
|
+
* silently dropped by parseAsset below.
|
|
104
|
+
* @param {*} value
|
|
105
|
+
* @returns {boolean}
|
|
106
|
+
*/
|
|
107
|
+
function isRegExp (value) {
|
|
108
|
+
return Object.prototype.toString.call(value) === '[object RegExp]'
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function isScriptType (type) {
|
|
112
|
+
return type === 'script'
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Determines whether a resolved URL matches any asset in a parsed manifest.
|
|
117
|
+
* @param {ParsedManifest|undefined} parsed
|
|
118
|
+
* @param {string} url
|
|
119
|
+
* @param {{scriptsOnly?: boolean}} [options]
|
|
120
|
+
* @returns {boolean}
|
|
121
|
+
*/
|
|
122
|
+
export function matchManifestAsset (parsed, url, { scriptsOnly = false } = {}) {
|
|
123
|
+
if (!parsed || !url) return false
|
|
124
|
+
const candidates = scriptsOnly ? parsed.scripts : parsed.assets
|
|
125
|
+
return candidates.some(asset => asset.test(url))
|
|
126
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {(start: number, end: number) => void} RecordManifestScriptWindowFn - Widens the live scriptStart/
|
|
8
|
+
* scriptEnd window with one manifest script asset's DOM correlation timing. `start`/`end` are that asset's
|
|
9
|
+
* `correlation.script.start`/`.end`, or falsy if not yet resolved. See `findScriptTimings`, which registers the
|
|
10
|
+
* concrete implementation, for the exact widening semantics (never shrinks either bound).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {Object} TimingsInternals
|
|
15
|
+
* @property {Set<string>} weighedAssetUrls - Always present (seeded by `getOrCreateInternals`, see
|
|
16
|
+
* `script-tracker.js`). Cleaned URLs already folded into `timings.totalWeight`/`timings.renderBlocking` by
|
|
17
|
+
* `applyResourceWeight`, so the same underlying resource (e.g. a manifest asset that's also the .register calling
|
|
18
|
+
* script itself) is never counted twice.
|
|
19
|
+
* @property {RecordManifestScriptWindowFn} recordManifestScriptWindow - Always present (seeded by
|
|
20
|
+
* `getOrCreateInternals`, see `script-tracker.js`). `findScriptTimings` overrides the seeded default with one that
|
|
21
|
+
* folds into the live `scriptStart`/`scriptEnd` getters instead.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
export default {}
|
|
@@ -12,8 +12,10 @@ import { CORRELATION_STALE_THRESHOLD_MS } from './script-tracker-constants'
|
|
|
12
12
|
import { timingFactory } from './timing-factory'
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* @typedef {import('
|
|
15
|
+
* @typedef {import('../../loaders/api/register-api-types').RegisterAPITimings} RegisterAPITimings
|
|
16
16
|
* @typedef {import('../../loaders/api/register-api-types').RegisterAPITarget} RegisterAPITarget
|
|
17
|
+
* @typedef {import('./script-tracker-types').RecordManifestScriptWindowFn} RecordManifestScriptWindowFn
|
|
18
|
+
* @typedef {import('./script-tracker-types').TimingsInternals} TimingsInternals
|
|
17
19
|
*/
|
|
18
20
|
|
|
19
21
|
/** export for testing purposes */
|
|
@@ -33,11 +35,43 @@ export const scriptCorrelations = new Map()
|
|
|
33
35
|
let poSubscribers = []
|
|
34
36
|
|
|
35
37
|
/**
|
|
36
|
-
*
|
|
38
|
+
* Bookkeeping keyed by a `timings` object, kept off the object itself since it's exposed directly to customers via
|
|
39
|
+
* `register().metadata.timings`.
|
|
40
|
+
* @type {WeakMap<RegisterAPITimings, TimingsInternals>}
|
|
41
|
+
*/
|
|
42
|
+
const timingsInternals = new WeakMap()
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Gets (or lazily creates) the bookkeeping record for a `timings` object. A fresh record's `recordManifestScriptWindow`
|
|
46
|
+
* defaults to widening `timings.scriptStart`/`scriptEnd` directly -- correct for a plain `timings` object never
|
|
47
|
+
* produced by `findScriptTimings`. `findScriptTimings` overrides that default with one that folds into its live
|
|
48
|
+
* getters instead.
|
|
49
|
+
* @param {RegisterAPITimings} timings
|
|
50
|
+
* @returns {TimingsInternals}
|
|
51
|
+
*/
|
|
52
|
+
function getOrCreateInternals (timings) {
|
|
53
|
+
let internals = timingsInternals.get(timings)
|
|
54
|
+
if (!internals) {
|
|
55
|
+
internals = {
|
|
56
|
+
weighedAssetUrls: new Set(),
|
|
57
|
+
recordManifestScriptWindow: (start, end) => {
|
|
58
|
+
if (start) timings.scriptStart = timings.scriptStart > 0 ? Math.min(timings.scriptStart, start) : start
|
|
59
|
+
if (end) timings.scriptEnd = timings.scriptEnd > 0 ? Math.max(timings.scriptEnd, end) : end
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
timingsInternals.set(timings, internals)
|
|
63
|
+
}
|
|
64
|
+
return internals
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Retrieves a script correlation by URL using exact matching. Exported so other features (e.g. generic_events'
|
|
69
|
+
* resource attribution) can key off the same DOM node/load-timing tracking this module already does for every
|
|
70
|
+
* `<script>` element, rather than setting up a second, redundant observer.
|
|
37
71
|
* @param {string} targetUrl - The URL to find
|
|
38
72
|
* @returns {ScriptCorrelation | undefined} - The correlation object if found
|
|
39
73
|
*/
|
|
40
|
-
function findCorrelation (targetUrl) {
|
|
74
|
+
export function findCorrelation (targetUrl) {
|
|
41
75
|
return scriptCorrelations.get(targetUrl)
|
|
42
76
|
}
|
|
43
77
|
|
|
@@ -88,20 +122,24 @@ if (globalScope.MutationObserver && globalScope.document) {
|
|
|
88
122
|
}
|
|
89
123
|
|
|
90
124
|
if (globalScope.PerformanceObserver?.supportedEntryTypes.includes('resource')) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
* after the buffer fills up we won't be able to get the script timing information from the resource timing API
|
|
94
|
-
*/
|
|
125
|
+
// Tracked via an observer (not a later buffer read) because the performance buffer stops accepting new entries
|
|
126
|
+
// once full, instead of dropping old ones -- a late register() call could otherwise miss timing entirely.
|
|
95
127
|
const scriptObserver = new PerformanceObserver((list) => {
|
|
96
|
-
list.getEntries().
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
128
|
+
list.getEntries().forEach((entry) => {
|
|
129
|
+
// Correlation bookkeeping only makes sense for script-like entries -- gated on validEntryCriteria so
|
|
130
|
+
// scriptCorrelations doesn't grow for every image/css/font load on the page.
|
|
131
|
+
if (validEntryCriteria(entry)) {
|
|
132
|
+
const entryUrl = cleanURL(entry.name)
|
|
133
|
+
const correlation = getOrCreateCorrelation(entryUrl)
|
|
134
|
+
correlation.performance.start = Math.floor(entry.startTime)
|
|
135
|
+
correlation.performance.end = Math.floor(entry.responseEnd)
|
|
136
|
+
correlation.performance.value = entry
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Late-resolution subscribers can be for any asset type (not just scripts), so every entry is checked here,
|
|
140
|
+
// unfiltered. Skipped when nothing is pending, the common case.
|
|
141
|
+
if (!poSubscribers.length) return
|
|
142
|
+
|
|
105
143
|
const canClear = []
|
|
106
144
|
poSubscribers.forEach(({ test, addedAt }, idx) => {
|
|
107
145
|
if (test(entry) || now() - addedAt > 10000) canClear.push(idx)
|
|
@@ -197,6 +235,32 @@ function applyPerformanceEntry (timings, entry) {
|
|
|
197
235
|
timings.fetchEnd = Math.floor(entry.responseEnd)
|
|
198
236
|
timings.asset = entry.name
|
|
199
237
|
timings.type = entry.initiatorType
|
|
238
|
+
applyResourceWeight(timings, entry)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Accumulates the byte weight and render-blocking status of a single detected asset (the entry script or a resolved
|
|
243
|
+
* manifest asset) into a timings object. Shared by both the entry-script path (`applyPerformanceEntry`) and the
|
|
244
|
+
* manifest path (`applyManifestEntry`) so `totalWeight`/`renderBlocking` reflect every asset actually detected,
|
|
245
|
+
* regardless of which path found it.
|
|
246
|
+
* @param {RegisterAPITimings} timings
|
|
247
|
+
* @param {PerformanceResourceTiming} entry
|
|
248
|
+
*/
|
|
249
|
+
function applyResourceWeight (timings, entry) {
|
|
250
|
+
// De-dupe by cleaned URL: a manifest can list the .register calling script itself as one of its own assets,
|
|
251
|
+
// which would otherwise weigh the same resource twice (once via findScriptTimings, once via applyManifestTimings).
|
|
252
|
+
const url = cleanURL(entry.name)
|
|
253
|
+
const { weighedAssetUrls } = getOrCreateInternals(timings)
|
|
254
|
+
if (weighedAssetUrls.has(url)) return
|
|
255
|
+
weighedAssetUrls.add(url)
|
|
256
|
+
|
|
257
|
+
// transferSize is 0 for cross-origin responses without Timing-Allow-Origin (a privacy restriction, not a
|
|
258
|
+
// zero-byte asset) -- adding 0 is correct either way.
|
|
259
|
+
timings.totalWeight = (timings.totalWeight || 0) + (entry.transferSize || 0)
|
|
260
|
+
// 'blocking' always wins and never gets downgraded; 'non-blocking' only applies if nothing already resolved
|
|
261
|
+
// true; no value at all (unsupported browser) leaves renderBlocking untouched (stays `undefined`).
|
|
262
|
+
if (entry.renderBlockingStatus === 'blocking') timings.renderBlocking = true
|
|
263
|
+
else if (entry.renderBlockingStatus === 'non-blocking' && timings.renderBlocking !== true) timings.renderBlocking = false
|
|
200
264
|
}
|
|
201
265
|
|
|
202
266
|
/**
|
|
@@ -220,13 +284,117 @@ function subscribeToLatePerformanceEntry (timings, mfeScriptUrl) {
|
|
|
220
284
|
}
|
|
221
285
|
|
|
222
286
|
/**
|
|
223
|
-
*
|
|
224
|
-
*
|
|
287
|
+
* Applies one manifest asset's performance entry to a timings object: weight/renderBlocking always accumulate;
|
|
288
|
+
* fetchStart/fetchEnd and scriptStart/scriptEnd widen (never shrink) only when `timingMethod` calls for it; asset/
|
|
289
|
+
* type get anchored to the first script asset seen to resolve.
|
|
290
|
+
* @param {RegisterAPITimings} timings
|
|
291
|
+
* @param {PerformanceResourceTiming} entry
|
|
292
|
+
* @param {import('./manifest').ParsedManifestAsset} asset - the manifest asset this entry resolved
|
|
293
|
+
* @param {{ resolved: boolean }} entryState - shared "first script asset wins" guard for a single `applyManifestTimings` call
|
|
294
|
+
* @param {'entry'|'scripts'|'all'} [timingMethod] - the registered MFE's timing method; `undefined`/'entry' means weight/render-blocking still accumulate, but no timing widening happens at all
|
|
295
|
+
*/
|
|
296
|
+
function applyManifestEntry (timings, entry, asset, entryState, timingMethod) {
|
|
297
|
+
// Weight isn't a timing concern, so it accumulates for every matched asset regardless of timingMethod.
|
|
298
|
+
applyResourceWeight(timings, entry)
|
|
299
|
+
|
|
300
|
+
if (timingMethod !== 'scripts' && timingMethod !== 'all') return // no timing-widening effect at the 'entry' default/unset
|
|
301
|
+
|
|
302
|
+
const widensAllAssets = timingMethod === 'all'
|
|
303
|
+
// Under 'scripts', only script assets widen the fetch window; under 'all', every matched asset does.
|
|
304
|
+
if (widensAllAssets || asset.isScript) {
|
|
305
|
+
const start = Math.floor(entry.startTime)
|
|
306
|
+
const end = Math.floor(entry.responseEnd)
|
|
307
|
+
// fetchStart/fetchEnd default to 0 ("not yet found") -- only fold into the min/max once they're positive,
|
|
308
|
+
// or 0 would permanently win Math.min.
|
|
309
|
+
timings.fetchStart = timings.fetchStart > 0 ? Math.min(timings.fetchStart, start) : start
|
|
310
|
+
timings.fetchEnd = timings.fetchEnd > 0 ? Math.max(timings.fetchEnd, end) : end
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Non-script assets never execute, so only script assets widen the execution window or anchor asset/type.
|
|
314
|
+
if (asset.isScript) {
|
|
315
|
+
const correlation = findCorrelation(cleanURL(entry.name))
|
|
316
|
+
if (correlation) {
|
|
317
|
+
// Widens the aggregate scriptStart/scriptEnd window with this asset's current correlation timing. Re-called
|
|
318
|
+
// as a 'load'/'error' listener below if its DOM completion hasn't fired yet, so a later, larger end still counts.
|
|
319
|
+
const widenScriptWindowForAsset = () => {
|
|
320
|
+
const { start: scriptStart, end: scriptEnd } = correlation.script
|
|
321
|
+
getOrCreateInternals(timings).recordManifestScriptWindow(scriptStart, scriptEnd)
|
|
322
|
+
}
|
|
323
|
+
widenScriptWindowForAsset()
|
|
324
|
+
if (!correlation.dom.end && correlation.dom.value) {
|
|
325
|
+
;['load', 'error'].forEach(eventType => correlation.dom.value.addEventListener(eventType, widenScriptWindowForAsset, { once: true }))
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (!entryState.resolved) {
|
|
330
|
+
timings.asset = entry.name
|
|
331
|
+
timings.type = entry.initiatorType
|
|
332
|
+
entryState.resolved = true
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Subscribes to late resource timing emissions for manifest assets not yet resolved against the buffered entries.
|
|
339
|
+
* Reuses the shared page-wide scriptObserver/poSubscribers mechanism (one PerformanceObserver for all MFEs, not
|
|
340
|
+
* one per MFE) and, unlike that observer's own correlation bookkeeping, checks every resource entry -- not just
|
|
341
|
+
* script-like ones -- so lazy-loaded images/fonts/stylesheets resolve too.
|
|
342
|
+
* @param {RegisterAPITimings} timings
|
|
343
|
+
* @param {Set<import('./manifest').ParsedManifestAsset>} pending - manifest assets still unresolved
|
|
344
|
+
* @param {{ resolved: boolean }} entryState - shared "first script asset wins" guard for a single `applyManifestTimings` call
|
|
345
|
+
* @param {'entry'|'scripts'|'all'} [timingMethod] - forwarded to `applyManifestEntry` for each late-resolving asset
|
|
346
|
+
*/
|
|
347
|
+
function subscribeToLateManifestEntries (timings, pending, entryState, timingMethod) {
|
|
348
|
+
if (!globalScope.PerformanceObserver?.supportedEntryTypes?.includes('resource')) return
|
|
349
|
+
|
|
350
|
+
poSubscribers.push({
|
|
351
|
+
addedAt: now(),
|
|
352
|
+
test: (entry) => {
|
|
353
|
+
const matched = [...pending].find(asset => asset.test(entry.name))
|
|
354
|
+
if (matched) {
|
|
355
|
+
applyManifestEntry(timings, entry, matched, entryState, timingMethod)
|
|
356
|
+
pending.delete(matched)
|
|
357
|
+
}
|
|
358
|
+
return pending.size === 0
|
|
359
|
+
}
|
|
360
|
+
})
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Applies a registered MFE's manifest to a timings object (already populated by `findScriptTimings`). No-op if no
|
|
365
|
+
* manifest is present. Weight/renderBlocking always accumulate from every detected manifest asset; timing widening
|
|
366
|
+
* (fetchStart/fetchEnd/scriptStart/scriptEnd/asset anchor) is opt-in via `timingMethod` -- see `applyManifestEntry`.
|
|
367
|
+
* @param {RegisterAPITimings} timings - the timings object to widen in place
|
|
368
|
+
* @param {RegisterAPITarget} target - the registered MFE target, which may carry a parsed `manifest`
|
|
369
|
+
*/
|
|
370
|
+
export function applyManifestTimings (timings, target) {
|
|
371
|
+
const parsedManifest = target?.manifest
|
|
372
|
+
if (!parsedManifest || !parsedManifest.assets.length) return
|
|
373
|
+
|
|
374
|
+
const entryState = { resolved: false }
|
|
375
|
+
const pending = new Set(parsedManifest.assets)
|
|
376
|
+
|
|
377
|
+
const resourceEntries = globalScope.performance?.getEntriesByType('resource') || []
|
|
378
|
+
resourceEntries.forEach((entry) => {
|
|
379
|
+
const matched = [...pending].find(asset => asset.test(entry.name))
|
|
380
|
+
if (matched) {
|
|
381
|
+
applyManifestEntry(timings, entry, matched, entryState, target.timingMethod)
|
|
382
|
+
pending.delete(matched)
|
|
383
|
+
}
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
if (pending.size) subscribeToLateManifestEntries(timings, pending, entryState, target.timingMethod)
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Uses the initiator function's stack to find script timing information via the resource timing API.
|
|
391
|
+
* @param {RegisterAPITarget} [target] - the MFE target being registered; its id scopes stale-correlation
|
|
392
|
+
* detection per-MFE rather than per-script-URL (see isCorrelationStale below)
|
|
225
393
|
* @returns {RegisterAPITimings} Object containing script fetch start and end times, and the asset URL if found
|
|
226
394
|
*/
|
|
227
395
|
export function findScriptTimings (target) {
|
|
228
396
|
const mfeId = target?.id
|
|
229
|
-
const timings = { registeredAt: now(), reportedAt: undefined, fetchStart: 0, fetchEnd: 0, scriptStart: 0, scriptEnd: 0, asset: undefined, type: 'unknown' }
|
|
397
|
+
const timings = { registeredAt: now(), reportedAt: undefined, fetchStart: 0, fetchEnd: 0, scriptStart: 0, scriptEnd: 0, asset: undefined, type: 'unknown', totalWeight: 0, renderBlocking: undefined }
|
|
230
398
|
const stack = getDeepStackTrace()
|
|
231
399
|
if (!stack) return timings
|
|
232
400
|
|
|
@@ -266,9 +434,8 @@ export function findScriptTimings (target) {
|
|
|
266
434
|
}
|
|
267
435
|
|
|
268
436
|
// A correlation can be reused across multiple `register()` calls for the same script URL (e.g. an SPA
|
|
269
|
-
// remounting the same MFE without the script
|
|
270
|
-
//
|
|
271
|
-
// below can ignore the stale data instead of reporting it as if it were fresh.
|
|
437
|
+
// remounting the same MFE without the script reloading) -- its dom/performance timings would then describe
|
|
438
|
+
// the *original* load. Detect that so scriptStart/scriptEnd below can ignore the stale data.
|
|
272
439
|
const correlation = timings.correlation
|
|
273
440
|
const alreadyClaimedByThisMFE = !!mfeId && !!correlation?.claimedBy.has(mfeId)
|
|
274
441
|
if (correlation && mfeId) correlation.claimedBy.add(mfeId)
|
|
@@ -279,17 +446,42 @@ export function findScriptTimings (target) {
|
|
|
279
446
|
return staleness > CORRELATION_STALE_THRESHOLD_MS
|
|
280
447
|
}
|
|
281
448
|
|
|
282
|
-
//
|
|
283
|
-
//
|
|
449
|
+
// Only reached for a real (non-inline), stack-attributable script -- scriptStart/scriptEnd become live getters
|
|
450
|
+
// below, so manifest widening needs a hook that composes with them instead of overriding them (see
|
|
451
|
+
// recordManifestScriptWindow's doc comment). Every other path keeps getOrCreateInternals' plain-value-widening
|
|
452
|
+
// default, which is already correct there.
|
|
453
|
+
let manifestScriptStart = 0
|
|
454
|
+
let manifestScriptEnd = 0
|
|
455
|
+
/**
|
|
456
|
+
* Widens the running manifestScriptStart/manifestScriptEnd accumulators with one asset's correlation timing.
|
|
457
|
+
* Never shrinks either bound; a falsy (unresolved) start/end is ignored.
|
|
458
|
+
* @type {RecordManifestScriptWindowFn}
|
|
459
|
+
*/
|
|
460
|
+
getOrCreateInternals(timings).recordManifestScriptWindow = (start, end) => {
|
|
461
|
+
if (start) manifestScriptStart = manifestScriptStart > 0 ? Math.min(manifestScriptStart, start) : start
|
|
462
|
+
if (end) manifestScriptEnd = manifestScriptEnd > 0 ? Math.max(manifestScriptEnd, end) : end
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Getters, since correlation data may still arrive after this function returns -- we want the freshest value
|
|
466
|
+
// at harvest time. Non-stale: fall back to fetchEnd (best approximation) if correlation isn't available yet.
|
|
467
|
+
// Stale: fall back to registeredAt, since fetchEnd would derive from the same stale correlation. Manifest
|
|
468
|
+
// widening is re-read on every access rather than baked in once, so it composes with a correlation that
|
|
469
|
+
// resolves later.
|
|
284
470
|
Object.defineProperty(
|
|
285
471
|
timings,
|
|
286
472
|
'scriptStart',
|
|
287
|
-
timingFactory(() =>
|
|
473
|
+
timingFactory(() => {
|
|
474
|
+
const ownStart = isCorrelationStale() ? timings.registeredAt : (correlation?.script.start ?? timings.fetchEnd)
|
|
475
|
+
return manifestScriptStart > 0 ? Math.min(ownStart, manifestScriptStart) : ownStart
|
|
476
|
+
})
|
|
288
477
|
)
|
|
289
478
|
Object.defineProperty(
|
|
290
479
|
timings,
|
|
291
480
|
'scriptEnd',
|
|
292
|
-
timingFactory(() =>
|
|
481
|
+
timingFactory(() => {
|
|
482
|
+
const ownEnd = isCorrelationStale() ? timings.registeredAt : (correlation?.script.end ?? timings.registeredAt)
|
|
483
|
+
return manifestScriptEnd > 0 ? Math.max(ownEnd, manifestScriptEnd) : ownEnd
|
|
484
|
+
})
|
|
293
485
|
)
|
|
294
486
|
} catch (error) {
|
|
295
487
|
// Don't let stack parsing errors break anything
|