Skip to content

Commit 8071ad3

Browse files
committed
Add ability to replay token processing
1 parent 4000470 commit 8071ad3

File tree

4 files changed

+110
-51
lines changed

4 files changed

+110
-51
lines changed

html-api-debugger/html-api-integration.php

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,19 @@ function get_tree( string $html, array $options ): array {
152152
$doctype_public_identifier = null;
153153
$doctype_system_identifier = null;
154154

155+
$playback = array();
156+
157+
$last_html = '';
155158
while ( $processor->next_token() ) {
159+
$playback[] = array( $last_html, $tree );
160+
/**
161+
* The bookmark of the current token.
162+
*
163+
* @var \WP_HTML_Span
164+
*/
165+
$bookmark = $processor_bookmarks->getValue( $processor )[ $processor_state->getValue( $processor )->current_token->bookmark_name ];
166+
$last_html = substr( $html, 0, $bookmark->start + $bookmark->length );
167+
156168
if ( $processor->get_last_error() !== null ) {
157169
break;
158170
}
@@ -183,7 +195,7 @@ function get_tree( string $html, array $options ): array {
183195
$current['childNodes'][] = array(
184196
'nodeType' => NODE_TYPE_DOCUMENT_TYPE,
185197
'nodeName' => $doctype_name,
186-
'_span' => $processor_bookmarks->getValue( $processor )[ $processor_state->getValue( $processor )->current_token->bookmark_name ],
198+
'_span' => $bookmark,
187199
'_mode' => $processor_state->getValue( $processor )->insertion_mode,
188200
'_bc' => $processor->get_breadcrumbs(),
189201
'_depth' => $get_current_depth(),
@@ -217,13 +229,14 @@ function get_tree( string $html, array $options ): array {
217229
}
218230

219231
$namespace = method_exists( WP_HTML_Processor::class, 'get_namespace' ) ? $processor->get_namespace() : 'html';
232+
220233
$self = array(
221234
'nodeType' => NODE_TYPE_ELEMENT,
222235
'nodeName' => $tag_name,
223236
'attributes' => $attributes,
224237
'childNodes' => array(),
225238
'_closer' => (bool) $processor->is_tag_closer(),
226-
'_span' => $processor_bookmarks->getValue( $processor )[ $processor_state->getValue( $processor )->current_token->bookmark_name ],
239+
'_span' => $bookmark,
227240
'_mode' => $processor_state->getValue( $processor )->insertion_mode,
228241
'_bc' => $processor->get_breadcrumbs(),
229242
'_virtual' => $is_virtual(),
@@ -266,7 +279,7 @@ function get_tree( string $html, array $options ): array {
266279
'nodeType' => NODE_TYPE_TEXT,
267280
'nodeName' => $processor->get_token_name(),
268281
'nodeValue' => $processor->get_modifiable_text(),
269-
'_span' => $processor_bookmarks->getValue( $processor )[ $processor_state->getValue( $processor )->current_token->bookmark_name ],
282+
'_span' => $bookmark,
270283
'_mode' => $processor_state->getValue( $processor )->insertion_mode,
271284
'_bc' => $processor->get_breadcrumbs(),
272285
'_virtual' => $is_virtual(),
@@ -281,7 +294,7 @@ function get_tree( string $html, array $options ): array {
281294
'nodeType' => NODE_TYPE_CDATA_SECTION,
282295
'nodeName' => $processor->get_token_name(),
283296
'nodeValue' => $processor->get_modifiable_text(),
284-
'_span' => $processor_bookmarks->getValue( $processor )[ $processor_state->getValue( $processor )->current_token->bookmark_name ],
297+
'_span' => $bookmark,
285298
'_mode' => $processor_state->getValue( $processor )->insertion_mode,
286299
'_bc' => $processor->get_breadcrumbs(),
287300
'_virtual' => $is_virtual(),
@@ -296,7 +309,7 @@ function get_tree( string $html, array $options ): array {
296309
'nodeType' => NODE_TYPE_COMMENT,
297310
'nodeName' => $processor->get_token_name(),
298311
'nodeValue' => $processor->get_modifiable_text(),
299-
'_span' => $processor_bookmarks->getValue( $processor )[ $processor_state->getValue( $processor )->current_token->bookmark_name ],
312+
'_span' => $bookmark,
300313
'_mode' => $processor_state->getValue( $processor )->insertion_mode,
301314
'_bc' => $processor->get_breadcrumbs(),
302315
'_virtual' => $is_virtual(),
@@ -310,7 +323,7 @@ function get_tree( string $html, array $options ): array {
310323
'nodeType' => NODE_TYPE_COMMENT,
311324
'nodeName' => $processor->get_token_name(),
312325
'nodeValue' => $processor->get_modifiable_text(),
313-
'_span' => $processor_bookmarks->getValue( $processor )[ $processor_state->getValue( $processor )->current_token->bookmark_name ],
326+
'_span' => $bookmark,
314327
'_mode' => $processor_state->getValue( $processor )->insertion_mode,
315328
'_bc' => $processor->get_breadcrumbs(),
316329
'_virtual' => $is_virtual(),
@@ -322,7 +335,7 @@ function get_tree( string $html, array $options ): array {
322335
case '#comment':
323336
$self = array(
324337
'nodeType' => NODE_TYPE_COMMENT,
325-
'_span' => $processor_bookmarks->getValue( $processor )[ $processor_state->getValue( $processor )->current_token->bookmark_name ],
338+
'_span' => $bookmark,
326339
'_mode' => $processor_state->getValue( $processor )->insertion_mode,
327340
'_bc' => $processor->get_breadcrumbs(),
328341
'_virtual' => $is_virtual(),
@@ -371,6 +384,7 @@ function get_tree( string $html, array $options ): array {
371384
throw new Exception( "Unhandled token type for tree construction: {$serialized_token_type}" );
372385
}
373386
}
387+
$playback[] = array( $last_html, $tree );
374388

375389
if ( null !== $processor->get_last_error() ) {
376390
if ( method_exists( WP_HTML_Processor::class, 'get_unsupported_exception' ) && $processor->get_unsupported_exception() ) {
@@ -387,6 +401,7 @@ function get_tree( string $html, array $options ): array {
387401

388402
return array(
389403
'tree' => $tree,
404+
'playback' => $playback,
390405
'compatMode' => $compat_mode,
391406
'doctypeName' => $doctype_name,
392407
'doctypePublicId' => $doctype_public_identifier,

html-api-debugger/interactivity.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,19 @@ class="html-api-debugger-container html-api-debugger--grid"
169169

170170
<div>
171171
<h2>Processed HTML</h2>
172+
<div data-wp-bind--hidden="!state.htmlapiResponse.result.playback">
173+
<label>
174+
Move the slider to replay token processing:
175+
<input
176+
type="range"
177+
min="2"
178+
style="width:100%"
179+
data-wp-bind--max="state.htmlapiResponse.result.playback.length"
180+
data-wp-bind--value="state.htmlapiResponse.result.playback.length"
181+
data-wp-on--input="handlePlaybackChange"
182+
>
183+
</label>
184+
</div>
172185
<pre class="html-text" id="processed-html" data-wp-ignore><?php echo esc_html( $html ); ?></pre>
173186
</div>
174187

html-api-debugger/readme.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Add a page to wp-admin for debugging the HTML API.
1212
== Changelog ==
1313

1414
= 2.0 =
15+
* Add ability to replay token processing.
1516

1617
= 1.9 =
1718
* Update WordPress Playground links to use current query args.

html-api-debugger/view.js

Lines changed: 74 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,15 @@ let mutationObserver = null;
4848
* @typedef HtmlApiResponse
4949
* @property {any} error
5050
* @property {Supports} supports
51-
* @property {{tree: any, compatMode:string, doctypeName:string, doctypePublicId:string, doctypeSystemId:string }|null} result
51+
* @property {{tree: any, compatMode:string, doctypeName:string, doctypePublicId:string, doctypeSystemId:string, playback: ReadonlyArray<[string,any]> }|null} result
5252
* @property {string|null} normalizedHtml
5353
* @property {string} html
5454
*
5555
*
5656
* @typedef State
57+
* @property {any|undefined} playbackTree
58+
* @property {string|undefined} playbackHTML
59+
* @property {number|null} playbackPoint
5760
* @property {string|null} htmlApiDoctypeName
5861
* @property {string|null} htmlApiDoctypePublicId
5962
* @property {string|null} htmlApiDoctypeSystemId
@@ -82,8 +85,7 @@ let mutationObserver = null;
8285
* @property {DOM} DOM
8386
* @property {boolean} hasMutatedDom
8487
* @property {HTMLAPISpan|false} span
85-
* @property {string} hoverSpan
86-
* @property {(span:HTMLAPISpan) => readonly [string,string,string]} hoverSpanSplit
88+
* @property {string} htmlForDisplay
8789
*/
8890

8991
/**
@@ -120,9 +122,27 @@ const store = createStore(NS, {
120122
quirksMode: Boolean(localStorage.getItem(`${NS}-quirksMode`)),
121123
fullParser: Boolean(localStorage.getItem(`${NS}-fullParser`)),
122124

125+
playbackPoint: null,
123126
previewCorePrNumber: null,
124127
previewGutenbergPrNumber: null,
125128

129+
get playbackTree() {
130+
if (store.state.playbackPoint === null) {
131+
return undefined;
132+
}
133+
return store.state.htmlapiResponse.result?.playback?.[
134+
store.state.playbackPoint
135+
]?.[1];
136+
},
137+
get playbackHTML() {
138+
if (store.state.playbackPoint === null) {
139+
return undefined;
140+
}
141+
return store.state.htmlapiResponse.result?.playback?.[
142+
store.state.playbackPoint
143+
]?.[0];
144+
},
145+
126146
/** @type {Link|null} */
127147
get previewCoreLink() {
128148
if (!store.state.previewCorePrNumber) {
@@ -223,51 +243,33 @@ const store = createStore(NS, {
223243
return store.state.htmlPreambleForProcessing + store.state.html;
224244
},
225245

226-
get hoverSpan() {
246+
get htmlForDisplay() {
227247
/** @type {string | undefined} */
228-
const html = store.state.htmlapiResponse.html;
248+
const html = store.state.playbackHTML ?? store.state.htmlapiResponse.html;
229249
if (!html) {
230250
return '';
231251
}
232252
return store.state.showInvisible ? replaceInvisible(html) : html;
233253
},
234-
235-
/** @param {HTMLAPISpan} span */
236-
hoverSpanSplit(span) {
237-
/** @type {string | undefined} */
238-
const html = store.state.htmlapiResponse.html;
239-
if (!html) {
240-
return /** @type {const} */ (['', '', '']);
241-
}
242-
const buf = new TextEncoder().encode(html);
243-
const decoder = new TextDecoder();
244-
245-
const { start: spanStart, length } = span;
246-
const spanEnd = spanStart + length;
247-
const split = /** @type {const} */ ([
248-
decoder.decode(buf.slice(0, spanStart)),
249-
decoder.decode(buf.slice(spanStart, spanEnd)),
250-
decoder.decode(buf.slice(spanEnd)),
251-
]);
252-
253-
return store.state.showInvisible
254-
? // @ts-expect-error It's fine, really.
255-
/** @type {typeof split} */ (split.map(replaceInvisible))
256-
: split;
257-
},
258254
},
259255

260256
clearSpan() {
261257
const el = /** @type {HTMLElement} */ (
262258
document.getElementById('processed-html')
263259
);
264260
el.classList.remove('has-highlighted-span');
265-
el.textContent = store.state.hoverSpan;
261+
el.textContent = store.state.htmlForDisplay;
266262
},
267263

268264
/** @param {MouseEvent} e */
269265
handleSpanOver(e) {
270266
const target = /** @type {HTMLElement} */ (e.target);
267+
268+
const html = store.state.playbackHTML ?? store.state.htmlapiResponse.html;
269+
if (!html) {
270+
return;
271+
}
272+
271273
/** @type {HTMLElement|null} */
272274
const spanElement = target.dataset['spanStart']
273275
? target
@@ -276,20 +278,30 @@ const store = createStore(NS, {
276278
if (!spanElement) {
277279
return;
278280
}
279-
const { spanStart, spanLength } = spanElement.dataset;
280-
if (!spanStart || !spanLength) {
281+
282+
const { spanStart: spanStartVal, spanLength: spanLengthVal } =
283+
spanElement.dataset;
284+
if (!spanStartVal || !spanLengthVal) {
281285
return;
282286
}
287+
const spanStart = Number(spanStartVal);
288+
const spanLength = Number(spanLengthVal);
289+
290+
const buf = new TextEncoder().encode(html);
291+
const decoder = new TextDecoder();
292+
293+
const spanEnd = spanStart + spanLength;
294+
/** @type {readonly [Text,Text,Text]} */
295+
// @ts-expect-error trust me!
296+
const [before, current, after] = /** @type {const} */ ([
297+
decoder.decode(buf.slice(0, spanStart)),
298+
decoder.decode(buf.slice(spanStart, spanEnd)),
299+
decoder.decode(buf.slice(spanEnd)),
300+
]).map((text) => {
301+
const t = store.state.showInvisible ? replaceInvisible(text) : text;
302+
return document.createTextNode(t);
303+
});
283304

284-
// @ts-expect-error 3-tuple to 3-tuple
285-
const [before, current, after] = /** @type {readonly [Text,Text,Text]} */ (
286-
store.state
287-
.hoverSpanSplit({
288-
start: Number(spanStart),
289-
length: Number(spanLength),
290-
})
291-
.map((text) => document.createTextNode(text))
292-
);
293305
const highlightCurrent = document.createElement('span');
294306
highlightCurrent.className = 'highlight-span';
295307
highlightCurrent.appendChild(current);
@@ -502,6 +514,7 @@ const store = createStore(NS, {
502514
}
503515

504516
store.state.htmlapiResponse = data;
517+
store.state.playbackPoint = null;
505518
store.clearSpan();
506519

507520
if (data.error) {
@@ -536,13 +549,24 @@ const store = createStore(NS, {
536549
mutationObserver?.disconnect();
537550
store.state.hasMutatedDom = false;
538551

552+
const html = store.state.playbackHTML ?? store.state.htmlForProcessing;
553+
539554
iframeDocument.open();
540-
iframeDocument.write(store.state.htmlForProcessing);
555+
iframeDocument.write(html);
541556
iframeDocument.close();
542557

543-
if (store.state.htmlapiResponse.result?.tree) {
558+
const tree =
559+
store.state.playbackTree ?? store.state.htmlapiResponse.result?.tree;
560+
561+
const processedHtmlEl = /** @type {HTMLElement} */ (
562+
document.getElementById('processed-html')
563+
);
564+
processedHtmlEl.classList.remove('has-highlighted-span');
565+
processedHtmlEl.textContent = store.state.htmlForDisplay;
566+
567+
if (tree) {
544568
printHtmlApiTree(
545-
store.state.htmlapiResponse.result.tree,
569+
tree,
546570
// @ts-expect-error
547571
document.getElementById('html_api_result_holder'),
548572
{
@@ -593,6 +617,12 @@ const store = createStore(NS, {
593617
alert('Copy failed, make sure the browser window is focused.');
594618
}
595619
},
620+
621+
/** @param {InputEvent} e */
622+
handlePlaybackChange(e) {
623+
const val = /** @type {HTMLInputElement} */ (e.target).valueAsNumber;
624+
store.state.playbackPoint = val - 1;
625+
},
596626
});
597627

598628
/** @param {keyof State} stateKey */

0 commit comments

Comments
 (0)