浏览器页面卡住定位分析

简介: 有童鞋在xxx系统页面反馈,遇到在弹出框后整个页面卡住无法使用的情况,属于必现问题。因此需要跟踪定位问题。

背景


有童鞋在xxx系统页面反馈,遇到在弹出框后整个页面卡住无法使用的情况,属于必现问题。因此需要跟踪定位问题。

问题定位


一般在浏览器遇到这种问题,基本上都某段逻辑进入死循环导致浏览器内核处理不过来导致的页面卡住动,这个时候需要使用Chrome开发调试工具进行跟踪定位的。

chrome source调试工具


按照F12或者Command + Alt + J可以打开浏览器的调试工具,然后找到Source源代码Tab页。如下图所示:

621272696e0877fb126acab3843a71c.png

步骤一:点击=按钮,进入调试模式:


a3a1c88cb870ae0dbdb5e710ddf6927.png

步骤二:调试代码,查看卡住代码段:


3e3fe77c4f4acbaebf9f3a426e8919a.png

步骤三: 定位代码段


确定是哪段代码后,就可以开始分析代码段是属于哪里,最终找到classname,定位到是属于水印插件导致。

问题分析(水印插件)


为什么?


为什么水印插件会导致页面进行死循环呢?这个就要跟踪到水印插件,目前采用的@pansy/watermark开源插件,然后找到其github issues,看看有没有相关issues。果不其然,还真的找到了,如下:

3acd86f73050b5c218f95db2a97c327.png

全局水印与其它遮罩层冲突那么接下来就开始跟踪他们的代码进行具体分析。

水印插件实现原理


在上面调试问题的时候,我们有看到一段代码MoutationRecord,其实这已经算是水印插件的重要实现原理之一了。


看了插件源代码,其实就几千行代码,水印插件实现原理有几个点:

1.利用Shadow DOM或者divDOM节点去插入水印,利用z-index显示到最前方

2.同时设置pointer-events: none;禁止任何操作,包括:选择、点击等,实现不阻碍其他元素操作

3.利用canvas生成水印图片(base64)

4.设置水印节点背景图片为水印图片(base64)

5.使用MutationObserver监听dom元素变化重复渲染生成水印图片,防止水印被人为删除 其中涉及到几个关键技术点为:

  1. pointer-events: none
  2. 通过canvas生成图片
  3. MutationObserver监听 当然,还有Shadow DOM和canvas等技术点也可以自己去研究学习,后面再用一些篇章详细讲解。

pointer-events


从MDN中它是这么定义的:

** pointer-events ** CSS 属性指定在什么情况下 (如果有) 某个特定的图形元素可以成为鼠标事件的 target (en-US)

简单解释一下,就是可以通过该属性设置DOM元素的鼠标事件,很多对应值都是给svg响应鼠标事件范围所设置的。


这里重点解释一下autonone两种值:

auto:与 pointer-events 属性未指定时的表现效果相同,对于 SVG 内容,该值与 visiblePainted 效果相同

none:元素永远不会成为鼠标事件的target (en-US)。但是,当其后代元素的 pointer-events 属性指定其他值时,鼠标事件可以指向后代元素,在这种情况下,鼠标事件将在捕获或冒泡阶段触发父元素的事件侦听器。

简单说,auto是默认值,可以触发该元素本身就有的鼠标事件。none则代表取消该元素原有的鼠标事件,可以直接透过该元素直接触发下方元素鼠标事件。 举个例子:

<style>
a[href="http://example.com"] {
  pointer-events: none;
}
</style>
<ul>
<li><a href="https://developer.mozilla.org/">MDN</a></li>
<li>
<!-- 点击链接 http://example.com 时,不会跳转 -->
<a href="http://example.com">example.com</a></li>
</ul>

MutationObserver监听


定义


MDN定义:

MutationObserver 接口提供了监视对 DOM 树所做更改的能力。它被设计为旧的 Mutation Events 功能的替代品,该功能是 DOM3 Events 规范的一部分。

简单说,就是可以监听某个DOM节点下元素发生变化触发的事件。 支持方法:

  • disconnect(),注销监听方法
  • observe(),开始监听
  • takeRecords(), 取消通知队列

