Re: [WICG/webcomponents] Need a callback for when children changed or parser finished parsing children (#809)

Updating on my case of streaming into a detached document and selectively moving nodes to the visible document. I found a much more efficient solution that also reliably identifies the closing tag by skipping the MutationObserver altogether and using a NodeIterator instead. Something similar might be possible for other use cases. Rough solution:

```javascript
// Only works when streaming into an empty detached document. Otherwise additional logic is needed.
function elementIsClosed(element) {
  let currentElement = element;
  while (currentElement) {
    if (currentElement.nextSibling) return true;
    currentElement = currentElement.parentElement;
  }
  return false;
}

const iterator = new NodeIterator(detachedDocument, NodeFilter.SHOW_ELEMENT);
let element;
const response = await fetch("https://example.com");
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();

while (true) {
  const { done, value } = await reader.read();
  if (done) return;
  detachedDocument.write(value);

  element = iterator.nextNode();
  while (element) {
    const isClosed = elementIsClosed(element);
    // Perform processing
    element = iterator.nextNode();
  }
}

detachedDocument.close();

// Process the root element if needed, document is closed so the root element is closed
```

This works for a few reasons:
1.  document.write() and NodeIterator are both synchronous, so each chunk is written and processed synchronously.
2. Assuming the HTML is well-formed, the document and NodeIterator will both write and iterate depth-first. This allows the elementIsClosed() function to work reliably. If we find a nextSibling or a nextSibling of a parent, we know we encountered the close tag of the current element for something to be written after it.
3. This assumption only holds in the detached document, not the visible document. It also only holds if we strictly remove nodes from the detached document and never add or move elements.
4. It's possible to run similar logic on an open element that is streamed into the visible document, but needs to also keep track of the root node being streamed in the elementIsClosed() function and stop bubbling the parentElement at that point, since we expect nodes to exist outside of that root.
5. If an element is moved to the visible document when it is open, it will continue to stream into the visible document. The NodeIterator in the detached document is live, so it will not find any of the children streaming element. The processing step won't find any new elements on subsequent chunks (at first). Once the streaming element closes, the next element will be written after it into the detached document, and iterator.nextNode() will again return that element. This can also be used to determine that the streamed element is closed, if needed.

In some simple tests, a slower device can iterate a ~5000 node document in ~10ms with some basic conditional checks like attribute lookups.

-- 
Reply to this email directly or view it on GitHub:
https://github.com/WICG/webcomponents/issues/809#issuecomment-2087913895
You are receiving this because you are subscribed to this thread.

Message ID: <WICG/webcomponents/issues/809/2087913895@github.com>

Received on Wednesday, 1 May 2024 03:15:06 UTC