glimmerjs / glimmer-vm

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.

johanrd opened this issue · comments

I see the error Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node popping up in sentry from time to time. I have yet to reproduce it when I try, but I know it freezes the whole UI when it happens.

Screen Shot 2022-11-25 at 13 07 14

It is not a very viable solution to tell users to uninstall various chrome extensions and/or not use google translate in order to not break a glimmer app. This has occurred both on Chrome and Edge.

Is it possible with a more resilient handling in clearElement?

export function clearElement(parent: SimpleElement) {
let current: Option<SimpleNode> = parent.firstChild;
while (current) {
let next = current.nextSibling;
parent.removeChild(current);
current = next;
}
}

Perhaps a try-catch? Or will that disturb some other inner workings?

Any news about this one?

@sebamza17 do you have any code that modifiers / remove DOM on its own, unknown to Glimmer?
(same for @johanrd )

A reproduction repo would be most helpful.

@NullVoxPopuli: For me, this error is logged in sentry from time to time from real world usage by customers (both from Chrome and Edge users)

No steps to reproduce yet (the closest I've got is html-next/vertical-collection#397), but a hypothesis is that some users have browser extensions that manipulate the DOM.

Anyways, glimmer should be more resilient to such errors IMO.

commented

Also seeing this pop up from time to time in our bug tracker, but unable to recreate it myself. Maybe just a specific error message instead of just the default DOMException would already help in tracking down the real issue?

We get this one in our logs using latest ember-table 5.0.6 and ember 3.28. We conditionally insert a div into the occluded list managed by vertical-collection, and the conditional's Comment Node in the DOM is the node implicated when breaking on the error.

<et.body as |b|>
  {{#if (eq this.rowHighlightId b.rowValue.id)}}
    <div class="rowHighlight"></div>
  {{/if}}
  <b.row/>
</et.body>

Conditionals have been a meaningful contributor for another glimmer-vm bug (#1410). Would be lovely if there was some common underlying bug with glimmer & conditionals, but I just mention this as a moonshot.

I can repro this by changing the language of my app with <html lang="es"> then use a browser's "translate this page" feature:
Screenshot 2023-08-23 at 2 23 37 PM

Wait for translate to modify the DOM, then do anything that causes the modified DOM to be removed (such as clicking a link to another route):
Screenshot 2023-08-23 at 2 24 23 PM

I haven't been able to figure out a workaround or way to ignore this error as this error halts Glimmer from rendering afterwards.

This hack fixes it works around the issue.
#1398 (comment)

@golampo Thanks but wrapping things that are affected by this issue in htmlSafe isn't safe nor feasible for most apps. I wouldn't say it "fixes it" but appreciate the idea.

I think this is a very common use case that warrants more resilient handling as OP suggests.

@shama The linked comment describes that the issue comes from the existing usages of htmlSafe. The workaround is modifying the output of htmlSafe. I didn't intend to suggest it as a permanent fix, and I mentioned in the comment that it's "not a reasonable solution in most cases". I would really love to see this fixed permanently, but it's still existing after several years despite multiple reports. While not ideal, the workaround has been the only option for allowing "translate" to function w/ out error that I'm aware of.

Let’s say the template is

{{#if this.isExpanded}}
  the time is {{this.currentTime}}.
{{/if}}

We kept a reference to the TextNode with the current time. When the time changes we need to update it. If at that time we discovered the text node is gone because google translate decided it’s ok to merge the adjacent text nodes together during translation, what are we supposed to do?

When the condition changes to false we need to clear the DOM. What are we supposed to do if we realized the nodes are not there anymore because Google translate decided to remove ours and make new ones in their place? Sure we can fail silently but that wouldn’t be correct either, you’ll just leave behind the translated text when it should have been removed.

I understand why it feels like we should “just fix it” but we need to be more specific about how we are expected to interop with things that are allowed to arbitrarily change the DOM we manage

Does anyone have a good debugging play when they encounter these? Perhaps a way to break execution the moment the Ember-managed element leaves the DOM in an unsupported way? Currently we are only seeing downstream of the root issue. We don't find out the DOM was invaded until Ember tries to remove an already-removed managed element. But reactivity broke long before then. Since we can't fix this downstream, we need to see if there are fixes that can be done at the root cause.

I think there's 2 classes of this issue. One is triggered by external actors, like Google translate or browser extension content scripts. The other kind comes from inside the house, e.g. it's possible to trigger these with handlebars conditionals inside VerticalCollection.

We should be able to address the instances caused by the Ember ecosystem itself, even if there's no good solution to the external actors problem.

There's maybe a clever spot to set up a MutationObserver. Temporarily during local development? With flag support inside Glimmer? Does knowing the parent element give us a foothold?

Being able to catch these in the moment will lead to reproduceable bug reports that don't involve external actors.

If there were a performant detector, it could even be used in production to show a banner or toast to users when they break reactivity via external actors. If we can't fix the root issue, maybe we can improve the user experience when it happens.

In the past I have tried to discourage page translation with some HTML meta. Something like

<!DOCTYPE html>
<html lang="en-US" translate="no" class="notranslate">
<head>
  <meta name="google" content="notranslate">

Although not specific glimmer and targeted at only a single root cause (page translation)

Also for context: Historical react issue facebook/react/issues/11538

also cross-linking #1438 (comment) for visibility and context

@gilest Yeah, I think that sounds about right and to the extent you are able to discourage these unauthorized mutations from happening, that would be the most ideal.

Beyond that, I think trying to catalog and the type of behavior of these tools may also help you find targeted workarounds. For example, if my assumption is correct that (one of) the issue is Google translate sometimes merging/replacing adjacent text nodes while doing the translation in a part of the DOM we are tracking, say:

{{!-- [ ... ] denotes a single text node --}}
[Hello, welcome ]{{#if @user.isNew}}[aboard]{{else}}[back]{{/if}}[.]
{{!-- rendered --}}
[Hello, welcome ][aboard][.]
{{!-- translated --}}
[你好,歡迎光臨。]

Then, maybe you can prevent that somewhat by:

{{!-- [ ... ] denotes a single text node --}}
[Hello, welcome ]{{#if @user.isNew}}<!-- -->[aboard]<!-- -->{{else}}[back]{{/if}}[.]
{{!-- [ ... ] denotes a single text node --}}
[Hello, welcome ]<!-- Glimmer can now use this comment node for the bound start -->[aboard]<!-- bound end -->[.]

If Google translate is civilized enough to know that it can't just remove comments randomly, then as far as we are concerned we don't care if they replace the middle text node. If we have specific cases we can analyze we can try to encourage the other party to play nicely, but yes, I think the root cause is not really fixable on our end.

Interesting. It sort of looks like Fastboot output markers for rehydration.

I think I read somewhere that Google Translate was particularly bad at mangling the DOM, like maybe it adds <font> nodes also.

Would your suggestion survive something like that?

Also curious about the performance impact of managing said comments... 🤔

Tracked by Chromium issue #872770

It's 5 years old... 💀