具体使用案例(来自MDN):

// 选择需要观察变动的节点
const targetNode = document.getElementById('some-id');
// 观察器的配置(需要观察什么变动)
const config = { attributes: true, childList: true, subtree: true };
// 当观察到变动时执行的回调函数
const callback = function(mutationsList, observer) {
    // Use traditional 'for loops' for IE 11
    for(let mutation of mutationsList) {
        if (mutation.type === 'childList') {
            console.log('A child node has been added or removed.');
        }
        else if (mutation.type === 'attributes') {
            console.log('The ' + mutation.attributeName + ' attribute was modified.');
        }
    }
};
// 创建一个观察器实例并传入回调函数
const observer = new MutationObserver(callback);
// 以上述配置开始观察目标节点
observer.observe(targetNode, config);
// 之后,可停止观察
observer.disconnect();

常用场景


  • 防止第三方注入js文件(运营商劫持)
  • 防止删除前端生成的水印
  • 用来处理页面的敏感数据
  • Vue.$nextTick的实现微任务原理

所以从问题定位中,分析水印陷入死循环很可能就是这一部分代码。

定位问题


回到最开始,我们是什么时候会遇到页面卡顿,当页面出现弹框的时候,会出现页面卡顿。

同时找到插件的issues(查看issue也是一种快速解决问题的途径),描述如下:

vue版本使用全局水印 :is-body="true"  并且开启保护模式的情况下,触发带遮罩的事件就会导致页面无响应,且无法恢复; 带遮罩的事件如对话框弹窗/图片点击放大; 测试后发现关闭保护模式 watermark.options.monitor = false ,或者不使用全局水印没有出现该问题;这里可以看全文

定位到源码


watermark/packages/core/src/index.ts,第244行,代码如下:

...
if (MutationObserver &amp;&amp; this.options.monitor) {
      this.mutationObserver = new MutationObserver(mutations => {
        mutations.forEach(mutation => {
          if (this._isAgainRender(mutation)) {
            this.destroy();
            this._render();
            return;
          }
        });
      });
      this.mutationObserver.observe(this.container, observeOptions);
      this.shadowRoot && this.mutationObserver.observe(this.shadowRoot, observeOptions);
    }

结合上面MutationObserver的作用,主要是监听某个DOM容器内节点变化,然后重新渲染水印,从而避免水印被人删除。


那么我们可以很简单的猜测一下,在弹框出来后,会触发MutationObserver,然后_render函数改变dom,又会触发自己本身的dom节点变化,再触发MutationObserver,导致陷入死循环。


目前执行顺序:

1.body容器被监听到DOM节点变化,触发MutationObserver事件

2.MutationObserver事件返回参数mutations是一个数组,可能会重复执行_render函数

3.多次_render函数会继续注册MutationObserver事件,导致后续body容器变更持续被监听到,进入死循环逻辑中

因此,只需要保证多次MutationObserver事件只触发一次_render函数,即可避免死循环逻辑。

问题解决


直接下载源码,放到本地调试(过程忽略),最终代码暂时如下:

