盘点浏览器翻译插件对DOM结构的影响及解决

翻译插件能够帮助我们快捷完成网页的翻译,但这种便利是有代价的,因为它会干扰许多现代网站的运作。这是因为翻译插件以破坏DOM结构的方式操作 DOM ,这会带来如 removeChild 方法的错误、让网站崩溃

本文将会探讨以下几点:

  • 翻译实际带来的问题 - 最近遇到的bug

  • 翻译插件的工作原理

  • 可能的解决方案

遇到问题

这段时间,有个别用户反馈在使用某项目时出现网站崩溃的情况:在选择框中输入文本后页面白屏

收到反馈后第一反应肯定是想办法复现它,可是在本地怎么就是复现不出来,即使使用同样的账号、网址和操作

--> 这个项目已经上线很长时间,大部分用户在使用上也没有出现类似问题,难道只能跑去找用户、用他电脑现场排查下吗?

在准备约用户前,突然灵光一动,想到之前有遇到过翻译导致的问题,也知道翻译会改变dom结构,于是赶紧打开翻译功能试了下,果然,问题复现了!

最小复现案例和步骤:

  • 可多选的选择器

复制 antd 官网中的 treeSelect 示例代码,生成一个支持多选的选择器

  • 输入内容
  • 删除内容

删除后出现报错:

原因探究

翻译插件运行时到底做了什么

既然已经知道问题是由翻译引起,那就得知道在翻译时,到底在页面上做了什么手脚

对于一段简单的 HTML 元素:

css 复制代码
<div>hello</div>

在开启翻译后,会变为

css 复制代码
<div><font>你好</font></div>

它会对页面上的文本内容进行翻译,并将翻译结果使用 font 标签进行包裹

看似只是改变了文本,并给文本加了个新标签,但实际的dom结构变化如下:

从图里可以看出,原始的TextNode文本实际已经被卸载了! 这个对 DOM 的影响就是产生问题的主要原因。

什么时候会发生问题

对节点的操作包括了更新值、删除或者添加子项,翻译插件对 DOM 结构的影响会对节点的这三个操作都带来影响

更新节点 -> 未及时更新文本 /数字

最小复现示例如下,当点击按钮 addCount 后,页面上对应的count没有同步更新

javascript 复制代码
import { useEffect, useState } from 'react';

function App() {
  const [count, setCount] = useState(1);
  
  useEffect(() => {
    console.log(count);
  }, [count]);
  
  return (
    <div>
      hello World{count}
      <button onClick={() => setCount((count) => count + 1)}>addCount</button>
    </div>
  );
}
export default App;

原因在于翻译插件将hello World{count}的内容合并到了一个<font>标签中,导致真实的{count}对应的节点已经不存在页面中了,故更新无效;

这问题不会导致浏览器崩溃,甚至不会有报错信息,在排查起来实际会更加困难,同时对用户也更有误导性

删除节点 -> 页面崩溃

最小复现示例如下,当点击按钮 addCount 后,页面报错

javascript 复制代码
import { useState } from 'react';

function App() {
  const [count, setCount] = useState(1);
  return (
    <div>
      hello World
      {count % 2 && <span>此时是{count}</span>}
      <button onClick={() => setCount((count) => count + 1)}>addCount</button>
    </div>
  );
}
export default App;

原因在于当count从1变为2后,React 尝试通过<span>的父级来卸载文本内容,却无法找到对应的节点

报错信息如下:

csharp 复制代码
Uncaught NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.

复现要求:子节点的 TextNode 个数大于1,且数量会发生改变

复现场景1:有条件渲染的节点具有同级的兄弟节点

less 复制代码
// 1.会出问题
  <div>
      hello World
      {count % 2 && <span>此时是{count}</span>}
  </div>

// 2. 会出问题
  <div>
      {count % 2 && <span>此时是{count}</span>}
       hello World
  </div>


// 3. 没有问题
    <div>{count % 2 && <span>此时是{count}</span>}</div>

