@pie-players/pie-players-shared 0.3.74 → 1.0.0-beta.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 (86) hide show
  1. package/README.md +88 -75
  2. package/dist/components/vendor/nds/README.md +4 -5
  3. package/dist/config/profile.d.ts +1 -1
  4. package/dist/config/profile.js +1 -1
  5. package/dist/i18n/index.d.ts +1 -1
  6. package/dist/i18n/index.js +1 -1
  7. package/dist/i18n/language-tags.d.ts +9 -1
  8. package/dist/i18n/language-tags.js +11 -1
  9. package/dist/i18n/messages/en-US.d.ts +33 -28
  10. package/dist/i18n/messages/en-US.js +34 -29
  11. package/dist/i18n/messages/nl-NL.d.ts +33 -28
  12. package/dist/i18n/messages/nl-NL.js +34 -29
  13. package/dist/i18n/provider.d.ts +14 -7
  14. package/dist/i18n/provider.js +26 -29
  15. package/dist/i18n/types.d.ts +4 -5
  16. package/dist/index.d.ts +1 -1
  17. package/dist/index.js +0 -1
  18. package/dist/instrumentation/providers/BaseInstrumentationProvider.js +0 -14
  19. package/dist/loader-config.d.ts +0 -2
  20. package/dist/loader-config.js +0 -2
  21. package/dist/loaders/element-loader.js +13 -6
  22. package/dist/loaders/esm-adapter.d.ts +11 -0
  23. package/dist/loaders/esm-adapter.js +74 -45
  24. package/dist/loaders/iife-adapter.d.ts +3 -3
  25. package/dist/loaders/iife-adapter.js +36 -35
  26. package/dist/loaders/index.d.ts +4 -2
  27. package/dist/loaders/index.js +2 -1
  28. package/dist/loaders/mathjax-events.d.ts +22 -0
  29. package/dist/loaders/mathjax-events.js +65 -0
  30. package/dist/loaders/preloaded-registration.d.ts +18 -2
  31. package/dist/loaders/preloaded-registration.js +14 -2
  32. package/dist/media/index.d.ts +4 -6
  33. package/dist/media/index.js +6 -8
  34. package/dist/object/index.d.ts +8 -3
  35. package/dist/object/index.js +8 -6
  36. package/dist/pie/asset-handler.d.ts +0 -17
  37. package/dist/pie/asset-handler.js +1 -1
  38. package/dist/pie/authoring.js +5 -7
  39. package/dist/pie/elements-rendered.d.ts +23 -0
  40. package/dist/pie/elements-rendered.js +96 -0
  41. package/dist/pie/index.d.ts +4 -6
  42. package/dist/pie/index.js +4 -7
  43. package/dist/pie/initialization.d.ts +13 -14
  44. package/dist/pie/initialization.js +61 -75
  45. package/dist/pie/instrumentation-event-bridge.js +23 -1
  46. package/dist/pie/instrumentation-event-map.js +11 -18
  47. package/dist/pie/item-controller.js +4 -0
  48. package/dist/pie/item-session-contract.js +7 -9
  49. package/dist/pie/logger.d.ts +6 -0
  50. package/dist/pie/logger.js +10 -0
  51. package/dist/pie/math-assets.d.ts +48 -0
  52. package/dist/pie/math-assets.js +81 -0
  53. package/dist/pie/resource-monitor.d.ts +34 -32
  54. package/dist/pie/resource-monitor.js +152 -231
  55. package/dist/pie/scoring.d.ts +1 -6
  56. package/dist/pie/scoring.js +3 -8
  57. package/dist/pie/session-commit.js +1 -1
  58. package/dist/pie/stages.d.ts +11 -47
  59. package/dist/pie/stages.js +11 -47
  60. package/dist/pie/updates.js +5 -4
  61. package/dist/pie/utils.d.ts +0 -2
  62. package/dist/pie/utils.js +0 -2
  63. package/dist/security/sanitize-item-markup.d.ts +8 -4
  64. package/dist/security/sanitize-item-markup.js +52 -6
  65. package/dist/security/sanitize-svg-icon.js +2 -2
  66. package/dist/security/wrap-overwide-images.d.ts +1 -1
  67. package/dist/security/wrap-overwide-images.js +20 -1
  68. package/dist/security/wrap-overwide.d.ts +8 -0
  69. package/dist/security/wrap-overwide.js +47 -3
  70. package/dist/timed-media/media-element-source.js +1 -2
  71. package/dist/types/index.d.ts +88 -153
  72. package/dist/types/index.js +2 -2
  73. package/dist/ui/attribute-coercion.d.ts +5 -0
  74. package/dist/ui/attribute-coercion.js +6 -4
  75. package/dist/ui/content-styles.d.ts +6 -10
  76. package/dist/ui/content-styles.js +8 -13
  77. package/dist/ui/first-focusable.d.ts +0 -1
  78. package/dist/ui/first-focusable.js +0 -1
  79. package/dist/ui/focus-trap.js +2 -3
  80. package/dist/ui/scope-css.d.ts +23 -26
  81. package/dist/ui/scope-css.js +23 -26
  82. package/package.json +6 -4
  83. package/dist/loaders/mathjax-version-conflict.d.ts +0 -17
  84. package/dist/loaders/mathjax-version-conflict.js +0 -51
  85. package/dist/pie/stage-tracker.d.ts +0 -50
  86. package/dist/pie/stage-tracker.js +0 -105