... 
if (MutationObserver &amp;&amp; this.options.monitor) {
      this.mutationObserver = new MutationObserver(mutations => {
    this.mutationObserver = new MutationObserver(mutations => {
        // 避免多次执行render函数,导致多次注册MutationObserver 从而进入死循环逻辑
        let lastMoutation;
        mutations.forEach(mutation => {
          if (this._isAgainRender(mutation)) {
            lastMoutation = mutation;
            return;
          }
        });
        if(lastMoutation){
          this.destroy();
          this._render();
        }
      });
      this.mutationObserver.observe(this.container, observeOptions);
      this.shadowRoot &amp;&amp; this.mutationObserver.observe(this.shadowRoot, observeOptions);
    }
...

后面可以研究一下为什么forEach || map函数无法跳出循环?

最后可以提交PR到开源github,这里面也有一些东西可以了解一下,如何在github上为开源项目提交PR?

这里是我提交的PR,全局水印与其它遮罩层冲突 [issue 129]

#参考资料MutationObserver MDN资料

目录
相关文章
|
7月前
|
缓存 JavaScript
vue阻止浏览器刷新和关闭页面提示
使用场景:在使用vuex进行缓存管理时,页面的缓存会随着页面关闭而消失,如果缓存动作仍在进行中,关闭页面会导致数据丢失,此时需要阻止页面关闭
1230 3
|
7月前
|
数据采集 Web App开发 JSON
浏览器插件:WebScraper基本用法和抓取页面内容(不会编程也能爬取数据)
本文以百度为实战案例演示使用WebScraper插件抓取页面内容保存到文件中。以及WebScraper用法【2月更文挑战第1天】
527 2
浏览器插件:WebScraper基本用法和抓取页面内容(不会编程也能爬取数据)
|
1月前
|
Web App开发 JavaScript 前端开发
使用 Chrome 浏览器的内存分析工具来检测 JavaScript 中的内存泄漏
【10月更文挑战第25天】利用 Chrome 浏览器的内存分析工具,可以较为准确地检测 JavaScript 中的内存泄漏问题,并帮助我们找出潜在的泄漏点,以便采取相应的解决措施。
300 9
|
1月前
|
存储 缓存 网络协议
计算机网络常见面试题(二):浏览器中输入URL返回页面过程、HTTP协议特点,GET、POST的区别,Cookie与Session
计算机网络常见面试题(二):浏览器中输入URL返回页面过程、HTTP协议特点、状态码、报文格式,GET、POST的区别,DNS的解析过程、数字证书、Cookie与Session,对称加密和非对称加密
|
6月前
|
前端开发 安全 UED
【项目实战】从终端到浏览器:实现 ANSI 字体在前端页面的彩色展示
在学习和工作中,我们经常需要使用日志来记录程序的运行状态和调试信息。而为了更好地区分不同的日志等级,我们可以使用不同的颜色来呈现,使其更加醒目和易于阅读。 在下图运行结果中,我们使用了 colorlog 库来实现彩色日志输出。通过定义不同日志等级对应的颜色,我们可以在控制台中以彩色的方式显示日志信息。例如,DEBUG 级别的日志使用白色,INFO 级别的日志使用绿色,WARNING 级别的日志使用黄色,ERROR 级别的日志使用红色,CRITICAL 级别的日志使用蓝色。
|
1月前
|
域名解析 缓存 网络协议
浏览器中输入URL返回页面过程(超级详细)、DNS域名解析服务,TCP三次握手、四次挥手
浏览器中输入URL返回页面过程(超级详细)、DNS域名解析服务,TCP三次握手、四次挥手
|
6月前
|
Web App开发 XML 开发框架
技术心得记录:在IE浏览器中的奇怪页面表现
技术心得记录:在IE浏览器中的奇怪页面表现
70 0
|
4月前
|
网络协议 前端开发 JavaScript
浏览器加载网页的幕后之旅:从URL到页面展示详解
【8月更文挑战第31天】当在浏览器地址栏输入URL并回车后,一系列复杂过程随即启动,包括DNS解析、TCP连接建立、HTTP请求发送、服务器请求处理及响应返回,最后是浏览器页面渲染。这一流程涉及网络通信、服务器处理和客户端渲染等多个环节。通过示例代码,本文详细解释了每个步骤,帮助读者深入理解Web应用程序的工作机制,从而在开发过程中作出更优决策。
77 5
|
4月前
|
Web App开发 编解码 监控
【Azure 媒体服务】Azure Media Player 在Edge浏览器中不能播放视频问题的分析与解决
【Azure 媒体服务】Azure Media Player 在Edge浏览器中不能播放视频问题的分析与解决
|
5月前
|
Web App开发 编解码
软件开发常见流程之兼容性和手机屏页面设计,PC端和移动端常见浏览器,国内的UC都是根据Webkit修改过来的内核,开发重点关注尺寸,常见移动端尺寸汇总,移动端,理想视口根据你设别的样式进行修改
软件开发常见流程之兼容性和手机屏页面设计,PC端和移动端常见浏览器,国内的UC都是根据Webkit修改过来的内核,开发重点关注尺寸,常见移动端尺寸汇总,移动端,理想视口根据你设别的样式进行修改