Google Translate's usage of <font> for text replacement breaks React |
|||
Issue descriptionChrome Version: Version 68.0.3440.106 (Official Build) (64-bit) OS: All Desktops tested. React - a very very popular web development framework - breaks when Google Translate runs across a page. The issue that React tracks textNodes in the DOM to ensure that when the data model changes that it's quick to update the text if needed. Google translate introduces a font element that breaks the parentElement<->textNode relationship and thus breaks frameworks like React. Example: https://nn92zy7240.codesandbox.io/ press the button after the page has translated to see the JS Error. The question from the React team is: Why do we need to wrap the text in a font tag (thus changing the parent of the textNode)? Can we just replace the text node content and not introduce new DOM (this would drastically help the situation). I suspect this issue affects many more frameworks than just React (pretty much anything that uses a VDOM model to help keep the DOM updates quick.) One suggested solution was use MutationObserver to look for characterDataChanges, but we would still have to re-parent everything and this is slow and and also a very specific introduction to all of react to fix a problem that Google Translate introduces.
,
Aug 30
For now I'm going to recommend React users affected by this to monkeypatch Node.prototype.insertBefore/removeChild as a practical solution: https://github.com/facebook/react/issues/11538#issuecomment-417504600 It's going to make those websites a bit slower but to many users this would be preferable to complete breakage. I hope we can stop recommending this though.
,
Sep 14
Upping the priority on this. CC'ing John as he has touched the Element and may have thoughts.
,
Sep 17
I'm not very familiar with the element script, but it seems the <font> wrapper nodes are created to make it easy to restore the original text and DOM structure. junyin@ is the original author from way back in 2011 and may have some thoughts.
,
Sep 27
Anthony - feel free to reach out to junyin@ when you start working on this.
,
Oct 17
,
Nov 6
,
Dec 11
I don't know how these <font> tags are added but they're messing with existing markup too. Try translating https://s.codepen.io/MatTheCat/debug/PXwVyN/wQrPoNJmOLDM for instance.
,
Jan 5
> The issue that React tracks textNodes in the DOM to ensure that when the data model changes that it's quick to update the text if needed. Most modern frameworks and many extensions / javascruot libraries rely on references to Nodes to function. Example scenario: - Library A (ie. React / VDOM framework) renders a page and stores Node references in memory. - Library B (ie. Chrome translation) modifies the DOM while translating a page for an end-user. The fact of the matter is that Library A will break when it co-exists with Library B, since Library A relies on DOM Node references to function. If Library A is a framework such a React (or other VDOM frameworks), it likely does not utilize something like MutationObserver to keep its internal references updated (performance overhead). Instead, VDOM libraries are often written under the assumption that nothing modifies the DOM other than itself (thereby creating incompatibilities with Library B, such as Chrome's browser translation, as well as any other chrome extensions and other javascript library that modifies the DOM in a way that Library A isn't expecting - the problem is exacerbated if Library A doesn't proactively monitor the DOM to keep refs up to date following externally triggered DOM changes). Thus, Library B (ie. Chrome's Google Translate) must compensate for Library A's requirement to always maintain accurate DOM references. This creates a significant burden on all libraries (except Library A) to conform to Library A's requirements with regards to careful attention to DOM modification and Library A's maintenance of accurate Node refs. In my humble opinion, this is an unrealistic expectation to put on developers of browser extensions, javascript libraries, and chromium itself (in the context of fixing Google Translate to work with VDOM frameworks). > One suggested solution was use MutationObserver to look for characterDataChanges, but we would still have to re-parent everything and this is slow and and also a very specific introduction to all of react to fix a problem that Google Translate introduces. Google Translate is not what's introducing the problem. The problem is introduced when a javascript library assumes that it has full and exclusive control over the DOM (such as a VDOM library) without accounting for the fact that the DOM is inherently mutable by design. I point this out because the "problems that Google Translate introduces" are not unique to Google Translate. The problems are very common, and Google Translate is just one DOM-modifying feature of one browser that is making the symptoms of the underlying problem more apparent. If MutationObserver (or a similar solution to keep refs up to date) were introduced into Library A, it could potentially resolve this chromium ticket, while also resolving thousands of other incompatibilities that occur for the same reason (chrome extensions, etc). Even if a website owner wanted to give full and exclusive control of the DOM to a VDOM library, it's not practical if you also have end-users with DOM-modifying chrome extensions (ie. Grammarly, password managers, etc) or if you have users with browsers that modify the DOM (ie. Chrome's built-in translation functionality). There's a large ecosystem dependent on DOM manipulation - that ecosystem is an unintended victim of the adoption of VDOM frameworks. I have nothing against React or VDOM (I personally use and like them), but entertaining the idea of changing chromium in response to compatibility issues that arise with React is setting a curious precident. > The question from the React team is: Why do we need to wrap the text in a font tag (thus changing the parent of the textNode)? If Google Translate in Chrome no longer uses font tags, that won't fix the compatibility problem with React. The incompatibility is much more complex. > Can we just replace the text node content and not introduce new DOM (this would drastically help the situation). This is true (the part about "helping the situation", but this still won't solve the problem entirely). It is possible to implement a bunch of complicated logic to avoid introducing any new DOM elements in an attempt to prevent introducing new DOM nodes (or replacing existing ones). In the Google Translate example, it's very possible to re-implement that feature using complex DOM manipulation logic to translate text on the page while replacing the context of existing text nodes, and attempting to retain the original Element Nodes as well. The complexity arises in common cases such as: <p>This is a sentence <a>with a link</a> inside</p> In some languages, the translation for the above phrase would require re-arranging the DOM tree. The Google translation for the phrase shifts the link to the end of the parent Node, and changes the total number of nodes from 5 in the original example to 4 in the translated example. (Without changing the translation to match the pre-existing DOM structure, it's very difficult, if not impossible, to maintain the references for a 3rd party VDOM framework in many edge cases such as this, where the DOM tree needs to fundamentally change) <p>這是一個句子<a>,裡面有一個鏈接</a></p> ... Getting back to the original issue, is this actually a chromium bug? To more accurately capture the core issue, an alternative title for this ticket to consider might be "Google Translate's modification of the DOM tree breaks React". Google Translate's use of <font> isn't the difficult problem here. Removing use of <font> would be the easy part, but fixing the DOM manipulations executed by Google Translate in a way that wouldn't break React's reference to the Node is near impossible. When the problem is reframed as a DOM tree / node reference problem caused by DOM manipulation (rather than simply <font> tags breaking React), the solution to this ticket becomes extremely difficult. Sure, we could easily remove <font> from Google Translate, and we could even re-use the existing DOM tree and the existing nodes on the tree to minimize the creation of unnecessary new nodes. But since translations (by their nature) will require the creation, deletion, and repositioning of nodes, such as in the example translation above. React would still need to adapt to changes to the DOM structure, as required by the specific translation being rendered. The repositioning and modification of the DOM tree is the cause of the problem today (without fixing anything), but it seems the solution would also require DOM tree modification and node repositioning. You'd see the benefit of cleaner markup with less <font> tags, but the underlying problem of React not reacting well to DOM manipulations will still be a problem when rendering certain translations. I might be missing something, but it sounds like to root cause of why Google Translate breaks React apps is Google Translate's operations related to overwriting/restructuring/modifying the DOM tree and its nodes. In any conceivable solution, the root cause of the issue (at least to me) seems impossible to resolve since translations will always require DOM tree changes and node additions/removals. If the React team is following this thread, it might be helpful for chromium maintainers to understand how the following source > target translation could be rendered without breaking React due to the DOM manipulation that is still required, despite the re-use of text nodes and existing dom tree when possible: Source phrase: <p>This is a sentence <a>with a link</a> inside</p> Target phrase: <p>這是一個句子<a>,裡面有一個鏈接</a></p> Source DOM tree: -> ELEMENT_NODE <p> ---> TEXT NODE ---> ELEMENT_NODE <a> ------> TEXT NODE ---> TEXT_NODE Target DOM tree: -> ELEMENT_NODE <p>. (Node reused from existing DOM tree) ---> TEXT NODE (Newly created text node, or re-use of the original TEXT_NODE from the source tree) ---> ELEMENT_NODE <a> (Node reused from existing DOM tree) ------> TEXT NODE (Node reused from existing DOM with text inside replaced) //-> Last TEXT_NODE from source DOM tree deleted (no trailing text node in Chinese translation) |
|||
►
Sign in to add a comment |
|||
Comment 1 by anthonyvd@chromium.org
, Aug 13Owner: anthonyvd@chromium.org
Status: Assigned (was: Untriaged)