@@ -1,15 +1,7 @@
1
1
  export const TOOLKIT_INSTRUMENTATION_EVENT_MAP = [
2
- // M6 canonical readiness vocabulary. The four-stage canonical list
3
- // (`composed`, `engine-ready`, `interactive`, `disposed`) is identical
4
- // across the toolkit CE and the layout CEs — the M6 retro removed the
5
- // `attached`, `runtime-bound`, and `ui-rendered` stages because they
6
- // had zero internal or external consumers. Hosts listen for the
7
- // DOM-prefixed name directly (`pie-stage-change`); the instrumentation
8
- // bridge forwards the same name to telemetry.
9
- {
10
- sourceEventName: "pie-stage-change",
11
- instrumentationEventName: "pie-toolkit-stage-change",
12
- },
2
+ // The toolkit emits no stage events: the section player's runtime
3
+ // engine dispatches `pie-stage-change` on the layout element, above the
4
+ // toolkit, and the section map tracks it there.
13
5
  {
14
6
  sourceEventName: "runtime-owned",
15
7
  instrumentationEventName: "pie-toolkit-runtime-owned",
@@ -18,6 +10,10 @@ export const TOOLKIT_INSTRUMENTATION_EVENT_MAP = [
18
10
  sourceEventName: "runtime-inherited",
19
11
  instrumentationEventName: "pie-toolkit-runtime-inherited",
20
12
  },
13
+ {
14
+ sourceEventName: "runtime-ready",
15
+ instrumentationEventName: "pie-toolkit-runtime-ready",
16
+ },
21
17
  {
22
18
  sourceEventName: "toolkit-ready",
23
19
  instrumentationEventName: "pie-toolkit-ready",
@@ -32,13 +28,10 @@ export const TOOLKIT_INSTRUMENTATION_EVENT_MAP = [
32
28
  },
33
29
  ];
34
30
  export const SECTION_INSTRUMENTATION_EVENT_MAP = [
35
- // M6 canonical readiness vocabulary. Hosts listen for the DOM-prefixed
36
- // names directly (`pie-stage-change`, `pie-loading-complete`); the
37
- // instrumentation bridge forwards the same names to telemetry. The
38
- // readiness alias mappings (`readiness-change`, `interaction-ready`,
39
- // `ready`) and the `section-controller-ready` mapping were removed
40
- // alongside their DOM-event surfaces. `session-changed` stays off the
41
- // bridge for the reason the item map gives below.
31
+ // The runtime engine dispatches `pie-stage-change` and
32
+ // `pie-loading-complete` on the layout element; `framework-error`
33
+ // bubbles to it from the player's toolkit. `session-changed` stays off
34
+ // the bridge for the reason the item map gives below.
42
35
  {
43
36
  sourceEventName: "pie-stage-change",
44
37
  instrumentationEventName: "pie-section-stage-change",
@@ -45,7 +45,11 @@ export class ItemController {
45
45
  setSession(input, options = {}) {
46
46
  const next = normalizeSessionContainer(input, this.sessionId);
47
47
  const allowMetadataOverwrite = options.allowMetadataOverwrite ?? false;
48
+ // The guard keeps a response from metadata churn on the same session. A
49
+ // session with another id is a new attempt or item, and replaces it.
50
+ const sameSession = next.id === "" || this.session.id === "" || next.id === this.session.id;
48
51
  if (!allowMetadataOverwrite &&
52
+ sameSession &&
49
53
  hasResponseValue(this.session) &&
50
54
  !hasResponseValue(next) &&
51
55
  !hasResponseField(next)) {
@@ -1,3 +1,4 @@
1
+ import { isPlainRecord } from "../object/index.js";
1
2
  const DEFAULT_SESSION_ID = "";
2
3
  const METADATA_ONLY_SESSION_KEYS = new Set([
3
4
  "complete",
@@ -146,9 +147,6 @@ export function hasResponseField(value) {
146
147
  }
147
148
  return false;
148
149
  }
149
- function isPlainObject(value) {
150
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
151
- }
152
150
  function getMetadataComponent(sessionDetail, candidate) {
153
151
  if (typeof sessionDetail.component === "string")
154
152
  return sessionDetail.component;
@@ -194,7 +192,7 @@ function mergeSessionEntry(existing, incoming) {
194
192
  for (const [key, value] of Object.entries(incoming)) {
195
193
  const previous = merged[key];
196
194
  merged[key] =
197
- isPlainObject(previous) && isPlainObject(value)
195
+ isPlainRecord(previous) && isPlainRecord(value)
198
196
  ? mergeSessionEntry(previous, value)
199
197
  : value;
200
198
  }
@@ -334,7 +332,7 @@ function classifyItemSessionChange(args) {
334
332
  };
335
333
  }
336
334
  function hostContainerEntries(hostContainer) {
337
- if (!isPlainObject(hostContainer)) {
335
+ if (!isPlainRecord(hostContainer)) {
338
336
  return null;
339
337
  }
340
338
  // A frozen container is not a live view of anything, and a shared immutable
@@ -355,7 +353,7 @@ function hostContainerEntries(hostContainer) {
355
353
  return null;
356
354
  }
357
355
  function sessionEntryId(value) {
358
- if (!isPlainObject(value)) {
356
+ if (!isPlainRecord(value)) {
359
357
  return null;
360
358
  }
361
359
  const id = value.id;
@@ -405,7 +403,7 @@ export function ensureHostSessionEntries(hostContainer, entryIds) {
405
403
  */
406
404
  export function projectSessionIntoHostContainer(hostContainer, liveSession) {
407
405
  const entries = hostContainerEntries(hostContainer);
408
- if (!entries || !isPlainObject(hostContainer)) {
406
+ if (!entries || !isPlainRecord(hostContainer)) {
409
407
  return false;
410
408
  }
411
409
  const live = normalizeItemSessionContainer(liveSession);
@@ -417,11 +415,11 @@ export function projectSessionIntoHostContainer(hostContainer, liveSession) {
417
415
  }
418
416
  for (const nextEntry of live.data) {
419
417
  const id = sessionEntryId(nextEntry);
420
- if (!id || !isPlainObject(nextEntry)) {
418
+ if (!id || !isPlainRecord(nextEntry)) {
421
419
  continue;
422
420
  }
423
421
  const target = entries.find((entry) => sessionEntryId(entry) === id);
424
- if (!isPlainObject(target)) {
422
+ if (!isPlainRecord(target)) {
425
423
  entries.push({ ...nextEntry });
426
424
  wrote = true;
427
425
  continue;
@@ -61,3 +61,9 @@ declare global {
61
61
  * Check if debug mode is enabled globally
62
62
  */
63
63
  export declare function isGlobalDebugEnabled(): boolean;
64
+ /**
65
+ * Read-aloud's debug flag: `PIE_TTS_DEBUG=1` in the environment, or
66
+ * `globalThis.__PIE_TTS_DEBUG__ = true` in a page. Every read-aloud logger is
67
+ * created with it, and reads it on each debug line.
68
+ */
69
+ export declare function isTtsDebugEnabled(): boolean;
@@ -42,3 +42,13 @@ export function createPieLogger(namespace, debugEnabledFn) {
42
42
  export function isGlobalDebugEnabled() {
43
43
  return typeof window !== "undefined" && window.PIE_DEBUG === true;
44
44
  }
45
+ /**
46
+ * Read-aloud's debug flag: `PIE_TTS_DEBUG=1` in the environment, or
47
+ * `globalThis.__PIE_TTS_DEBUG__ = true` in a page. Every read-aloud logger is
48
+ * created with it, and reads it on each debug line.
49
+ */
50
+ export function isTtsDebugEnabled() {
51
+ const scope = globalThis;
52
+ return (scope.process?.env?.PIE_TTS_DEBUG === "1" ||
53
+ scope.__PIE_TTS_DEBUG__ === true);
54
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Where the MathJax adapter's copies load MathJax's fonts, speech worker and,
3
+ * on its npm build, MathJax itself from, and whether math is in the tab order.
4
+ * Every copy of `@pie-element/shared-math-rendering-mathjax` reads these from
5
+ * the legacy renderer's page options as it starts MathJax, on its first render,
6
+ * so they are set before any element renders. See
7
+ * https://github.com/pie-framework/pie-elements-ng/blob/develop/docs/MATH-RENDERING.md#assets
8
+ */
9
+ export interface MathAssetOptions {
10
+ /**
11
+ * An npm root: a URL under which `<package>@<version>/<path>` serves that
12
+ * file of the package, such as `https://cdn.jsdelivr.net/npm`. A relative URL
13
+ * resolves against the page.
14
+ */
15
+ assetRoot?: string | URL;
16
+ /**
17
+ * The directory of `speech-worker.js` and its `mathmaps/`, by default
18
+ * `mathjax@<version>/sre` under the asset root.
19
+ */
20
+ speechPath?: string | URL;
21
+ /**
22
+ * The URL of individual files by npm path, `<package>@<version>/<path>`: a
23
+ * listed file loads from its URL, the rest from the asset root. A bundle lists
24
+ * each file it ships as `new URL("./…", import.meta.url)`, which its host's
25
+ * bundler emits, and needs no root. Read by the adapter's browser build, for
26
+ * its fonts, speech worker and speech rules.
27
+ */
28
+ assetUrls?: Readonly<Record<string, string | URL>>;
29
+ /**
30
+ * The speech locales the MathJax menu lists, by id (`["en", "es"]`), or by id
31
+ * with the label it shows. Unset, it lists every locale SRE ships.
32
+ */
33
+ speechLocales?: readonly string[] | Readonly<Record<string, string>>;
34
+ /**
35
+ * Puts typeset math in the keyboard tab order, for the MathJax menu and its
36
+ * explorer. Off by default.
37
+ */
38
+ inTabOrder?: boolean;
39
+ }
40
+ /** Why `options` is not a `MathAssetOptions`, or `undefined` when it is. */
41
+ export declare function mathAssetOptionsError(options: unknown): string | undefined;
42
+ /**
43
+ * Writes `options` to the page options, keeping the ones it leaves unset and
44
+ * the files of `assetUrls` it does not list.
45
+ */
46
+ export declare function setMathAssetOptions(options: MathAssetOptions): void;
47
+ /** The asset root the page options set, if any. */
48
+ export declare function pageMathAssetRoot(): string | undefined;
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Where the MathJax adapter's copies load MathJax's fonts, speech worker and,
3
+ * on its npm build, MathJax itself from, and whether math is in the tab order.
4
+ * Every copy of `@pie-element/shared-math-rendering-mathjax` reads these from
5
+ * the legacy renderer's page options as it starts MathJax, on its first render,
6
+ * so they are set before any element renders. See
7
+ * https://github.com/pie-framework/pie-elements-ng/blob/develop/docs/MATH-RENDERING.md#assets
8
+ */
9
+ /** The legacy renderer's page options, which the adapter reads as well. */
10
+ const PAGE_OPTIONS_KEY = "@pie-lib/math-rendering@2";
11
+ const isUrl = (value) => (typeof value === "string" && value !== "") || value instanceof URL;
12
+ /** Why `options` is not a `MathAssetOptions`, or `undefined` when it is. */
13
+ export function mathAssetOptionsError(options) {
14
+ if (!options || typeof options !== "object" || Array.isArray(options)) {
15
+ return "must be an object";
16
+ }
17
+ const { assetRoot, speechPath, assetUrls, speechLocales, inTabOrder } = options;
18
+ if (assetRoot !== undefined && !isUrl(assetRoot)) {
19
+ return `assetRoot must be a URL, got ${JSON.stringify(assetRoot)}`;
20
+ }
21
+ if (speechPath !== undefined && !isUrl(speechPath)) {
22
+ return `speechPath must be a URL, got ${JSON.stringify(speechPath)}`;
23
+ }
24
+ if (assetUrls !== undefined &&
25
+ (!assetUrls ||
26
+ typeof assetUrls !== "object" ||
27
+ Array.isArray(assetUrls) ||
28
+ !Object.values(assetUrls).every(isUrl))) {
29
+ return "assetUrls must map npm paths to URLs";
30
+ }
31
+ if (speechLocales !== undefined) {
32
+ const listed = Array.isArray(speechLocales)
33
+ ? speechLocales
34
+ : speechLocales && typeof speechLocales === "object"
35
+ ? Object.values(speechLocales)
36
+ : null;
37
+ if (!listed || listed.some((entry) => typeof entry !== "string" || !entry)) {
38
+ return "speechLocales must be locale ids, or locale ids mapped to their labels";
39
+ }
40
+ }
41
+ if (inTabOrder !== undefined && typeof inTabOrder !== "boolean") {
42
+ return `inTabOrder must be a boolean, got ${JSON.stringify(inTabOrder)}`;
43
+ }
44
+ return undefined;
45
+ }
46
+ /**
47
+ * Writes `options` to the page options, keeping the ones it leaves unset and
48
+ * the files of `assetUrls` it does not list.
49
+ */
50
+ export function setMathAssetOptions(options) {
51
+ if (typeof window === "undefined")
52
+ return;
53
+ const host = window;
54
+ const page = (host[PAGE_OPTIONS_KEY] ??= {});
55
+ const opts = (page.opts ??= {});
56
+ if (options.assetRoot !== undefined)
57
+ opts.assetRoot = String(options.assetRoot);
58
+ if (options.speechPath !== undefined)
59
+ opts.speechPath = String(options.speechPath);
60
+ if (options.assetUrls !== undefined) {
61
+ const listed = opts.assetUrls && typeof opts.assetUrls === "object" ? opts.assetUrls : {};
62
+ opts.assetUrls = {
63
+ ...listed,
64
+ ...Object.fromEntries(Object.entries(options.assetUrls).map(([path, url]) => [path, String(url)])),
65
+ };
66
+ }
67
+ if (options.speechLocales !== undefined) {
68
+ opts.speechLocales = Array.isArray(options.speechLocales)
69
+ ? [...options.speechLocales]
70
+ : { ...options.speechLocales };
71
+ }
72
+ if (options.inTabOrder !== undefined)
73
+ opts.inTabOrder = options.inTabOrder;
74
+ }
75
+ /** The asset root the page options set, if any. */
76
+ export function pageMathAssetRoot() {
77
+ if (typeof window === "undefined")
78
+ return undefined;
79
+ const root = window[PAGE_OPTIONS_KEY]?.opts?.assetRoot;
80
+ return typeof root === "string" && root ? root : undefined;
81
+ }
@@ -69,7 +69,9 @@ export interface MediaRetryReadyDetail extends ResourceMonitorEventDetail {
69
69
  mediaTag: "audio" | "video";
70
70
  }
71
71
  /**
72
- * Tracks resource loads and provides retry capability
72
+ * Tracks the resource loads of one container and retries failed ones with
73
+ * exponential backoff and cache-busting URLs. The events it dispatches are
74
+ * listed in the event contract above.
73
75
  */
74
76
  export declare class ResourceMonitor {
75
77
  private config;
@@ -77,13 +79,20 @@ export declare class ResourceMonitor {
77
79
  private observer;
78
80
  private mutationObserver;
79
81
  private errorHandler;
82
+ /** Retries scheduled so far, keyed by the URL without retry parameters. */
80
83
  private retryAttempts;
84
+ /** Elements that failed to load each URL; a retry reloads the first one. */
81
85
  private retryTargets;
82
86
  private container;
83
- private isBrowser;
87
+ private readonly isBrowser;
88
+ /** URLs referenced inside the container; timing entries for others are ignored. */
84
89
  private containerResources;
85
90
  private provider;
86
91
  private started;
92
+ /**
93
+ * Bumped on every start and stop, so timers and listeners captured in an
94
+ * earlier lifecycle see themselves as stale.
95
+ */
87
96
  private lifecycleVersion;
88
97
  private pendingRetryTimers;
89
98
  private retryInFlight;
@@ -116,13 +125,10 @@ export declare class ResourceMonitor {
116
125
  * Start monitoring resources in the given container
117
126
  */
118
127
  start(container: HTMLElement): void;
119
- /**
120
- * Stop monitoring and clean up
121
- */
122
128
  stop(): void;
123
129
  /**
124
- * Set up MutationObserver to track DOM changes within container
125
- * This allows us to know which resources belong to our container
130
+ * Tracks resources added to the container, or retargeted inside it, so
131
+ * timing entries can be attributed to this container.
126
132
  */
127
133
  private setupMutationObserver;
128
134
  /**
@@ -137,45 +143,38 @@ export declare class ResourceMonitor {
137
143
  * Set up PerformanceObserver to track resource loading timing
138
144
  */
139
145
  private setupPerformanceObserver;
140
- /**
141
- * Handle resource timing entry
142
- */
143
146
  private handleResourceTiming;
147
+ private logResourceTiming;
144
148
  private isLifecycleActive;
145
149
  private clearRetryTracking;
146
150
  private clearPendingMediaCheck;
147
151
  private getRetryTargets;
148
152
  private getPrimaryRetryTarget;
149
- /**
150
- * Handle successful resource load
151
- */
152
153
  private handleSuccessfulLoad;
153
154
  private resolveMediaTarget;
154
155
  private resolveMediaTargetsForUrl;
155
156
  private normalizeRetrySuccessResourceType;
156
157
  private isMediaRetryRecovered;
157
158
  private finalizeRetrySuccess;
159
+ /**
160
+ * A retried media fetch that succeeds does not mean the element recovered:
161
+ * it can still hold an error or no source. Success waits until a media
162
+ * target reports metadata, checked on `loadedmetadata`/`canplay` and by
163
+ * polling, and the reconciliation timeout bounds the wait.
164
+ */
158
165
  private reconcileMediaRetrySuccess;
166
+ private completeMediaRetry;
159
167
  private handleMediaRetryReconciliationTimeout;
160
168
  private dispatchMediaRetryReady;
161
- /**
162
- * Handle failed resource load
163
- */
164
169
  private handleFailedLoad;
165
170
  /**
166
- * Check if resource is relevant to our container
167
- * Uses container-scoped tracking via MutationObserver
168
- * Also retroactively checks if resource belongs to container if not yet tracked
171
+ * Whether a timing entry belongs to this container. Timing entries carry
172
+ * absolute URLs, so a suffix match against a tracked URL also counts. The
173
+ * buffered observer also reports resources that loaded before the scan
174
+ * tracked them; those are looked up in the container's DOM.
169
175
  */
170
176
  private isRelevantResource;
171
- /**
172
- * Check if a resource URL actually belongs to our container by checking DOM elements
173
- * This is a fallback for resources that loaded before MutationObserver scanned
174
- */
175
177
  private isResourceInContainer;
176
- /**
177
- * Set up error event handler for resource loading failures
178
- */
179
178
  private setupErrorHandler;
180
179
  /**
181
180
  * Check if element is a resource element
@@ -189,18 +188,21 @@ export declare class ResourceMonitor {
189
188
  * Handle permanent resource failure after all retries exhausted
190
189
  */
191
190
  private handlePermanentFailure;
192
- /**
193
- * Log retry schedule information
194
- */
191
+ /** Exponential backoff, capped at `maxRetryDelay`. */
192
+ private retryDelay;
195
193
  private logRetrySchedule;
196
194
  /**
197
- * Retry loading a failed resource with exponential backoff
195
+ * Reloads one element under a cache-busting URL. Media elements also need
196
+ * `load()`, since changing a `<source>` alone does not restart the fetch.
198
197
  */
199
198
  private performRetryLoadAttempt;
200
- private retryResourceLoad;
201
199
  /**
202
- * Dispatch a custom event from the container
200
+ * Schedules the next retry of a failed resource with exponential backoff,
201
+ * or fails it permanently once `maxRetries` is spent. Every failing element
202
+ * joins the URL's retry targets; concurrent errors for one URL share the
203
+ * retry already in flight.
203
204
  */
205
+ private retryResourceLoad;
204
206
  private dispatchEvent;
205
207
  /**
206
208
  * Get current retry statistics