-
Notifications
You must be signed in to change notification settings - Fork 47k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add failing tests for resilience to Google Translate #13347
Conversation
Thank you for sending this out! This is super helpful. Right now I'm playing around with passing the index of the Fiber to the host config. React DOM can either: commitText: export function commitTextUpdate(
textInstance: TextInstance,
oldText: string,
newText: string,
parent,
index,
): void {
let actualNode = parent.childNodes[index]
if (actualNode !== textInstance) {
actualNode.replaceWith(textInstance);
}
textInstance.nodeValue = newText;
} Essentially: check to see if the DOM has what we expect. If not, fix it before applying the update. removeChild
Similarly. Don't remove a child that could have been replaced. Assuming we want to remove the child anyway, just remove the item at the current index. insertInContainerBefore export function insertInContainerBefore(
container: Container,
child: Instance | TextInstance,
beforeChild: Instance | TextInstance,
index: Number
): void {
let actualNode = container.childNodes[index]
if (actualNode !== beforeChild) {
actualNode.replceWith(beforeChild)
}
if (container.nodeType === COMMENT_NODE) {
(container.parentNode: any).insertBefore(child, beforeChild);
} else {
container.insertBefore(child, beforeChild);
}
} This behaves just like This approach actually fixes a few of the tests in this PR that assert incorrect markup. Like here. For fun, I'm calling this self healing. It works well for Google Translate because it swaps out the translated nodes in-place, which (as far as I can tell) triggers the MutationObserver bound to the parent. Text is magically re-translated. The problem with this approach is that the index for Fibers isn't updated as they are removed: as far as I can tell, this only happens after they are committed. It breaks down when you remove nodes, particularly for clearing all nodes, like:
What if we made it so that Deletion side-effects updated the index of sibling nodes? |
Fiber index won't help you here because it's fiber index, not a host node index. <div>
<React.Fragment>
<Child1>
<div />
</Child1>
</React.Fragment>
<div />
<React.Fragment>
<div /> <!-- fiber.index is 0, but hostIndex is 2 -->
</React.Fragment>
</div> the last div fiber's index will be |
Right. Would it be practical to track the host node index? If so, is there merit in the approach I've laid out? |
How would you track it without incurring overhead for every single operation? |
There would be overhead 😞 for most operations. Both to track the index, and to check for correctness. I wonder if MutationObserver is a good candidate here. It would watch insertions/deletions and to track unexpected mutations. Those DOM nodes could be placed in a map of "tainted" elements, which could be swapped back into the DOM when React needed to next operate on them. That would have its own overhead, but, speculatively, it could look like: const taintedNodes = new Map()
let guardian = new MutationObserver(mutationList => {
mutationList.forEach(mutation => {
switch(mutation.type) {
case 'childList':
// map added items to removed items when added items are not
// tracked by React
// ....
taintedNodes.add(removedNode, addedNode)
break;
}
})
})
guardian.observe(root, { attributes: false, childList: true, subtree: false })
function heal(node) {
if (taintedNodes.has(node)) {
// key is the child, value is the node that replaced it
taintedNodes.get(node).replaceWith(node)
taintedNodes.delete(node)
}
} I'm going to play around with it. Unfortunately, this adds overhead to every insertion/deletion too. |
Just following up, I started down this path and quickly discovered a few issues:
There might be something there, but it feels really mechanical and fraught with error. Back to the drawing board. |
If someone is looking for workaround: #11538 (comment) |
While #13341 didn't work out I figured I'd share the test cases I used with it. The test code is kinda wonky — it's adapted from our text node permutation tests, but with some twists to simulate what a mutation should do in each case.