复现场景2:使用三元表达式渲染不同数量的文本节点

xml 复制代码
 <div>
        {count % 2 ? (
          <span>此时是{count}</span>
        ) : (
          <>
            <span>此时是</span>
            {count}
          </>
        )}
      </div>

除了上述两种场景,还有很多方式可以改变渲染的 TextNode 数量,这使得很难找到解决所有情况的有效方法

添加节点 -> 页面崩溃

最小复现示例如下,当count从3变为4时,控制台报错

javascript 复制代码
import { useState } from 'react';

function App() {
  const [count, setCount] = useState(1);
  return (
    <div>
      hello World
      <div>
        {count % 2 ? <span>此时是{count}</span> : count}
        {count > 2 && 'Oops快出问题了'}
      </div>
      <button onClick={() => setCount((count) => count + 1)}>addCount</button>
    </div>
  );
}
export default App;

报错信息:

csharp 复制代码
Uncaught NotFoundError: Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.

解决方案

目前还没有终极解决方案,禁用翻译暂时是最好的解决办法,在需要国际化时,通过自己的应用程序实现本地化,使机器翻译变得没有必要。

通过设置HTML标签的lang 换为 zh,浏览器即使开启了自动翻译功能,也不会再自动翻译我们的页面了

html 复制代码
<html lang="zh">
...
</html>

不过这并不能阻止用户手动强行翻译,因此如果要从根本上禁止翻译,可以结合translate="no"一起使用

html 复制代码
<html lang="zh" translate="no">
...
</html>

不仅仅是翻译插件

翻译插件导致问题的根本原因是对DOM结构的干扰,这可能会对JavaScript操作DOM节点造成影响,这类问题在采用"虚拟DOM"技术的框架中更为明显,因为虚拟DOM通过保留对所有DOM节点的引用,实现更新时只需要更改实际发生变化的DOM部分(这一过程称为协调),因此它要求对DOM具有完全的控制和独占性,这意味着在与第三方扩展插件协作时,若这些插件修改了页面的DOM结构,就可能引发上述提到的问题

查找 BUG 经验和思路小结

  • 当所有用户都出问题时,那一定是代码有问题;

  • 当极小部分用户出问题时,考虑是否是翻译插件导致;

  • 当只有一些高级用户(会使用浏览器插件的人)出问题时,考虑是否为第三方拓展插件导致

参考文章:

  1. 关于 Google 翻译导致 React(和其他 Web 应用程序)崩溃的一切
  2. github-issue - 使 React 对来自 Google 翻译 的 DOM 突变具有弹性
  3. stackoverflow --- React DOMException:在 'Node' 上执行 'removeChild' 失败:要删除的节点不是该节点的子节点
  4. chrome浏览器翻译可能引发的页面问题
相关推荐
kiramario1 分钟前
用IconContext.Provider修改react-icons的icon样式
前端·javascript·react.js
destinyol2 分钟前
React首页加载速度优化
前端·javascript·react.js·webpack·前端框架
程序员小续3 分钟前
React 多个 HOC 嵌套太深,会带来哪些隐患?
java·前端·javascript·vue.js·python·react.js·webpack
大猫会长29 分钟前
用AbortController取消事件绑定
前端
程序员小杰@1 小时前
AI前端组件库Ant DesIgn X
开发语言·前端·人工智能
致微1 小时前
Vue项目 bug 解决
前端·vue.js·bug
慕斯策划一场流浪2 小时前
fastGPT—nextjs—mongoose—团队管理之部门相关api接口实现
前端·javascript·html·fastgpt部门创建·fastgpt团队管理·fastgpt部门成员更新·fastgpt部门成员创建
我自纵横20233 小时前
事件处理程序
开发语言·前端·javascript·css·json·ecmascript
坊钰3 小时前
【MySQL 数据库】数据类型
java·开发语言·前端·数据库·学习·mysql·html
我是小路路呀3 小时前
css 文字换行每一个字渐变
前端·css