@microsoft/webui-framework 0.0.25 → 0.0.26
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/README.md +209 -53
- package/dist/component-asset/asset.d.ts +4 -2
- package/dist/component-asset/asset.js +19 -3
- package/dist/component-asset/generated-manifest.d.ts +1 -0
- package/dist/component-asset/generated-manifest.js +32 -0
- package/dist/component-asset/loader.d.ts +2 -1
- package/dist/component-asset/loader.js +98 -22
- package/dist/component-asset/manifest.d.ts +2 -1
- package/dist/component-asset.d.ts +1 -1
- package/dist/component-asset.js +12 -2
- package/dist/element/link-styles.d.ts +20 -0
- package/dist/element/link-styles.js +1030 -0
- package/dist/element/markers.d.ts +4 -0
- package/dist/element/markers.js +60 -5
- package/dist/element/styles.d.ts +39 -1
- package/dist/element/styles.js +623 -13
- package/dist/element/types.d.ts +5 -3
- package/dist/element.js +9 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/interaction-hydration.d.ts +8 -0
- package/dist/interaction-hydration.js +167 -0
- package/dist/static-host.js +43 -1
- package/dist/streaming-activation.d.ts +2 -2
- package/dist/streaming-activation.js +4 -2
- package/dist/streaming-bootstrap.d.ts +2 -2
- package/dist/streaming-bootstrap.js +7 -1
- package/dist/streaming-cleanup.d.ts +1 -0
- package/dist/streaming-cleanup.js +66 -14
- package/dist/streaming-coordinator.d.ts +3 -2
- package/dist/streaming-coordinator.js +107 -47
- package/dist/streaming-deferred.d.ts +14 -4
- package/dist/streaming-deferred.js +200 -87
- package/dist/streaming-dom.d.ts +12 -3
- package/dist/streaming-dom.js +41 -27
- package/dist/streaming-mode.d.ts +5 -0
- package/dist/streaming-mode.js +4 -0
- package/dist/streaming-protocol.d.ts +19 -3
- package/dist/streaming-protocol.js +4 -3
- package/dist/streaming-spans.d.ts +9 -0
- package/dist/streaming-spans.js +200 -0
- package/dist/template-content.d.ts +17 -0
- package/dist/template-content.js +106 -0
- package/dist/template-element.d.ts +17 -2
- package/dist/template-element.js +493 -177
- package/dist/template-events.d.ts +7 -0
- package/dist/template-events.js +7 -1
- package/dist/template-types.d.ts +1 -2
- package/dist/template.d.ts +7 -1
- package/dist/template.js +79 -16
- package/package.json +9 -3
- package/dist/component-asset/resources.d.ts +0 -4
- package/dist/component-asset/resources.js +0 -83
|
@@ -2,10 +2,11 @@ import { abortStreamingGate, markBoundaryCommitted, markBoundaryPending, } from
|
|
|
2
2
|
import { activateRootsBetween, } from './streaming-activation.js';
|
|
3
3
|
import { applyBoundaryBootstrap } from './streaming-bootstrap.js';
|
|
4
4
|
import { abandonDeferredDocumentRoots, abandonDeferredRange, } from './streaming-cleanup.js';
|
|
5
|
-
import { abandonPendingWaiters, configureStreamingFailureHandler, elementHasPendingStateForTests, pendingTagWaiterCountForTests, pendingUndefinedRootCountForTests, resetDeferredActivationForTests, } from './streaming-deferred.js';
|
|
6
|
-
import { findBoundaryScript,
|
|
7
|
-
import { parseBoundaryEnvelope, RECORD_KIND_STATE_UPDATE, RECORD_KIND_TERMINAL, RECORD_KIND_UPDATABLE_CHECKPOINT, } from './streaming-protocol.js';
|
|
5
|
+
import { abandonPendingWaiters, configureStreamingFailureHandler, elementHasPendingStateForTests, pendingBarrierRootCountForTests, pendingTagWaiterCountForTests, pendingUndefinedRootCountForTests, resetDeferredActivationForTests, } from './streaming-deferred.js';
|
|
6
|
+
import { findBoundaryScript, findRangeEndMarkerByPrefix, findRangeStartMarkerByPrefix, markerlessRecordViolation, removeBoundaryScaffolding, resolveBoundaryRange, resolveSpanRange, streamingErrorMessage, } from './streaming-dom.js';
|
|
7
|
+
import { parseBoundaryEnvelope, RECORD_KIND_FINAL_CHECKPOINT, RECORD_KIND_SPAN_COMPLETION, RECORD_KIND_STATE_UPDATE, RECORD_KIND_TERMINAL, RECORD_KIND_UPDATABLE_CHECKPOINT, } from './streaming-protocol.js';
|
|
8
8
|
import { applyStateUpdate } from './streaming-state.js';
|
|
9
|
+
import { abandonOpenSpans, completeSpan, hasOpenSpans, openSpanCountForTests, prepareSpanCompletion, registerEnclosingSpans, spanHostFor, } from './streaming-spans.js';
|
|
9
10
|
const MAX_QUEUED_BOUNDARIES = 512;
|
|
10
11
|
const MAX_UPDATABLE_BOUNDARIES = 128;
|
|
11
12
|
const MAX_RETAINED_UPDATE_ROOTS = 50_000;
|
|
@@ -13,6 +14,7 @@ const BOUNDARY_HYDRATED_EVENT = 'webui:boundary-hydrated';
|
|
|
13
14
|
const BOUNDARY_MARK_PREFIX = 'webui:boundary:';
|
|
14
15
|
const UPDATE_MARK_SUFFIX = ':update';
|
|
15
16
|
const TERMINAL_MARK = 'webui:streaming:terminal';
|
|
17
|
+
const SPAN_MARK_PREFIX = 'webui:span:';
|
|
16
18
|
const perf = globalThis.performance;
|
|
17
19
|
const queue = [];
|
|
18
20
|
let queueHead = 0;
|
|
@@ -20,7 +22,7 @@ let pumpScheduled = false;
|
|
|
20
22
|
let slicedDrainActive = false;
|
|
21
23
|
let halted = false;
|
|
22
24
|
let nextExpectedRecordSequence = 0;
|
|
23
|
-
let
|
|
25
|
+
let nextExpectedBoundaryInstanceId = 0;
|
|
24
26
|
let terminalCommitted = false;
|
|
25
27
|
let pendingTerminalSequence = null;
|
|
26
28
|
let terminalValidationScheduled = false;
|
|
@@ -106,6 +108,7 @@ function fail(reason) {
|
|
|
106
108
|
console.error(`[WebUI] streaming hydration halted: ${reason}.`);
|
|
107
109
|
abortStreamingGate();
|
|
108
110
|
abandonPendingWaiters();
|
|
111
|
+
abandonOpenSpans();
|
|
109
112
|
clearUpdatableBoundaries();
|
|
110
113
|
settlePendingTerminal(false);
|
|
111
114
|
for (let i = queueHead; i < queue.length; i++) {
|
|
@@ -124,9 +127,9 @@ function discardRejectedBoundary(sentinel) {
|
|
|
124
127
|
let endMarker = null;
|
|
125
128
|
let startMarker = null;
|
|
126
129
|
if (scriptEl) {
|
|
127
|
-
endMarker =
|
|
130
|
+
endMarker = findRangeEndMarkerByPrefix(scriptEl);
|
|
128
131
|
if (endMarker)
|
|
129
|
-
startMarker =
|
|
132
|
+
startMarker = findRangeStartMarkerByPrefix(endMarker);
|
|
130
133
|
}
|
|
131
134
|
if (startMarker && endMarker) {
|
|
132
135
|
abandonDeferredRange(startMarker, endMarker);
|
|
@@ -153,7 +156,22 @@ function processSentinel(sentinel) {
|
|
|
153
156
|
failBoundary(sentinel, `expected streaming record sequence ${nextExpectedRecordSequence}, received ${sequence}`);
|
|
154
157
|
return;
|
|
155
158
|
}
|
|
156
|
-
if (kind === RECORD_KIND_STATE_UPDATE) {
|
|
159
|
+
if (kind === RECORD_KIND_STATE_UPDATE || kind === RECORD_KIND_TERMINAL) {
|
|
160
|
+
const terminal = kind === RECORD_KIND_TERMINAL;
|
|
161
|
+
const violation = markerlessRecordViolation(scriptEl, terminal ? 'terminal' : 'state update');
|
|
162
|
+
if (violation) {
|
|
163
|
+
failBoundary(sentinel, violation);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (terminal) {
|
|
167
|
+
if (hasOpenSpans()) {
|
|
168
|
+
failBoundary(sentinel, 'terminal record arrived before every component span completed');
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
nextExpectedRecordSequence++;
|
|
172
|
+
commitTerminal(sequence, sentinel, scriptEl);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
157
175
|
const boundary = updatableBoundaries.get(target);
|
|
158
176
|
if (!boundary) {
|
|
159
177
|
failBoundary(sentinel, `state update targets boundary ${target}, which is not committed as updatable`);
|
|
@@ -163,71 +181,105 @@ function processSentinel(sentinel) {
|
|
|
163
181
|
commitStateUpdate(boundary, payload, target, sequence, sentinel, scriptEl);
|
|
164
182
|
return;
|
|
165
183
|
}
|
|
166
|
-
if (kind ===
|
|
167
|
-
const resolved =
|
|
184
|
+
if (kind === RECORD_KIND_SPAN_COMPLETION) {
|
|
185
|
+
const resolved = resolveSpanRange(scriptEl, target);
|
|
168
186
|
if (!resolved.ok) {
|
|
169
|
-
|
|
187
|
+
failRangeResolution(sentinel, scriptEl, resolved);
|
|
170
188
|
return;
|
|
171
189
|
}
|
|
172
190
|
nextExpectedRecordSequence++;
|
|
173
|
-
|
|
191
|
+
commitRecord(true, payload, resolved.range, sequence, target, false, sentinel, scriptEl);
|
|
174
192
|
return;
|
|
175
193
|
}
|
|
176
|
-
if (
|
|
177
|
-
|
|
194
|
+
if (kind !== RECORD_KIND_FINAL_CHECKPOINT &&
|
|
195
|
+
kind !== RECORD_KIND_UPDATABLE_CHECKPOINT) {
|
|
196
|
+
failBoundary(sentinel, `unsupported streaming record kind ${kind}`);
|
|
178
197
|
return;
|
|
179
198
|
}
|
|
180
|
-
|
|
199
|
+
if (target !== nextExpectedBoundaryInstanceId) {
|
|
200
|
+
failBoundary(sentinel, `expected boundary instance ${nextExpectedBoundaryInstanceId}, received ${target}`);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
const resolved = resolveBoundaryRange(scriptEl, target);
|
|
181
204
|
if (!resolved.ok) {
|
|
182
|
-
|
|
183
|
-
if (resolved.start) {
|
|
184
|
-
abandonDeferredRange(resolved.start, scriptEl);
|
|
185
|
-
}
|
|
186
|
-
removeBoundaryScaffolding(sentinel, scriptEl, resolved.start, null);
|
|
187
|
-
fail(resolved.reason);
|
|
188
|
-
}
|
|
189
|
-
else {
|
|
190
|
-
failBoundary(sentinel, resolved.reason);
|
|
191
|
-
}
|
|
205
|
+
failRangeResolution(sentinel, scriptEl, resolved);
|
|
192
206
|
return;
|
|
193
207
|
}
|
|
194
208
|
nextExpectedRecordSequence++;
|
|
195
|
-
|
|
196
|
-
|
|
209
|
+
nextExpectedBoundaryInstanceId++;
|
|
210
|
+
commitRecord(false, payload, resolved.range, sequence, target, kind === RECORD_KIND_UPDATABLE_CHECKPOINT, sentinel, scriptEl);
|
|
197
211
|
}
|
|
198
|
-
function
|
|
212
|
+
function commitRecord(span, payload, range, sequence, target, updatable, sentinel, scriptEl) {
|
|
199
213
|
markBoundaryPending();
|
|
200
214
|
let committed = false;
|
|
201
215
|
try {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
const boundary = updatable
|
|
205
|
-
? { roots: [], retained: 0, pendingRoots: 0 }
|
|
206
|
-
: undefined;
|
|
207
|
-
activateRootsBetween(range.start, range.end, bootstrap.state, boundary);
|
|
208
|
-
if (boundary)
|
|
209
|
-
retainUpdatableBoundary(target, boundary);
|
|
216
|
+
if (span) {
|
|
217
|
+
hydrateSpanCompletion(payload, range, target);
|
|
210
218
|
}
|
|
211
|
-
else
|
|
212
|
-
|
|
213
|
-
roots: [],
|
|
214
|
-
retained: 0,
|
|
215
|
-
pendingRoots: 0,
|
|
216
|
-
});
|
|
219
|
+
else {
|
|
220
|
+
hydrateCheckpoint(payload, range, target, updatable);
|
|
217
221
|
}
|
|
218
222
|
committed = true;
|
|
219
223
|
}
|
|
220
224
|
catch (error) {
|
|
221
|
-
|
|
225
|
+
const detail = streamingErrorMessage(error);
|
|
226
|
+
fail(span
|
|
227
|
+
? `error completing span ${target}: ${detail}`
|
|
228
|
+
: `error committing boundary ${sequence}: ${detail}`);
|
|
222
229
|
}
|
|
223
230
|
finally {
|
|
224
231
|
removeBoundaryScaffolding(sentinel, scriptEl, range.start, range.end);
|
|
225
232
|
if (committed) {
|
|
226
|
-
notifyCommit(`${BOUNDARY_MARK_PREFIX}${target}`, sequence, 'checkpoint');
|
|
233
|
+
notifyCommit(`${span ? SPAN_MARK_PREFIX : BOUNDARY_MARK_PREFIX}${target}`, sequence, span ? 'span' : 'checkpoint');
|
|
227
234
|
}
|
|
228
235
|
markBoundaryCommitted(false);
|
|
229
236
|
}
|
|
230
237
|
}
|
|
238
|
+
function hydrateCheckpoint(bootstrap, range, target, updatable) {
|
|
239
|
+
let bypass;
|
|
240
|
+
const enclosing = bootstrap.enclosingSpanInstanceId;
|
|
241
|
+
if (enclosing !== undefined && range.start?.parentNode) {
|
|
242
|
+
const invalid = registerEnclosingSpans(range.start.parentNode, enclosing);
|
|
243
|
+
if (invalid)
|
|
244
|
+
throw new Error(invalid);
|
|
245
|
+
const host = spanHostFor(enclosing);
|
|
246
|
+
if (host)
|
|
247
|
+
bypass = { id: String(enclosing), host };
|
|
248
|
+
}
|
|
249
|
+
applyBoundaryBootstrap(bootstrap);
|
|
250
|
+
if (range.start && range.end) {
|
|
251
|
+
const boundary = updatable
|
|
252
|
+
? newUpdatableBoundary()
|
|
253
|
+
: undefined;
|
|
254
|
+
activateRootsBetween(range.start, range.end, bootstrap.state, boundary, bypass);
|
|
255
|
+
if (boundary)
|
|
256
|
+
retainUpdatableBoundary(target, boundary);
|
|
257
|
+
}
|
|
258
|
+
else if (updatable) {
|
|
259
|
+
retainUpdatableBoundary(target, newUpdatableBoundary());
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
function newUpdatableBoundary() {
|
|
263
|
+
return { roots: [], active: true, retained: 0, pendingRoots: 0 };
|
|
264
|
+
}
|
|
265
|
+
function hydrateSpanCompletion(payload, range, target) {
|
|
266
|
+
const invalid = prepareSpanCompletion(target, range);
|
|
267
|
+
if (invalid)
|
|
268
|
+
throw new Error(invalid);
|
|
269
|
+
applyBoundaryBootstrap(payload);
|
|
270
|
+
activateRootsBetween(range.start, range.end, payload.state);
|
|
271
|
+
completeSpan(target);
|
|
272
|
+
}
|
|
273
|
+
function failRangeResolution(sentinel, scriptEl, resolved) {
|
|
274
|
+
if (!resolved.truncated) {
|
|
275
|
+
failBoundary(sentinel, resolved.reason);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
if (resolved.start)
|
|
279
|
+
abandonDeferredRange(resolved.start, scriptEl);
|
|
280
|
+
removeBoundaryScaffolding(sentinel, scriptEl, resolved.start, null);
|
|
281
|
+
fail(resolved.reason);
|
|
282
|
+
}
|
|
231
283
|
function retainUpdatableBoundary(target, boundary) {
|
|
232
284
|
if (updatableBoundaries.size >= MAX_UPDATABLE_BOUNDARIES) {
|
|
233
285
|
throw new Error(`updatable boundary count exceeds ${MAX_UPDATABLE_BOUNDARIES}`);
|
|
@@ -269,10 +321,17 @@ function commitTerminal(sequence, sentinel, scriptEl) {
|
|
|
269
321
|
removeBoundaryScaffolding(sentinel, scriptEl, null, null);
|
|
270
322
|
terminalCommitted = true;
|
|
271
323
|
pendingTerminalSequence = sequence;
|
|
272
|
-
clearUpdatableBoundaries();
|
|
324
|
+
clearUpdatableBoundaries(true);
|
|
273
325
|
scheduleTerminalValidation();
|
|
274
326
|
}
|
|
275
|
-
function clearUpdatableBoundaries() {
|
|
327
|
+
function clearUpdatableBoundaries(preservePendingPatches = false) {
|
|
328
|
+
for (const boundary of updatableBoundaries.values()) {
|
|
329
|
+
boundary.active = false;
|
|
330
|
+
boundary.roots.length = 0;
|
|
331
|
+
if (!preservePendingPatches || boundary.pendingRoots === 0) {
|
|
332
|
+
boundary.patch = undefined;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
276
335
|
updatableBoundaries.clear();
|
|
277
336
|
retainedUpdateRoots = 0;
|
|
278
337
|
}
|
|
@@ -341,6 +400,7 @@ function onDomContentLoaded(generation) {
|
|
|
341
400
|
}
|
|
342
401
|
export function resetStreamingCoordinatorStateForTests() {
|
|
343
402
|
resetDeferredActivationForTests();
|
|
403
|
+
abandonOpenSpans();
|
|
344
404
|
settlePendingTerminal(false);
|
|
345
405
|
clearUpdatableBoundaries();
|
|
346
406
|
coordinatorGeneration++;
|
|
@@ -350,7 +410,7 @@ export function resetStreamingCoordinatorStateForTests() {
|
|
|
350
410
|
slicedDrainActive = false;
|
|
351
411
|
halted = false;
|
|
352
412
|
nextExpectedRecordSequence = 0;
|
|
353
|
-
|
|
413
|
+
nextExpectedBoundaryInstanceId = 0;
|
|
354
414
|
terminalCommitted = false;
|
|
355
415
|
pendingTerminalSequence = null;
|
|
356
416
|
terminalValidationScheduled = false;
|
|
@@ -361,4 +421,4 @@ export function isStreamingHaltedForTests() {
|
|
|
361
421
|
export function streamingRetentionStateForTests() {
|
|
362
422
|
return [updatableBoundaries.size, retainedUpdateRoots];
|
|
363
423
|
}
|
|
364
|
-
export { elementHasPendingStateForTests, pendingTagWaiterCountForTests, pendingUndefinedRootCountForTests, };
|
|
424
|
+
export { elementHasPendingStateForTests, openSpanCountForTests, pendingBarrierRootCountForTests, pendingTagWaiterCountForTests, pendingUndefinedRootCountForTests, };
|
|
@@ -1,16 +1,25 @@
|
|
|
1
|
-
export declare const
|
|
2
|
-
export declare const
|
|
3
|
-
export declare const
|
|
4
|
-
export declare const
|
|
1
|
+
export declare const ELEMENT_IGNORED = 10;
|
|
2
|
+
export declare const ELEMENT_DEFERRED = 11;
|
|
3
|
+
export declare const ELEMENT_LIMIT_FAILURE = 12;
|
|
4
|
+
export declare const ELEMENT_ACTIVATED_FROM_PENDING = 13;
|
|
5
|
+
export declare const ELEMENT_BARRIER_LIMIT_FAILURE = 14;
|
|
6
|
+
export declare const ELEMENT_INVALID_OUTCOME = 15;
|
|
5
7
|
export declare const MAX_PENDING_UNDEFINED_ROOTS = 50000;
|
|
8
|
+
export declare const MAX_PENDING_BARRIER_ROOTS = 50000;
|
|
9
|
+
export interface SpanBypass {
|
|
10
|
+
readonly id: string;
|
|
11
|
+
readonly host: Element;
|
|
12
|
+
}
|
|
6
13
|
export interface PendingBoundaryUpdates {
|
|
7
14
|
readonly roots: Element[];
|
|
15
|
+
active: boolean;
|
|
8
16
|
retained: number;
|
|
9
17
|
pendingRoots: number;
|
|
10
18
|
patch?: Record<string, unknown>;
|
|
11
19
|
}
|
|
12
20
|
export interface DeferredActivationOptions {
|
|
13
21
|
updates?: PendingBoundaryUpdates;
|
|
22
|
+
bypass?: SpanBypass;
|
|
14
23
|
countRetention?: boolean;
|
|
15
24
|
}
|
|
16
25
|
export declare function configureStreamingFailureHandler(handler: (reason: string) => void): void;
|
|
@@ -19,4 +28,5 @@ export declare function abandonPendingWaiters(): void;
|
|
|
19
28
|
export declare function resetDeferredActivationForTests(): void;
|
|
20
29
|
export declare function pendingTagWaiterCountForTests(): number;
|
|
21
30
|
export declare function pendingUndefinedRootCountForTests(): number;
|
|
31
|
+
export declare function pendingBarrierRootCountForTests(): number;
|
|
22
32
|
export declare function elementHasPendingStateForTests(el: Element): boolean;
|