跟进一个 jserror 问题带来的思考

2021-08-03 14:56:09 浏览数 (1)

作者:徐杰

wns-cgi的业务使用同学反馈了一个问题:在安卓手Q的空间里,打开小游戏,然后进入空间宠物,聊天窗口,发送消息。这里会注册一个jsbridge回调,发送消息后,回调后,小游戏页面会报一个js的error,比较奇怪,报错截图如下

报错信息倒是很明确,是因为方法未定义导致的异常;触发条件是因为宠物聊天用的是wns-cgi,所以最初怀疑是否和wns-cgi有关,就由我和xixinhuang一起在跟进这个问题;庆幸的是问题必现,这样我们就可以复现和构造各种场景;下面简单总结下定位的过程:A打开了B页面,B页面用了wns-cgi进行聊天(打开了输入法控制面板),B页面没出现异常,但是A页面的jserror报错了;

(1)根据报错信息最初是想确认为什么A页面会收到这个回调?明明是在B页面的操作啊?于是赶紧拉上了客户端同学和mqq.js的开发同学进行定位,可是没有什么头绪和进展,客户端同学觉得没问题啊,调用wns-cgi,客户端正常的执行了一段回调;wns-cgi的请求对象如下:

代码语言:javascript复制
{"method":"post","url":"http://h5.qzone.qq.com/webapp/json/pet_chat/Chat?uin=2577199125&g_tk=830846970&t=0.36583463430175467","body":{"qua":"V1_AND_SQ_7.2.5_0_RDM_B","iEventID":2,"stCommInfo.lLoginUin":2577199125,"stCommInfo.lHostUin":2577199125,"stCommInfo.stGeoInfo.lLon":113933731,"stCommInfo.stGeoInfo.lLat":22540520,"stCommInfo.stGeoInfo.lAlt":0,"stCommInfo.strQua":"V1_AND_SQ_7.2.5_0_RDM_B","strText":"好红红火火恍恍惚惚","mapExtInfo.source":"h5","mapExtInfo.gpstype":"1","mapExtInfo.devicetype":"1","mapExtInfo.device_info":"","mapExtInfo.userPetId":10116,"mapExtInfo.hostPetId":10116,"mapExtInfo.first_sentence_id":"","strCookie":""},"timeout":0,"dataNeedBase64":false,"callback":"__MQQ_CALLBACK_AUTO_35"} 客户端

客户端处理后,loadurl调用的js代码如下:

代码语言:javascript复制
(window.mqq && mqq.version > 20140616001 && mqq.execGlobalCallback||function(cb) {window[cb] && window[cb].apply(window, [].slice.call(arguments, 1));}).apply(window, ["__MQQ_CALLBACK_AUTO_35", {"status":4,"url":"http://h5.qzone.qq.com/webapp/json/pet_chat/Chat?uin=2577199125&g_tk=830846970&t=0.36583463430175467","code":0,"dataIsBase64":false,"data":"{"ret":0,"msg":"","data":{"stActionSet":{"iID":0,"vecActionInfo":[{"vecActionNode":[{"vecName":["f_speak"],"iLoopCount":1,"fSpeed":1}],"stContentInfo":{"iContentType":1,"strText":"小宠的语文阔是数学老师教的哦~ 诗词歌赋样样精通!你想考我吗!","strUrl":"","vecChoice":[],"iStayTimeMs":-1,"stFile":{"iFileId":0,"strName":"","strUrl":"","strMd5":"","iSize":0,"iFileType":0,"iWidth":0,"iHeight":0},"stUgcVideo":{"strVideoId":"","iAppId":0,"iVideoWidth":0,"iVideoHeight":0,"iOperateMask":0,"iVideoState":0,"uiDurationTimeMs":0,"strVideoUrl":"","strCoverImgUrl":"","iCoverWidth":0,"iCoverHeight":0,"strOrgKey":"","strCurKey":"","strCid":"","strUgcKey":"","lUin":0,"strNick":""},"vecPetQuickCommentPic":[],"stRelationChain":{"iTotal":0,"vecFriInfo":[]},"stFmInfo":{"strShowId":"","strShowName":"","strAudioUrl":"","strCoverUrl":"","strOwner":"","iShowDuration":0,"strShowUrl":""}},"strTrace":"","iDelayMs":0}]},"mapExtInfo":{},"stValidator":{"lUin":2577199125,"strModelKey":"mao_phase1_20172217"},"strCookie":"chat_session_id=0A9E20724B9A4AF28A0CECA0BE2FC8CF"}}","httpStatusCode":200,"header":{"x-powered-by":"TSW/Node.js","cache-control":"no-cache","connection":"keep-alive","keep-alive":"timeout=10","vary":"Origin, Accept","mod-map":"webapp_json","content-type":"application/json; charset=UTF-8","content-length":"1082","date":"Wed, 13 Sep 2017 07"}}]);

看起来调用wns-cgi没啥问题啊,问题重新回到了我们这边;

(2)我和xixin同学开始了错误的重新定位过程,既然报错是来自A页面,一定是和mqq的什么事件监听有关,之前我处理过mqq.debug.js文件,先定位到和这个错误有关的代码,是在invokeClientMethod中注册的,如下图:

那我们代理上这个mqq.js,然后在这个方法中加上console.trace()看看调用栈,会不会有帮助?

同时我们在A页面有涉及到mqq.invoke等方法的地方,能加上callback的,我们统一都加上测试代码,比如类似的代码,如果A页面有执行这个callback,我们可以从对应的ns和method入手查:

代码语言:javascript复制
var img = new Image();
img.src = 'https://h5.qzone.qq.com/proxy/domain/www.qq.com/xxx/xxx';

结果上面的img的请求根本没发出来,而且我们在invokeClientMethod中设置的错误信息,只有openUrl的日志,这个是合理的,因为A打开B页面,A调用的就是openUrl,我们感觉有些没辙了。。。客户端没问题,H5也没问题,问题在哪?明明有jserror报错,而且也很怀疑是和事件监听有关,可是我们H5能做的比较有限;

(3)峰回路转:

在和xixin重现错误的时候,碰到一个常见的联调问题,就是js缓存,经常需要重新清理缓存,而且也可能代理不到本地,给重现带来一定的困扰;比如A打开B,如果A加上了_proxy=1走到了wns-html,可能都没有js请求,这样我们设置的什么代理都没用,清理缓存也不一定行。。。很焦急的看着时间慢慢流逝。。。没办法,只能先把url地址修改下,去掉_proxy=1,在聊天消息框中打开对应的url,总算可以代理上了,可以看到正常的invokeClientMethod的日志信息,意外出现了,在AIO聊天窗口打开的页面,居然没有报错???为什么?目前来看已有的信息,就是为什么从好友动态进入,一定会报错,而从QQ聊天点击链接进去就不会报错?都是同样打开的webview,H5的代码没变,那我们唯一可以确定的就是webview有什么差别;

只能继续求助于客户端,客户端保罗大神pauloliu根据这个线索,对比了代码,结果如下:

代码语言:javascript复制
空间和AIO里面打开url,用的都是一个activity,但是确实aio里面打开的不会有问题。
AIO里面打开url的intent:
Bundle[{qqBrowserActivityCreateTime=1505354889492, fling_action_key=2, from_aio=1, param_force_internal_browser=false, friendUin=373922647, from_aio_opt=1, preAct=QQBrowserDelegationActivity, leftViewText=返回, aio_open_web=true, fling_code_key=199961721, fromAio=true, from_aio_time=43533567, uinType=0, articalChannelId=2, useDefBackText=true, startOpenPageTime=1505354885699, uin=2577199125, url=https://h5.qzone.qq.com/h5plus/home/index/alpha?_proxy=1&_wv=3&_nav_alpha=0&from=qqnavigation, preAct_time=1505354885699, key_isReadModeEnabled=true, needSkey=true, injectrecommend=true, curtype=0}]
空间的
Bundle[{qqBrowserActivityCreateTime=1505355030872, key_isFromQZone=true, fling_action_key=2, preAct=QQBrowserDelegationActivity, leftViewText=返回, fling_code_key=158123890, articalChannelId=5, startOpenPageTime=1505355027800, source_name=QQ空间, uin=2577199125, url=https://h5.qzone.qq.com/h5plus/home/index/alpha?_proxy=1&_wv=3&_nav_alpha=0&from=qqnavigation, fromQZone=true, preAct_time=1505355027800, key_isReadModeEnabled=true, needSkey=true, injectrecommend=true, insertPluginsArray=[Ljava.lang.String;@27f8d6b, isNeedAdvReport=false, post_data=null}]

我们都发现了下面多了一个insertPluginsArray,这个是干嘛的?insertPluginsArray代码如下:

在浏览器中插入了一个Qzone的命名空间,这个是啥?Qzone是个大插件,里面东西太多了,保罗发现干掉这个插件就OK了,那证明问题就出在这个Qzone插件上;找到源头我们也感觉有了一丝丝希望;在客户端同学努力下,问题根源找到了:

输入面板关闭的时候,会发出一个广播,而这个广播的接收者在webview初始化的时候就被创建了,所以每个web页面都可以收到。。。所以问题的原因我们来回归下:

我们从小游戏页面进入宠物页面,在宠物页面聊天的时候,会调用TopicComment这个方法,同时也会注册一个回调;关闭输入框会执行一下这个callback,方法名是上面的报错的哪个匿名方法,这个回调方法命名规则就是MQQ_CALLBACKAUTO SN(这个SN的值UUIDSeed自增生成),在A页面因为没有注册这个回调方法,但是在A页面却收到了这个广播,这个是浏览器层面的事件监听,但是没有对应的回调方法,所以报错了,不过不影响功能,只是影响了jserror的统计;客户端同学也已经修复了这个bug,在下个版本会兼容处理;

通过这次的问题定位,对我们未来碰到类似的问题进行解决的时候,提供了思路,就是客户端可能会在浏览器加载的时候,不时的插入一些包括js,事件等等,如果没有相应的代码进行辅助证明,可以从这个角度去定位,举一反三下 :)

0 人点赞