@microsoft/webui-framework 0.0.25 → 0.0.27
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 +1042 -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
|
@@ -1,22 +1,28 @@
|
|
|
1
1
|
import { markLateActivationPending, settleLateActivation, } from './lifecycle.js';
|
|
2
|
-
import { abandonDeferredDescendants, abandonDeferredElement, } from './streaming-cleanup.js';
|
|
3
|
-
import { firstNodeWithin, MAX_ELEMENTS_PER_BOUNDARY, MAX_MARKER_SCAN_NODES, nextAfterSubtreeWithin, nextWithinRoot,
|
|
4
|
-
import { PENDING_ROOT_CONNECTED, STREAMED_HOST_ATTR, STREAMING_BOUNDARY_ACTIVATE, } from './streaming-mode.js';
|
|
2
|
+
import { abandonDeferredDescendants, abandonDeferredElement, removeStreamingAttributes, } from './streaming-cleanup.js';
|
|
3
|
+
import { firstNodeWithin, MAX_ELEMENTS_PER_BOUNDARY, MAX_MARKER_SCAN_NODES, nextAfterSubtreeWithin, nextWithinRoot, STREAMING_ENCLOSING_SPAN_ATTR, streamingErrorMessage, } from './streaming-dom.js';
|
|
4
|
+
import { ACTIVATION_ACTIVATED, ACTIVATION_ANCESTOR_BARRIER, ACTIVATION_MISSING_TEMPLATE, ACTIVATION_STATIC_HOST_OPT_OUT, PENDING_ROOT_CONNECTED, STREAMED_HOST_ATTR, STREAMING_BOUNDARY_ACTIVATE, } from './streaming-mode.js';
|
|
5
5
|
import { applyStateUpdate } from './streaming-state.js';
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
export const
|
|
9
|
-
export const
|
|
10
|
-
export const
|
|
11
|
-
export const
|
|
6
|
+
export const ELEMENT_IGNORED = 10;
|
|
7
|
+
export const ELEMENT_DEFERRED = 11;
|
|
8
|
+
export const ELEMENT_LIMIT_FAILURE = 12;
|
|
9
|
+
export const ELEMENT_ACTIVATED_FROM_PENDING = 13;
|
|
10
|
+
export const ELEMENT_BARRIER_LIMIT_FAILURE = 14;
|
|
11
|
+
export const ELEMENT_INVALID_OUTCOME = 15;
|
|
12
12
|
export const MAX_PENDING_UNDEFINED_ROOTS = 50_000;
|
|
13
|
+
export const MAX_PENDING_BARRIER_ROOTS = 50_000;
|
|
14
|
+
function retainsBoundaryUpdates(el, outcome) {
|
|
15
|
+
return outcome === ACTIVATION_ACTIVATED
|
|
16
|
+
|| (outcome === ACTIVATION_STATIC_HOST_OPT_OUT
|
|
17
|
+
&& typeof el.setState === 'function');
|
|
18
|
+
}
|
|
13
19
|
const pendingTagWaiters = new Map();
|
|
20
|
+
const pendingBarrierRoots = new Set();
|
|
14
21
|
let pendingUndefinedRoots = 0;
|
|
15
22
|
let activationGeneration = 0;
|
|
16
23
|
let failureHandler = null;
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
const NO_BOUNDARY_STATE = Symbol();
|
|
24
|
+
let invalidActivationOutcome;
|
|
25
|
+
const PENDING_RECORD = Symbol();
|
|
20
26
|
export function configureStreamingFailureHandler(handler) {
|
|
21
27
|
failureHandler = handler;
|
|
22
28
|
}
|
|
@@ -26,22 +32,54 @@ function fail(reason) {
|
|
|
26
32
|
}
|
|
27
33
|
failureHandler(reason);
|
|
28
34
|
}
|
|
35
|
+
function tagOf(el) {
|
|
36
|
+
return el.tagName.toLowerCase();
|
|
37
|
+
}
|
|
38
|
+
function missingTemplateReason(tag) {
|
|
39
|
+
return `template metadata missing while activating <${tag}>`;
|
|
40
|
+
}
|
|
41
|
+
function invalidOutcomeReason(tag) {
|
|
42
|
+
return `<${tag}> returned an unrecognized streaming activation outcome ${String(invalidActivationOutcome)}`;
|
|
43
|
+
}
|
|
44
|
+
function barrierLimitReason() {
|
|
45
|
+
return `pending ancestor-barrier root count exceeds ${MAX_PENDING_BARRIER_ROOTS}`;
|
|
46
|
+
}
|
|
29
47
|
function requireStateUpdate(el, patch) {
|
|
30
48
|
if (applyStateUpdate(el, patch))
|
|
31
49
|
return;
|
|
32
|
-
fail(`<${el
|
|
50
|
+
fail(`<${tagOf(el)}> activated without a setState() method`);
|
|
51
|
+
}
|
|
52
|
+
function joinBoundaryUpdates(el, updates) {
|
|
53
|
+
if (updates.active)
|
|
54
|
+
updates.roots.push(el);
|
|
55
|
+
if (updates.patch)
|
|
56
|
+
requireStateUpdate(el, updates.patch);
|
|
33
57
|
}
|
|
34
|
-
function activateMarkedElement(el, state, updates) {
|
|
35
|
-
const tag = el
|
|
58
|
+
function activateMarkedElement(el, state, updates, bypass) {
|
|
59
|
+
const tag = tagOf(el);
|
|
36
60
|
if (tag.indexOf('-') === -1)
|
|
37
61
|
return ELEMENT_IGNORED;
|
|
38
|
-
if (customElements.get(tag))
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
62
|
+
if (customElements.get(tag)) {
|
|
63
|
+
if (pendingBarrierRoots.has(el)) {
|
|
64
|
+
return activatePendingBarrierRoot(el);
|
|
65
|
+
}
|
|
66
|
+
if (hasPendingRecord(el))
|
|
67
|
+
return ELEMENT_DEFERRED;
|
|
68
|
+
const outcome = invokeActivationHook(el, state, bypass);
|
|
69
|
+
if (outcome !== ACTIVATION_ANCESTOR_BARRIER)
|
|
70
|
+
return outcome;
|
|
71
|
+
if (pendingBarrierRoots.size >= MAX_PENDING_BARRIER_ROOTS) {
|
|
72
|
+
return ELEMENT_BARRIER_LIMIT_FAILURE;
|
|
73
|
+
}
|
|
74
|
+
deferBehindBarrier(el, state, updates, bypass);
|
|
75
|
+
return ELEMENT_DEFERRED;
|
|
76
|
+
}
|
|
77
|
+
if (!hasPendingRecord(el)) {
|
|
78
|
+
if (pendingUndefinedRoots >= MAX_PENDING_UNDEFINED_ROOTS) {
|
|
79
|
+
return ELEMENT_LIMIT_FAILURE;
|
|
80
|
+
}
|
|
81
|
+
stashPendingRecord(el, state, updates, bypass);
|
|
43
82
|
}
|
|
44
|
-
stashPendingState(el, state, updates);
|
|
45
83
|
let waiter = pendingTagWaiters.get(tag);
|
|
46
84
|
if (!waiter) {
|
|
47
85
|
waiter = { generation: activationGeneration, roots: new Set() };
|
|
@@ -59,6 +97,58 @@ function activateMarkedElement(el, state, updates) {
|
|
|
59
97
|
el[PENDING_ROOT_CONNECTED] = resumePendingRoot;
|
|
60
98
|
return ELEMENT_DEFERRED;
|
|
61
99
|
}
|
|
100
|
+
function deferBehindBarrier(el, state, updates, bypass) {
|
|
101
|
+
stashPendingRecord(el, state, updates, bypass);
|
|
102
|
+
pendingBarrierRoots.add(el);
|
|
103
|
+
el[PENDING_ROOT_CONNECTED] = resumeBarrierRoot;
|
|
104
|
+
}
|
|
105
|
+
function activatePendingBarrierRoot(el) {
|
|
106
|
+
if (!pendingBarrierRoots.delete(el))
|
|
107
|
+
return ELEMENT_IGNORED;
|
|
108
|
+
delete el[PENDING_ROOT_CONNECTED];
|
|
109
|
+
const record = takePendingRecord(el);
|
|
110
|
+
const updates = releaseUpdates(record);
|
|
111
|
+
try {
|
|
112
|
+
const outcome = resumeRetainedRoot(el, record, updates);
|
|
113
|
+
if (outcome === ACTIVATION_ANCESTOR_BARRIER)
|
|
114
|
+
return ELEMENT_DEFERRED;
|
|
115
|
+
return outcome === ACTIVATION_MISSING_TEMPLATE ||
|
|
116
|
+
outcome === ELEMENT_INVALID_OUTCOME
|
|
117
|
+
? outcome
|
|
118
|
+
: ELEMENT_ACTIVATED_FROM_PENDING;
|
|
119
|
+
}
|
|
120
|
+
finally {
|
|
121
|
+
if (updates?.pendingRoots === 0)
|
|
122
|
+
updates.patch = undefined;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function resumeRetainedRoot(el, record, updates) {
|
|
126
|
+
const outcome = invokeActivationHook(el, record?.state, record?.bypass);
|
|
127
|
+
if (outcome === ACTIVATION_ANCESTOR_BARRIER) {
|
|
128
|
+
deferBehindBarrier(el, record?.state, updates, record?.bypass);
|
|
129
|
+
}
|
|
130
|
+
else if (updates && retainsBoundaryUpdates(el, outcome)) {
|
|
131
|
+
joinBoundaryUpdates(el, updates);
|
|
132
|
+
}
|
|
133
|
+
return outcome;
|
|
134
|
+
}
|
|
135
|
+
function resumeBarrierRoot() {
|
|
136
|
+
try {
|
|
137
|
+
const outcome = activatePendingBarrierRoot(this);
|
|
138
|
+
if (outcome === ACTIVATION_MISSING_TEMPLATE) {
|
|
139
|
+
abandonDeferredTree(this);
|
|
140
|
+
fail(missingTemplateReason(tagOf(this)));
|
|
141
|
+
}
|
|
142
|
+
else if (outcome === ELEMENT_INVALID_OUTCOME) {
|
|
143
|
+
abandonDeferredTree(this);
|
|
144
|
+
fail(invalidOutcomeReason(tagOf(this)));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
abandonDeferredDescendants(this);
|
|
149
|
+
reportActivationFailure(tagOf(this), error);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
62
152
|
function onTagDefined(tag, generation) {
|
|
63
153
|
if (generation !== activationGeneration)
|
|
64
154
|
return;
|
|
@@ -85,7 +175,7 @@ function onTagDefined(tag, generation) {
|
|
|
85
175
|
}
|
|
86
176
|
}
|
|
87
177
|
function resumePendingRoot() {
|
|
88
|
-
const tag = this
|
|
178
|
+
const tag = tagOf(this);
|
|
89
179
|
const waiter = pendingTagWaiters.get(tag);
|
|
90
180
|
if (!waiter ||
|
|
91
181
|
waiter.generation !== activationGeneration ||
|
|
@@ -100,23 +190,30 @@ function activatePendingRoot(tag, waiter, el) {
|
|
|
100
190
|
return;
|
|
101
191
|
pendingUndefinedRoots--;
|
|
102
192
|
delete el[PENDING_ROOT_CONNECTED];
|
|
103
|
-
|
|
193
|
+
const record = takePendingRecord(el);
|
|
194
|
+
const updates = releaseUpdates(record);
|
|
195
|
+
const state = record?.state;
|
|
196
|
+
const bypass = record?.bypass;
|
|
104
197
|
try {
|
|
105
|
-
|
|
106
|
-
const state = takePendingState(el);
|
|
107
|
-
const outcome = invokeActivationHook(el, state);
|
|
198
|
+
const outcome = resumeRetainedRoot(el, record, updates);
|
|
108
199
|
if (outcome === ACTIVATION_MISSING_TEMPLATE) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
fail(`template metadata missing while activating <${tag}>`);
|
|
200
|
+
abandonDeferredTree(el);
|
|
201
|
+
fail(missingTemplateReason(tag));
|
|
112
202
|
return;
|
|
113
203
|
}
|
|
114
|
-
if (
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
204
|
+
if (outcome === ELEMENT_INVALID_OUTCOME) {
|
|
205
|
+
abandonDeferredTree(el);
|
|
206
|
+
fail(invalidOutcomeReason(tag));
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (outcome === ACTIVATION_ANCESTOR_BARRIER) {
|
|
210
|
+
if (pendingBarrierRoots.size > MAX_PENDING_BARRIER_ROOTS) {
|
|
211
|
+
abandonDeferredTree(el);
|
|
212
|
+
fail(barrierLimitReason());
|
|
213
|
+
}
|
|
214
|
+
return;
|
|
118
215
|
}
|
|
119
|
-
const failure = activateDeferredTree(firstNodeWithin(el), el, null, state, updates ? { updates } : undefined);
|
|
216
|
+
const failure = activateDeferredTree(firstNodeWithin(el), el, null, state, updates || bypass ? { updates, bypass } : undefined);
|
|
120
217
|
if (failure)
|
|
121
218
|
fail(failure);
|
|
122
219
|
}
|
|
@@ -136,6 +233,7 @@ function activatePendingRoot(tag, waiter, el) {
|
|
|
136
233
|
}
|
|
137
234
|
export function activateDeferredTree(first, root, end, state, options) {
|
|
138
235
|
const updates = options?.updates;
|
|
236
|
+
const bypass = options?.bypass;
|
|
139
237
|
const countRetention = options?.countRetention === true && updates !== undefined;
|
|
140
238
|
let node = first;
|
|
141
239
|
let resumeAfterDeferred = null;
|
|
@@ -170,28 +268,30 @@ export function activateDeferredTree(first, root, end, state, options) {
|
|
|
170
268
|
if (marked) {
|
|
171
269
|
const el = node;
|
|
172
270
|
try {
|
|
173
|
-
const outcome = activateMarkedElement(el, state, updates);
|
|
271
|
+
const outcome = activateMarkedElement(el, state, updates, bypass);
|
|
174
272
|
if (outcome === ACTIVATION_MISSING_TEMPLATE) {
|
|
175
|
-
return
|
|
273
|
+
return missingTemplateReason(tagOf(el));
|
|
274
|
+
}
|
|
275
|
+
if (outcome === ELEMENT_INVALID_OUTCOME) {
|
|
276
|
+
return invalidOutcomeReason(tagOf(el));
|
|
176
277
|
}
|
|
177
278
|
if (outcome === ELEMENT_LIMIT_FAILURE) {
|
|
178
279
|
return `pending undefined root count exceeds ${MAX_PENDING_UNDEFINED_ROOTS}`;
|
|
179
280
|
}
|
|
281
|
+
if (outcome === ELEMENT_BARRIER_LIMIT_FAILURE) {
|
|
282
|
+
return barrierLimitReason();
|
|
283
|
+
}
|
|
180
284
|
if (outcome === ELEMENT_DEFERRED) {
|
|
181
285
|
resumeAfterDeferred = nextAfterSubtreeWithin(node, root);
|
|
182
286
|
skippingDeferredDescendants = true;
|
|
183
287
|
}
|
|
184
288
|
else if (updates &&
|
|
185
|
-
(outcome
|
|
186
|
-
|
|
187
|
-
updates.roots.push(el);
|
|
188
|
-
if (updates.patch) {
|
|
189
|
-
requireStateUpdate(el, updates.patch);
|
|
190
|
-
}
|
|
289
|
+
retainsBoundaryUpdates(el, outcome)) {
|
|
290
|
+
joinBoundaryUpdates(el, updates);
|
|
191
291
|
}
|
|
192
292
|
}
|
|
193
293
|
catch (error) {
|
|
194
|
-
reportActivationFailure(el
|
|
294
|
+
reportActivationFailure(tagOf(el), error);
|
|
195
295
|
}
|
|
196
296
|
}
|
|
197
297
|
}
|
|
@@ -204,73 +304,83 @@ export function activateDeferredTree(first, root, end, state, options) {
|
|
|
204
304
|
function reportActivationFailure(tag, error) {
|
|
205
305
|
console.error(`[WebUI] streaming: late activation failed for <${tag}>: ${streamingErrorMessage(error)}`);
|
|
206
306
|
}
|
|
307
|
+
function abandonDeferredTree(el) {
|
|
308
|
+
abandonDeferredDescendants(el);
|
|
309
|
+
abandonDeferredElement(el);
|
|
310
|
+
}
|
|
207
311
|
export function abandonPendingWaiters() {
|
|
208
|
-
if (
|
|
209
|
-
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
for (const waiter of pendingTagWaiters.values()) {
|
|
213
|
-
for (const el of waiter.roots)
|
|
312
|
+
if (pendingBarrierRoots.size !== 0) {
|
|
313
|
+
for (const el of pendingBarrierRoots)
|
|
214
314
|
clearPendingRoot(el);
|
|
215
|
-
|
|
315
|
+
pendingBarrierRoots.clear();
|
|
316
|
+
}
|
|
317
|
+
if (pendingTagWaiters.size !== 0) {
|
|
318
|
+
for (const waiter of pendingTagWaiters.values()) {
|
|
319
|
+
for (const el of waiter.roots)
|
|
320
|
+
clearPendingRoot(el);
|
|
321
|
+
settleLateActivation();
|
|
322
|
+
}
|
|
323
|
+
pendingTagWaiters.clear();
|
|
216
324
|
}
|
|
217
|
-
pendingTagWaiters.clear();
|
|
218
325
|
pendingUndefinedRoots = 0;
|
|
219
326
|
}
|
|
220
327
|
function clearPendingRoot(el) {
|
|
221
|
-
|
|
222
|
-
takePendingState(el);
|
|
223
|
-
takePendingUpdates(el);
|
|
328
|
+
releaseUpdates(takePendingRecord(el));
|
|
224
329
|
delete el[PENDING_ROOT_CONNECTED];
|
|
225
|
-
|
|
226
|
-
abandonDeferredElement(el);
|
|
330
|
+
abandonDeferredTree(el);
|
|
227
331
|
}
|
|
228
|
-
function
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
332
|
+
function stashPendingRecord(el, state, updates, bypass) {
|
|
333
|
+
el[PENDING_RECORD] = {
|
|
334
|
+
state,
|
|
335
|
+
updates,
|
|
336
|
+
bypass,
|
|
337
|
+
};
|
|
338
|
+
if (updates)
|
|
234
339
|
updates.pendingRoots++;
|
|
235
|
-
}
|
|
236
340
|
}
|
|
237
|
-
function
|
|
238
|
-
return Object.prototype.hasOwnProperty.call(el,
|
|
341
|
+
function hasPendingRecord(el) {
|
|
342
|
+
return Object.prototype.hasOwnProperty.call(el, PENDING_RECORD);
|
|
239
343
|
}
|
|
240
|
-
function
|
|
344
|
+
function takePendingRecord(el) {
|
|
241
345
|
const store = el;
|
|
242
|
-
const
|
|
243
|
-
delete store[
|
|
244
|
-
return
|
|
245
|
-
? undefined
|
|
246
|
-
: stored;
|
|
346
|
+
const record = store[PENDING_RECORD];
|
|
347
|
+
delete store[PENDING_RECORD];
|
|
348
|
+
return record;
|
|
247
349
|
}
|
|
248
|
-
function
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
return updates;
|
|
350
|
+
function releaseUpdates(record) {
|
|
351
|
+
const updates = record?.updates;
|
|
352
|
+
if (!updates)
|
|
353
|
+
return undefined;
|
|
354
|
+
updates.pendingRoots--;
|
|
355
|
+
return updates.active || updates.patch !== undefined ? updates : undefined;
|
|
255
356
|
}
|
|
256
|
-
function invokeActivationHook(el, state) {
|
|
357
|
+
function invokeActivationHook(el, state, bypass) {
|
|
257
358
|
const hook = el[STREAMING_BOUNDARY_ACTIVATE];
|
|
258
359
|
if (typeof hook !== 'function')
|
|
259
360
|
return ACTIVATION_MISSING_TEMPLATE;
|
|
361
|
+
const bypassAncestor = bypass !== undefined &&
|
|
362
|
+
el.getAttribute(STREAMING_ENCLOSING_SPAN_ATTR) === bypass.id
|
|
363
|
+
? bypass.host
|
|
364
|
+
: undefined;
|
|
260
365
|
let outcome;
|
|
261
366
|
try {
|
|
262
|
-
outcome = hook.call(el, state);
|
|
367
|
+
outcome = hook.call(el, state, bypassAncestor);
|
|
263
368
|
}
|
|
264
369
|
catch (error) {
|
|
265
|
-
|
|
370
|
+
removeStreamingAttributes(el);
|
|
266
371
|
throw error;
|
|
267
372
|
}
|
|
268
|
-
if (outcome
|
|
269
|
-
outcome
|
|
270
|
-
|
|
373
|
+
if (outcome === ACTIVATION_ACTIVATED ||
|
|
374
|
+
outcome === ACTIVATION_STATIC_HOST_OPT_OUT) {
|
|
375
|
+
removeStreamingAttributes(el);
|
|
376
|
+
return outcome;
|
|
271
377
|
}
|
|
272
|
-
|
|
273
|
-
|
|
378
|
+
if (outcome === ACTIVATION_ANCESTOR_BARRIER ||
|
|
379
|
+
outcome === ACTIVATION_MISSING_TEMPLATE) {
|
|
380
|
+
return outcome;
|
|
381
|
+
}
|
|
382
|
+
invalidActivationOutcome = outcome;
|
|
383
|
+
return ELEMENT_INVALID_OUTCOME;
|
|
274
384
|
}
|
|
275
385
|
export function resetDeferredActivationForTests() {
|
|
276
386
|
abandonPendingWaiters();
|
|
@@ -282,6 +392,9 @@ export function pendingTagWaiterCountForTests() {
|
|
|
282
392
|
export function pendingUndefinedRootCountForTests() {
|
|
283
393
|
return pendingUndefinedRoots;
|
|
284
394
|
}
|
|
395
|
+
export function pendingBarrierRootCountForTests() {
|
|
396
|
+
return pendingBarrierRoots.size;
|
|
397
|
+
}
|
|
285
398
|
export function elementHasPendingStateForTests(el) {
|
|
286
|
-
return
|
|
399
|
+
return hasPendingRecord(el);
|
|
287
400
|
}
|
package/dist/streaming-dom.d.ts
CHANGED
|
@@ -2,7 +2,14 @@ export declare const BOUNDARY_SCRIPT_ATTR = "data-webui-boundary";
|
|
|
2
2
|
export declare const MAX_ELEMENTS_PER_BOUNDARY = 10000;
|
|
3
3
|
export declare const MAX_MARKER_SCAN_NODES = 50000;
|
|
4
4
|
export declare const MAX_BOUNDARY_SCRIPT_SCAN = 8;
|
|
5
|
+
export declare const STREAMING_SPAN_HOST_ATTR = "data-ws-span";
|
|
6
|
+
export declare const STREAMING_ENCLOSING_SPAN_ATTR = "data-ws-enclosing";
|
|
5
7
|
export declare function streamingErrorMessage(error: unknown): string;
|
|
8
|
+
export declare const BOUNDARY_START_PREFIX = "wb:";
|
|
9
|
+
export declare const BOUNDARY_END_PREFIX = "/wb:";
|
|
10
|
+
export declare const SPAN_START_PREFIX = "ws:";
|
|
11
|
+
export declare const SPAN_END_PREFIX = "/ws:";
|
|
12
|
+
export declare function isRangeEndMarker(data: string): boolean;
|
|
6
13
|
export interface HydrationRange {
|
|
7
14
|
readonly start: Comment | null;
|
|
8
15
|
readonly end: Comment | null;
|
|
@@ -20,10 +27,12 @@ export type RangeResolution = {
|
|
|
20
27
|
readonly truncated: true;
|
|
21
28
|
readonly start: Comment | null;
|
|
22
29
|
};
|
|
23
|
-
export declare function resolveBoundaryRange(scriptEl: Element,
|
|
30
|
+
export declare function resolveBoundaryRange(scriptEl: Element, instanceId: number): RangeResolution;
|
|
31
|
+
export declare function resolveSpanRange(scriptEl: Element, instanceId: number): RangeResolution;
|
|
32
|
+
export declare function markerlessRecordViolation(scriptEl: Element, kind: string): string | null;
|
|
24
33
|
export declare function findBoundaryScript(sentinel: Element): Element | null;
|
|
25
|
-
export declare function
|
|
26
|
-
export declare function
|
|
34
|
+
export declare function findRangeEndMarkerByPrefix(scriptEl: Element): Comment | null;
|
|
35
|
+
export declare function findRangeStartMarkerByPrefix(endMarker: Comment): Comment | null;
|
|
27
36
|
export declare function safeRemove(node: Node | null | undefined): void;
|
|
28
37
|
export declare function safeRemoveAttribute(el: Element, name: string): void;
|
|
29
38
|
export declare function removeBoundaryScaffolding(sentinel: Element, scriptEl: Element | null, startMarker: Comment | null, endMarker: Comment | null): void;
|
package/dist/streaming-dom.js
CHANGED
|
@@ -2,41 +2,52 @@ export const BOUNDARY_SCRIPT_ATTR = 'data-webui-boundary';
|
|
|
2
2
|
export const MAX_ELEMENTS_PER_BOUNDARY = 10_000;
|
|
3
3
|
export const MAX_MARKER_SCAN_NODES = 50_000;
|
|
4
4
|
export const MAX_BOUNDARY_SCRIPT_SCAN = 8;
|
|
5
|
+
export const STREAMING_SPAN_HOST_ATTR = 'data-ws-span';
|
|
6
|
+
export const STREAMING_ENCLOSING_SPAN_ATTR = 'data-ws-enclosing';
|
|
5
7
|
export function streamingErrorMessage(error) {
|
|
6
8
|
return error instanceof Error ? error.message : String(error);
|
|
7
9
|
}
|
|
8
|
-
const BOUNDARY_START_PREFIX = 'wb:';
|
|
9
|
-
const BOUNDARY_END_PREFIX = '/wb:';
|
|
10
|
-
const
|
|
11
|
-
export
|
|
12
|
-
|
|
10
|
+
export const BOUNDARY_START_PREFIX = 'wb:';
|
|
11
|
+
export const BOUNDARY_END_PREFIX = '/wb:';
|
|
12
|
+
export const SPAN_START_PREFIX = 'ws:';
|
|
13
|
+
export const SPAN_END_PREFIX = '/ws:';
|
|
14
|
+
export function isRangeEndMarker(data) {
|
|
15
|
+
return data.startsWith(BOUNDARY_END_PREFIX) ||
|
|
16
|
+
data.startsWith(SPAN_END_PREFIX);
|
|
17
|
+
}
|
|
18
|
+
const MARKER_KIND_BOUNDARY = 'boundary';
|
|
19
|
+
const MARKER_KIND_SPAN = 'span';
|
|
20
|
+
export function resolveBoundaryRange(scriptEl, instanceId) {
|
|
21
|
+
return resolveMarkerRange(scriptEl, instanceId, BOUNDARY_START_PREFIX, BOUNDARY_END_PREFIX, MARKER_KIND_BOUNDARY);
|
|
22
|
+
}
|
|
23
|
+
export function resolveSpanRange(scriptEl, instanceId) {
|
|
24
|
+
return resolveMarkerRange(scriptEl, instanceId, SPAN_START_PREFIX, SPAN_END_PREFIX, MARKER_KIND_SPAN);
|
|
25
|
+
}
|
|
26
|
+
function resolveMarkerRange(scriptEl, instanceId, startPrefix, endPrefix, kind) {
|
|
27
|
+
const end = findEndMarker(scriptEl, instanceId, endPrefix);
|
|
13
28
|
if (end) {
|
|
14
|
-
|
|
15
|
-
return {
|
|
16
|
-
ok: false,
|
|
17
|
-
reason: `terminal boundary ${sequence} must be markerless`,
|
|
18
|
-
truncated: false,
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
const start = findStartMarkerBefore(end, sequence);
|
|
29
|
+
const start = findStartMarkerBefore(end, instanceId, startPrefix);
|
|
22
30
|
if (!start) {
|
|
23
31
|
return {
|
|
24
32
|
ok: false,
|
|
25
|
-
reason: `missing start marker for
|
|
33
|
+
reason: `missing start marker for ${kind} ${instanceId}`,
|
|
26
34
|
truncated: false,
|
|
27
35
|
};
|
|
28
36
|
}
|
|
29
37
|
return { ok: true, range: { start, end } };
|
|
30
38
|
}
|
|
31
|
-
if (terminal)
|
|
32
|
-
return { ok: true, range: MARKERLESS_RANGE };
|
|
33
39
|
return {
|
|
34
40
|
ok: false,
|
|
35
|
-
reason: `missing end marker for
|
|
41
|
+
reason: `missing end marker for ${kind} ${instanceId}`,
|
|
36
42
|
truncated: true,
|
|
37
|
-
start: findStartMarkerBefore(scriptEl,
|
|
43
|
+
start: findStartMarkerBefore(scriptEl, instanceId, startPrefix),
|
|
38
44
|
};
|
|
39
45
|
}
|
|
46
|
+
export function markerlessRecordViolation(scriptEl, kind) {
|
|
47
|
+
return previousRangeEndMarker(scriptEl)
|
|
48
|
+
? `${kind} record must be markerless`
|
|
49
|
+
: null;
|
|
50
|
+
}
|
|
40
51
|
export function findBoundaryScript(sentinel) {
|
|
41
52
|
let node = sentinel.previousElementSibling;
|
|
42
53
|
for (let hops = 0; node && hops < MAX_BOUNDARY_SCRIPT_SCAN; hops++) {
|
|
@@ -48,21 +59,24 @@ export function findBoundaryScript(sentinel) {
|
|
|
48
59
|
}
|
|
49
60
|
return null;
|
|
50
61
|
}
|
|
51
|
-
function findEndMarker(scriptEl,
|
|
62
|
+
function findEndMarker(scriptEl, instanceId, prefix) {
|
|
52
63
|
const marker = previousComment(scriptEl);
|
|
53
|
-
return marker?.data === `${
|
|
64
|
+
return marker?.data === `${prefix}${instanceId}`
|
|
54
65
|
? marker
|
|
55
66
|
: null;
|
|
56
67
|
}
|
|
57
|
-
function findStartMarkerBefore(nodeBefore,
|
|
58
|
-
return findCommentBefore(nodeBefore, `${
|
|
68
|
+
function findStartMarkerBefore(nodeBefore, instanceId, prefix) {
|
|
69
|
+
return findCommentBefore(nodeBefore, `${prefix}${instanceId}`);
|
|
59
70
|
}
|
|
60
|
-
export function
|
|
61
|
-
|
|
62
|
-
|
|
71
|
+
export function findRangeEndMarkerByPrefix(scriptEl) {
|
|
72
|
+
return previousRangeEndMarker(scriptEl);
|
|
73
|
+
}
|
|
74
|
+
export function findRangeStartMarkerByPrefix(endMarker) {
|
|
75
|
+
return findCommentBefore(endMarker, endMarker.data.slice(1));
|
|
63
76
|
}
|
|
64
|
-
|
|
65
|
-
|
|
77
|
+
function previousRangeEndMarker(node) {
|
|
78
|
+
const marker = previousComment(node);
|
|
79
|
+
return marker && isRangeEndMarker(marker.data) ? marker : null;
|
|
66
80
|
}
|
|
67
81
|
function previousComment(node) {
|
|
68
82
|
const previous = node.previousSibling;
|
package/dist/streaming-mode.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
export declare const STREAMING_BOUNDARY_ACTIVATE: unique symbol;
|
|
2
2
|
export declare const PENDING_ROOT_CONNECTED: unique symbol;
|
|
3
3
|
export declare const STREAMED_HOST_ATTR = "data-ws";
|
|
4
|
+
export declare const ACTIVATION_ACTIVATED = 1;
|
|
5
|
+
export declare const ACTIVATION_STATIC_HOST_OPT_OUT = 2;
|
|
6
|
+
export declare const ACTIVATION_MISSING_TEMPLATE = 3;
|
|
7
|
+
export declare const ACTIVATION_ANCESTOR_BARRIER = 4;
|
|
8
|
+
export type ActivationOutcome = typeof ACTIVATION_ACTIVATED | typeof ACTIVATION_STATIC_HOST_OPT_OUT | typeof ACTIVATION_MISSING_TEMPLATE | typeof ACTIVATION_ANCESTOR_BARRIER;
|
|
4
9
|
export declare function isStreamingHydrationMode(): boolean;
|
|
5
10
|
export declare function resetStreamingModeForTests(): void;
|
package/dist/streaming-mode.js
CHANGED
|
@@ -2,6 +2,10 @@ let cached;
|
|
|
2
2
|
export const STREAMING_BOUNDARY_ACTIVATE = Symbol.for('microsoft.webui.boundaryActivate');
|
|
3
3
|
export const PENDING_ROOT_CONNECTED = Symbol.for('microsoft.webui.pendingRootConnected');
|
|
4
4
|
export const STREAMED_HOST_ATTR = 'data-ws';
|
|
5
|
+
export const ACTIVATION_ACTIVATED = 1;
|
|
6
|
+
export const ACTIVATION_STATIC_HOST_OPT_OUT = 2;
|
|
7
|
+
export const ACTIVATION_MISSING_TEMPLATE = 3;
|
|
8
|
+
export const ACTIVATION_ANCESTOR_BARRIER = 4;
|
|
5
9
|
export function isStreamingHydrationMode() {
|
|
6
10
|
if (cached !== undefined)
|
|
7
11
|
return cached;
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { TemplateMeta } from './template.js';
|
|
2
|
+
import type { ComponentStyles } from './element/styles.js';
|
|
3
|
+
export declare const STREAMING_PROTOCOL_VERSION = 2;
|
|
2
4
|
export interface BoundaryBootstrap {
|
|
5
|
+
declarationId: number;
|
|
6
|
+
enclosingSpanInstanceId?: number;
|
|
3
7
|
state?: Record<string, unknown>;
|
|
4
8
|
templates?: Record<string, TemplateMeta>;
|
|
5
9
|
inventory?: string;
|
|
@@ -7,14 +11,26 @@ export interface BoundaryBootstrap {
|
|
|
7
11
|
chain?: unknown[];
|
|
8
12
|
css?: string[];
|
|
9
13
|
styles?: string[];
|
|
14
|
+
componentStyles: ComponentStyles;
|
|
10
15
|
[key: string]: unknown;
|
|
11
16
|
}
|
|
12
17
|
export declare const RECORD_KIND_FINAL_CHECKPOINT = 0;
|
|
13
18
|
export declare const RECORD_KIND_UPDATABLE_CHECKPOINT = 1;
|
|
14
19
|
export declare const RECORD_KIND_STATE_UPDATE = 2;
|
|
15
|
-
export declare const
|
|
16
|
-
export
|
|
17
|
-
export type
|
|
20
|
+
export declare const RECORD_KIND_SPAN_COMPLETION = 3;
|
|
21
|
+
export declare const RECORD_KIND_TERMINAL = 4;
|
|
22
|
+
export type BoundaryRecordKind = typeof RECORD_KIND_FINAL_CHECKPOINT | typeof RECORD_KIND_UPDATABLE_CHECKPOINT | typeof RECORD_KIND_STATE_UPDATE | typeof RECORD_KIND_SPAN_COMPLETION | typeof RECORD_KIND_TERMINAL;
|
|
23
|
+
export interface SpanCompletionPayload {
|
|
24
|
+
state?: Record<string, unknown>;
|
|
25
|
+
templates?: Record<string, TemplateMeta>;
|
|
26
|
+
inventory?: string;
|
|
27
|
+
nonce?: string;
|
|
28
|
+
chain?: unknown[];
|
|
29
|
+
css?: string[];
|
|
30
|
+
styles?: string[];
|
|
31
|
+
[key: string]: unknown;
|
|
32
|
+
}
|
|
33
|
+
export type BoundaryRecordPayload = BoundaryBootstrap | SpanCompletionPayload | Record<string, unknown>;
|
|
18
34
|
export type BoundaryEnvelope = readonly [
|
|
19
35
|
version: number,
|
|
20
36
|
recordSequence: number,
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
const
|
|
1
|
+
export const STREAMING_PROTOCOL_VERSION = 2;
|
|
2
2
|
export const RECORD_KIND_FINAL_CHECKPOINT = 0;
|
|
3
3
|
export const RECORD_KIND_UPDATABLE_CHECKPOINT = 1;
|
|
4
4
|
export const RECORD_KIND_STATE_UPDATE = 2;
|
|
5
|
-
export const
|
|
5
|
+
export const RECORD_KIND_SPAN_COMPLETION = 3;
|
|
6
|
+
export const RECORD_KIND_TERMINAL = 4;
|
|
6
7
|
function invalid(reason) {
|
|
7
8
|
return { ok: false, reason };
|
|
8
9
|
}
|
|
@@ -17,7 +18,7 @@ export function parseBoundaryEnvelope(text) {
|
|
|
17
18
|
if (!Array.isArray(parsed) || parsed.length !== 5) {
|
|
18
19
|
return invalid('boundary envelope must be a 5-element [version, recordSequence, kind, target, payload] array');
|
|
19
20
|
}
|
|
20
|
-
if (parsed[0] !==
|
|
21
|
+
if (parsed[0] !== STREAMING_PROTOCOL_VERSION) {
|
|
21
22
|
return invalid(`unsupported boundary envelope version ${JSON.stringify(parsed[0])}`);
|
|
22
23
|
}
|
|
23
24
|
return {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { HydrationRange } from './streaming-dom.js';
|
|
2
|
+
export declare const MAX_SPAN_NESTING = 32;
|
|
3
|
+
export declare function spanHostFor(id: number): Element | undefined;
|
|
4
|
+
export declare function registerEnclosingSpans(renderRoot: Node, enclosingSpanInstanceId: number): string | null;
|
|
5
|
+
export declare function prepareSpanCompletion(id: number, range: HydrationRange): string | null;
|
|
6
|
+
export declare function completeSpan(id: number): void;
|
|
7
|
+
export declare function abandonOpenSpans(): void;
|
|
8
|
+
export declare function openSpanCountForTests(): number;
|
|
9
|
+
export declare function hasOpenSpans(): boolean;
|