[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"content-a-bug-in-wechat-work":3,"$f97lzmgjnxq48":22},{"id":4,"type":5,"slug":6,"title":7,"date":8,"category":9,"tags":10,"body_markdown":14,"permalink":15,"excerpt_src":15,"media_type":15,"media_title":15,"media_author":15,"media_url":15,"rating":15,"layout":15,"pv":16,"admin_only":16,"created_at":17,"updated_at":17,"deleted_at":15,"status":18,"html":19,"excerpt":20,"cover":21},182,"article","a-bug-in-wechat-work","记一次企业微信webview bug排查","2018-07-24 10:40","web",[11,12,13],"bug","debug","企业微信","\n## 背景\n\n咱们在企业微信上开发了一个简单的选课系统，用于让公司内的同学在企业微信上进行选课。昨天下午HR的同学很开心地推送了8月份的课程列表，让大家报名。然后就炸锅了：企业微信中点击课程没反应，报不了名！\n\n收到消息第一时间感觉确认了一下问题，发现Mac下使用完全没有任何问题。于是打听了一下，出问题的都是Windows电脑的企业微信。直接使用Chrome浏览器访问或者在手机企业微信上访问都是没有问题的。\n\n那就简单啦，赶紧引导大家在浏览器或者手机企业微信上访问。问题解决！顺便感慨一下：狡兔三窟真重要啊，还好我们及早打通了OA登录，在各种环境下都可以登录使用。\n\n当然，夜深人静的时候，作为一名靠谱的前端工程师，还是要老老实实查问题的。于是诞生此文，流水账，记录bug排查的全程并揭秘原因。\n\n\u003C!-- more -->\n\n## TLDR\n\n> Too Long, Didn't Read\n>\n> 太长了，不想读\n\n好的，说重点：\n\n1. Windows企业微信某个版本的webview有bug，导致`'ontouchstart' in windows`为true，事实上它并不支持触控事件。\n2. 这会导致第三方库`iscroll`产生误判，认为这是一个触控设备，从而错误地将`disableMouse`设为`true`，进而忽略`mousedown\u002Fmouseup`事件，导致内部不向外发出`click`事件。\n3. 最终导致外层绑定的`click`事件处理函数不触发，点击无效。\n\n## 排查过程\n\n### 重现\n\n首先，想办法重现。\n\n打开虚拟机，下载企业微信，登录、开应用，一顿操作猛如虎，然后……没发现问题。\n\n重现失败。\n\n接下来找反馈方。您的系统是什么版本？您的企业微信是什么版本？您是什么网络？得知是旧版企业微信。于是赶紧让组里小伙伴确认一下，是不是有问题的升级一下就没有问题了。\n\n是。\n\n锁定问题范围：旧版本Windows企业微信下页面出现点击无效。\n\n虚拟机下载了一个旧版本企业微信，覆盖安装，再次登录、开应用，一顿操作猛如虎，然后果然看到了问题所在。\n\n接下来，想办法在开发环境重现问题。开启本机环境，配置hosts访问本机，问题依旧，重现成功。\n\n### 确认问题\n\n再接下来，确认问题来源。\n\n因为企业微信没有调试工具或者调试接口，只能先引入`vconsole`模块，确保日志能被看到。\n\n```sh\nnpm install vconsole\n```\n\n```javascript\nvar VConsole = require('vconsole');\nnew VConsole();\n```\n\n![VConsole界面](\u002Fassets\u002Fweb\u002F2018\u002Fa-bug-in-wechat-work\u002F1.png)\n\n展示一下项目的Vue代码：\n\n```vue\n\u003Ctemplate>\n    \u003Cdiv class=\"content\">\n        \u003Cdiv class=\"listWrapper\" ref=\"wrapper\">\n            \u003Cul>\n                \u003Cli v-for=\"course in courseList\" @click=\"chooseCourse\">{{course.name}}\u003C\u002Fli>\n            \u003C\u002Ful>\n        \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cscript>\nimport IScroll from 'iscroll';\n\nexport default {\n    created(){\n        this.scroll = new IScroll(this.$refs.wrapper, {\n            click: true,\n            tap: true,\n            wheel: true,\n            mouseWheel: true,\n        })\n    },\n    methods:{\n        chooseCourse(){\n\n        }\n    }\n}\n\u003C\u002Fscript>\n```\n\n核心逻辑是，`.listWrapper`部分会被`iscroll`模块处理，以便能够方便地使用无限滚动、滚动加载等功能。\n\n于是将`@click`分别绑定到`.listWrapper`内部和外部，发现外部的点击可以响应，内部的点击无法响应。\n\n问题被进一步缩小范围：`iscroll`导致的元素点击事件不触发。\n\n接下来就想到将代码中初始化过的`iscroll`实例对象的`options`属性打印出来进行对比，看看是否初始化的时候环境检测结果有些不一样。果然发现了一点区别：\n\n没有问题的浏览器上：\n\n```\ndisableMouse: true\ndisablePointer: false\ndisableTouch: true\n```\n\n有问题的浏览器上：\n\n```\ndisableMouse: true\ndisablePointer: true\ndisableTouch: false\n```\n\n可以看到`disablePointer`和`disableTouch`的值是不一样的。开始翻看`iscroll`的源码\u003Chttps:\u002F\u002Fgithub.com\u002Fcubiq\u002Fiscroll\u002Fblob\u002Fmaster\u002Fbuild\u002Fiscroll.js>，发现它的基本逻辑：\n\n构造函数`IScroll()`从第317行开始，首先处理了选项的合并。\n\n第331行有三个关键的选项：\n\n```javascript\ndisablePointer : !utils.hasPointer,\ndisableTouch : utils.hasPointer || !utils.hasTouch,\ndisableMouse : utils.hasPointer || utils.hasTouch,\n```\n\n于是，往回翻，找到`utils`中这几个东东的定义：\n\n```javascript\nhasTouch: 'ontouchstart' in window,\nhasPointer: !!(window.PointerEvent || window.MSPointerEvent), \u002F\u002F IE10 is prefixed\n```\n\n因此`hasTouch`和`hasPointer`的值不同会导致上述选项`disableTouch`和`disableMouse`的不同。再接下来就简单了，将这两个值分别打印出来，很快就能发现，正是旧版企业微信的`hasTouch`判断失误，导致了后续`disableMouse`为`true`，导致鼠标`mousedown`\u002F`mouseup`事件相关处理函数没有被调用。\n\n值得注意的是，没有问题的浏览器，`disableMouse`也为`true`。这里经过调试跟踪，发现这部分浏览器也没有走鼠标事件`mousedown`\u002F`mouseup`，而是走了`pointerdown`\u002F`pointerup`事件。于是caniuse了一下，发现`Pointer`事件从Chrome 55开始支持的，而出问题的企业微信的webview使用的是Chrome 49。因此这个最后的保险，`Pointer`事件也失效了。\n\n![Pointer事件兼容性](\u002Fassets\u002Fweb\u002F2018\u002Fa-bug-in-wechat-work\u002F2.png)\n\n### 修复\n\n超级简单，只需要在调用的时候传入`disableMouse:false`即可。\n\n```javascript\nthis.scroll = new IScroll(this.$refs.wrapper, {\n    disableMouse: false,\n    click: true,\n    tap: true,\n    wheel: true,\n    mouseWheel: true,\n})\n```\n\n### 复盘\n\n问题解决了，自然要回想一下，是什么导致了这个问题，我还能做点什么。\n\n导致问题的直接原因是：企业微信的webview有一个bug，明明不支持触控，但是在`'ontouchstart' in window`时却返回了`true`。又因为新版本的企业微信已经没有了这个问题，所以要反馈给企业微信也是一件无意义的事情了。\n\n另一个有一些关系的原因是：引入了一些没有把握的第三方库。事实上我们对于第三方库的引入一直非常谨慎，但这个系统因为早期并不是一个正式的项目，因此有些随意了。\n\n再接下来就是记录一下这整个过程，整理成本文，虽然并不知道对其他人是否有一些思路上的借鉴意义。\n\n值得一提的是，在修复的过程中，发现`iscroll`的源码早已不是我很多年前看的时候的样子了，整个模块划分非常清晰，所以代码读起来很容易。接下来有空的时候我应该会再仔细读一下它的代码，关于事件的检测、分发还是做得挺有意思的。\n\n修复的过程让我想到了知乎上看过的一个问题：我去修电脑，别人只飞了一根线，却要了我100元，合理吗？下面有个回答：线值0.5元，知道怎么飞这根线值99.5元。\n\n对工程师来说也一样，修复的过程只有一行代码，但找到这个方案的过程却并不容易。比如我，因为这个问题的出现，半夜11点半才下班。\n",null,0,"2026-08-28 04:37:17","published","\u003Ch2>背景\u003C\u002Fh2>\n\u003Cp>咱们在企业微信上开发了一个简单的选课系统，用于让公司内的同学在企业微信上进行选课。昨天下午HR的同学很开心地推送了8月份的课程列表，让大家报名。然后就炸锅了：企业微信中点击课程没反应，报不了名！\u003C\u002Fp>\n\u003Cp>收到消息第一时间感觉确认了一下问题，发现Mac下使用完全没有任何问题。于是打听了一下，出问题的都是Windows电脑的企业微信。直接使用Chrome浏览器访问或者在手机企业微信上访问都是没有问题的。\u003C\u002Fp>\n\u003Cp>那就简单啦，赶紧引导大家在浏览器或者手机企业微信上访问。问题解决！顺便感慨一下：狡兔三窟真重要啊，还好我们及早打通了OA登录，在各种环境下都可以登录使用。\u003C\u002Fp>\n\u003Cp>当然，夜深人静的时候，作为一名靠谱的前端工程师，还是要老老实实查问题的。于是诞生此文，流水账，记录bug排查的全程并揭秘原因。\u003C\u002Fp>\n\u003Ch2>TLDR\u003C\u002Fh2>\n\u003Cblockquote>\n\u003Cp>Too Long, Didn't Read\u003C\u002Fp>\n\u003Cp>太长了，不想读\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>好的，说重点：\u003C\u002Fp>\n\u003Col>\n\u003Cli>Windows企业微信某个版本的webview有bug，导致\u003Ccode>'ontouchstart' in windows\u003C\u002Fcode>为true，事实上它并不支持触控事件。\u003C\u002Fli>\n\u003Cli>这会导致第三方库\u003Ccode>iscroll\u003C\u002Fcode>产生误判，认为这是一个触控设备，从而错误地将\u003Ccode>disableMouse\u003C\u002Fcode>设为\u003Ccode>true\u003C\u002Fcode>，进而忽略\u003Ccode>mousedown\u002Fmouseup\u003C\u002Fcode>事件，导致内部不向外发出\u003Ccode>click\u003C\u002Fcode>事件。\u003C\u002Fli>\n\u003Cli>最终导致外层绑定的\u003Ccode>click\u003C\u002Fcode>事件处理函数不触发，点击无效。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2>排查过程\u003C\u002Fh2>\n\u003Ch3>重现\u003C\u002Fh3>\n\u003Cp>首先，想办法重现。\u003C\u002Fp>\n\u003Cp>打开虚拟机，下载企业微信，登录、开应用，一顿操作猛如虎，然后……没发现问题。\u003C\u002Fp>\n\u003Cp>重现失败。\u003C\u002Fp>\n\u003Cp>接下来找反馈方。您的系统是什么版本？您的企业微信是什么版本？您是什么网络？得知是旧版企业微信。于是赶紧让组里小伙伴确认一下，是不是有问题的升级一下就没有问题了。\u003C\u002Fp>\n\u003Cp>是。\u003C\u002Fp>\n\u003Cp>锁定问题范围：旧版本Windows企业微信下页面出现点击无效。\u003C\u002Fp>\n\u003Cp>虚拟机下载了一个旧版本企业微信，覆盖安装，再次登录、开应用，一顿操作猛如虎，然后果然看到了问题所在。\u003C\u002Fp>\n\u003Cp>接下来，想办法在开发环境重现问题。开启本机环境，配置hosts访问本机，问题依旧，重现成功。\u003C\u002Fp>\n\u003Ch3>确认问题\u003C\u002Fh3>\n\u003Cp>再接下来，确认问题来源。\u003C\u002Fp>\n\u003Cp>因为企业微信没有调试工具或者调试接口，只能先引入\u003Ccode>vconsole\u003C\u002Fcode>模块，确保日志能被看到。\u003C\u002Fp>\n\u003Cpre>\u003Ccode>npm install vconsole\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cpre>\u003Ccode>var VConsole = require('vconsole');\nnew VConsole();\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cimg src=\"https:\u002F\u002Fassets.toobug.net\u002Fassets\u002Fweb\u002F2018\u002Fa-bug-in-wechat-work\u002F1.png\" alt=\"VConsole界面\">\u003C\u002Fp>\n\u003Cp>展示一下项目的Vue代码：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>&lt;template&gt;\n    &lt;div class=&quot;content&quot;&gt;\n        &lt;div class=&quot;listWrapper&quot; ref=&quot;wrapper&quot;&gt;\n            &lt;ul&gt;\n                &lt;li v-for=&quot;course in courseList&quot; @click=&quot;chooseCourse&quot;&gt;{{course.name}}&lt;\u002Fli&gt;\n            &lt;\u002Ful&gt;\n        &lt;\u002Fdiv&gt;\n    &lt;\u002Fdiv&gt;\n&lt;\u002Ftemplate&gt;\n\n&lt;script&gt;\nimport IScroll from 'iscroll';\n\nexport default {\n    created(){\n        this.scroll = new IScroll(this.$refs.wrapper, {\n            click: true,\n            tap: true,\n            wheel: true,\n            mouseWheel: true,\n        })\n    },\n    methods:{\n        chooseCourse(){\n\n        }\n    }\n}\n&lt;\u002Fscript&gt;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>核心逻辑是，\u003Ccode>.listWrapper\u003C\u002Fcode>部分会被\u003Ccode>iscroll\u003C\u002Fcode>模块处理，以便能够方便地使用无限滚动、滚动加载等功能。\u003C\u002Fp>\n\u003Cp>于是将\u003Ccode>@click\u003C\u002Fcode>分别绑定到\u003Ccode>.listWrapper\u003C\u002Fcode>内部和外部，发现外部的点击可以响应，内部的点击无法响应。\u003C\u002Fp>\n\u003Cp>问题被进一步缩小范围：\u003Ccode>iscroll\u003C\u002Fcode>导致的元素点击事件不触发。\u003C\u002Fp>\n\u003Cp>接下来就想到将代码中初始化过的\u003Ccode>iscroll\u003C\u002Fcode>实例对象的\u003Ccode>options\u003C\u002Fcode>属性打印出来进行对比，看看是否初始化的时候环境检测结果有些不一样。果然发现了一点区别：\u003C\u002Fp>\n\u003Cp>没有问题的浏览器上：\u003C\u002Fp>\n\u003Cpre class=\"shiki\" style=\"background-color:#121212;color:#dbd7caee\" tabindex=\"0\">\u003Ccode>disableMouse: true\ndisablePointer: false\ndisableTouch: true\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>有问题的浏览器上：\u003C\u002Fp>\n\u003Cpre class=\"shiki\" style=\"background-color:#121212;color:#dbd7caee\" tabindex=\"0\">\u003Ccode>disableMouse: true\ndisablePointer: true\ndisableTouch: false\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>可以看到\u003Ccode>disablePointer\u003C\u002Fcode>和\u003Ccode>disableTouch\u003C\u002Fcode>的值是不一样的。开始翻看\u003Ccode>iscroll\u003C\u002Fcode>的源码\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fcubiq\u002Fiscroll\u002Fblob\u002Fmaster\u002Fbuild\u002Fiscroll.js\">https:\u002F\u002Fgithub.com\u002Fcubiq\u002Fiscroll\u002Fblob\u002Fmaster\u002Fbuild\u002Fiscroll.js\u003C\u002Fa>，发现它的基本逻辑：\u003C\u002Fp>\n\u003Cp>构造函数\u003Ccode>IScroll()\u003C\u002Fcode>从第317行开始，首先处理了选项的合并。\u003C\u002Fp>\n\u003Cp>第331行有三个关键的选项：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>disablePointer : !utils.hasPointer,\ndisableTouch : utils.hasPointer || !utils.hasTouch,\ndisableMouse : utils.hasPointer || utils.hasTouch,\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>于是，往回翻，找到\u003Ccode>utils\u003C\u002Fcode>中这几个东东的定义：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>hasTouch: 'ontouchstart' in window,\nhasPointer: !!(window.PointerEvent || window.MSPointerEvent), \u002F\u002F IE10 is prefixed\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>因此\u003Ccode>hasTouch\u003C\u002Fcode>和\u003Ccode>hasPointer\u003C\u002Fcode>的值不同会导致上述选项\u003Ccode>disableTouch\u003C\u002Fcode>和\u003Ccode>disableMouse\u003C\u002Fcode>的不同。再接下来就简单了，将这两个值分别打印出来，很快就能发现，正是旧版企业微信的\u003Ccode>hasTouch\u003C\u002Fcode>判断失误，导致了后续\u003Ccode>disableMouse\u003C\u002Fcode>为\u003Ccode>true\u003C\u002Fcode>，导致鼠标\u003Ccode>mousedown\u003C\u002Fcode>\u002F\u003Ccode>mouseup\u003C\u002Fcode>事件相关处理函数没有被调用。\u003C\u002Fp>\n\u003Cp>值得注意的是，没有问题的浏览器，\u003Ccode>disableMouse\u003C\u002Fcode>也为\u003Ccode>true\u003C\u002Fcode>。这里经过调试跟踪，发现这部分浏览器也没有走鼠标事件\u003Ccode>mousedown\u003C\u002Fcode>\u002F\u003Ccode>mouseup\u003C\u002Fcode>，而是走了\u003Ccode>pointerdown\u003C\u002Fcode>\u002F\u003Ccode>pointerup\u003C\u002Fcode>事件。于是caniuse了一下，发现\u003Ccode>Pointer\u003C\u002Fcode>事件从Chrome 55开始支持的，而出问题的企业微信的webview使用的是Chrome 49。因此这个最后的保险，\u003Ccode>Pointer\u003C\u002Fcode>事件也失效了。\u003C\u002Fp>\n\u003Cp>\u003Cimg src=\"https:\u002F\u002Fassets.toobug.net\u002Fassets\u002Fweb\u002F2018\u002Fa-bug-in-wechat-work\u002F2.png\" alt=\"Pointer事件兼容性\">\u003C\u002Fp>\n\u003Ch3>修复\u003C\u002Fh3>\n\u003Cp>超级简单，只需要在调用的时候传入\u003Ccode>disableMouse:false\u003C\u002Fcode>即可。\u003C\u002Fp>\n\u003Cpre>\u003Ccode>this.scroll = new IScroll(this.$refs.wrapper, {\n    disableMouse: false,\n    click: true,\n    tap: true,\n    wheel: true,\n    mouseWheel: true,\n})\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>复盘\u003C\u002Fh3>\n\u003Cp>问题解决了，自然要回想一下，是什么导致了这个问题，我还能做点什么。\u003C\u002Fp>\n\u003Cp>导致问题的直接原因是：企业微信的webview有一个bug，明明不支持触控，但是在\u003Ccode>'ontouchstart' in window\u003C\u002Fcode>时却返回了\u003Ccode>true\u003C\u002Fcode>。又因为新版本的企业微信已经没有了这个问题，所以要反馈给企业微信也是一件无意义的事情了。\u003C\u002Fp>\n\u003Cp>另一个有一些关系的原因是：引入了一些没有把握的第三方库。事实上我们对于第三方库的引入一直非常谨慎，但这个系统因为早期并不是一个正式的项目，因此有些随意了。\u003C\u002Fp>\n\u003Cp>再接下来就是记录一下这整个过程，整理成本文，虽然并不知道对其他人是否有一些思路上的借鉴意义。\u003C\u002Fp>\n\u003Cp>值得一提的是，在修复的过程中，发现\u003Ccode>iscroll\u003C\u002Fcode>的源码早已不是我很多年前看的时候的样子了，整个模块划分非常清晰，所以代码读起来很容易。接下来有空的时候我应该会再仔细读一下它的代码，关于事件的检测、分发还是做得挺有意思的。\u003C\u002Fp>\n\u003Cp>修复的过程让我想到了知乎上看过的一个问题：我去修电脑，别人只飞了一根线，却要了我100元，合理吗？下面有个回答：线值0.5元，知道怎么飞这根线值99.5元。\u003C\u002Fp>\n\u003Cp>对工程师来说也一样，修复的过程只有一行代码，但找到这个方案的过程却并不容易。比如我，因为这个问题的出现，半夜11点半才下班。\u003C\u002Fp>\n","\u003Ch2>背景\u003C\u002Fh2>\n\u003Cp>咱们在企业微信上开发了一个简单的选课系统，用于让公司内的同学在企业微信上进行选课。昨天下午HR的同学很开心地推送了8月份的课程列表，让大家报名。然后就炸锅了：企业微信中点击课程没反应，报不了名！\u003C\u002Fp>\n\u003Cp>收到消息第一时间感觉确认了一下问题，发现Mac下使用完全没有任何问题。于是打听了一下，出问题的都是Windows电脑的企业微信。直接使用Chrome浏览器访问或者在手机企业微信上访问都是没有问题的。\u003C\u002Fp>\n\u003Cp>那就简单啦，赶紧引导大家在浏览器或者手机企业微信上访问。问题解决！顺便感慨一下：狡兔三窟真重要啊，还好我们及早打通了OA登录，在各种环境下都可以登录使用。\u003C\u002Fp>\n\u003Cp>当然，夜深人静的时候，作为一名靠谱的前端工程师，还是要老老实实查问题的。于是诞生此文，流水账，记录bug排查的全程并揭秘原因。\u003C\u002Fp>\n","https:\u002F\u002Fassets.toobug.net\u002Fassets\u002Fweb\u002F2018\u002Fa-bug-in-wechat-work\u002F1.png",{"total":16,"totalRoots":16,"comments":23,"pv":16},[]]