今天你上班了吗?来聊聊一个隐蔽了 5 年的BUG!
前言
今天,我们要揭晓一个 FineUI 隐藏最深的一个BUG,这个问题从 2014-07-30 发布 FineUIPro v1.0.0 就一直存在,直到最新于 2020-01-10 发布的 v6.1.1版本依然存在,之所以一直没有被提上台面,是因为这个BUG的重现场景比较少,特别是现在网络速度越来越快的情况下。
提出问题
这个问题分别由中山市的一个企业客户和美国的一个企业客户独立发现,我们先来看下中山市客户的提问:
发现一个小问题,就是当我打开一个有表格的页面时,表格还没加载完成,我切换别的页面,当有表格的页面加载完成后,我再切回去,表格就布局失败了,挤在一起。
这位客户还特意做了一个GIF图片,演示遇到的问题:

再来看下美国一个企业客户的提问:
I wanted to also ask you about an issue with timer. The problem appears when you open a page with a timer which reloads a grid every 10 seconds, and you navigate to another page. If navigating to another page and meanwhile the timer reloads the grid behind the scenes, when going back to the initial page (the one with the timer), the page is not reloaded entirely (the grid is missing).
这位客户的提问也非常专业,甚至做了一个可重现问题的示例,感兴趣的可以自己尝试下:

重现问题
由于这个美国客户给出了可重现问题的示例,我们就来仔细分析一下,这个示例有 3 个文件:

把这些页面放到官网示例源代码的 test 目录下,打开 /test/grid_timer.aspx 页面:

然后,点击 Add new tab to parent page 按钮,会新开一个选项卡,页面效果如下:

在这个页面停留 10 秒,然后返回第一个页面,此时的页面效果如下所示:

很明显,此时表格的宽度不对了,因为这个页面处于隐藏状态时更新了表格数据,因此这个问题可能是由于页面隐藏时宽度计算不对造成的。
在着手分析问题之前,先对照上面的页面效果看下 grid_timer.aspx 页面的代码逻辑:
<f:Grid ID="Grid1" IsFluid="true" CssClass="blockpanel" ShowBorder="true" ShowHeader="true" Title="Grid"
runat="server" DataKeyNames="Id,Name" DataIDField="Id" EnableCheckBoxSelect="false">
<Columns>
<f:RenderField Width="140px" DataField="Id" ColumnID="Id" HeaderText="Id" SortField="Id" />
<f:RenderField Width="140px" DataField="Name" ColumnID="Name" HeaderText="Name" ExpandUnusedSpace="true" />
<f:RenderField Width="80px" DataField="EntranceYear" ColumnID="EntranceYear" HeaderText="Entrance year" />
<f:CheckBoxField Width="80px" RenderAsStaticField="true" DataField="AtSchool" ColumnID="AtSchool" HeaderText="At school" />
</Columns>
</f:Grid> <f:Timer ID="timer1" Interval="10" Enabled="true" OnTick="Timer1_Tick" EnableAjaxLoading="false" runat="server" /> <f:Button runat="server" Text="Add new tab to parent page" OnClientClick="openHelloPage();" />
其中,openHelloPage 是一个自定义JS函数,用来添加一个新的选项卡(addExampleTab 是定义在外部框架页面的一个JS函数,用来新增选项卡):
var basePath = '<%= ResolveUrl("~/") %>';
function openHelloPage() {
parent.addExampleTab({
id: 'hello_fineui_tab',
iframeUrl: basePath + 'test/grid_timer_hello.aspx',
title: 'New Page',
refreshWhenExist: true
});
}
这个页面有一个 Timer 控件,会每隔 10 秒回发页面,现在看下后台的代码逻辑:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
BindGrid();
}
else
{
if (Request.Form["__EVENTARGUMENT"] == "MyTimer")
{
BindGrid();
}
} }
private void BindGrid()
{
Grid1.DataUrl = "./grid_timer_handler.ashx";
Grid1.DataBind();
} protected void Timer1_Tick(object sender, EventArgs e)
{
BindGrid();
}
可以看出,Timer控件会每隔 10 秒返回后台,并重新对表格进行数据绑定。
分析问题
经过测试,我们发现 iframe 中的页面处于隐藏状态时,此时获取的页面宽度为0,所以表格重新布局时高度也为零。
为了解决这个问题,可以自定义timer,在页面处于隐藏状态时不更新表格数据,代码如下:
1. 自定义JS脚本:
window.setInterval(function () {
// Check if the current page is visible
if ($('body').width()) {
__doPostBack('', 'MyTimer');
}
}, 10000);
2. 后台接受自定义回发:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
BindGrid();
}
else
{
if (Request.Form["__EVENTARGUMENT"] == "MyTimer")
{
BindGrid();
}
} }
由于这个页面逻辑其实还是蛮特殊的,所以我们通过上述逻辑是可以解决这个问题,我们也及时答复了客户。
显然,这个答复并没有让客户满意,随后我们收到如下反馈:
if there would be a solution to redraw the grid if we let the timer make the updates behind the scenes? Maybe if there is an event on the main TabStrip for changing the active tab, and if the active tab is the one with the timer, then set the width for the grid to the initial value?
其实用户的诉求也很正常:希望表格处于隐藏状态下也能得到更新,而在切换选项卡时重新设置表格的宽度。
这样可行吗?
显然是不行的,我们不可能记录所有控件的初始宽度,也不可能对某一两个控件进行特殊处理。
那该怎么办,我们也陷入了深思......
解决问题
其实,明眼人都能看明白,最直接的解决办法就是:切换选项卡时重新对其中的某些控件进行布局。
问题的关键,如果知道哪些控件需要布局呢?
哪些控件在页面处于隐藏状态时进行了无效的布局操作呢?显然这要从 FineUI 控件层面给出通用的解决办法。
经过一番尝试,我们给出了如下解决办法:
1. 在控件基类 F.Component 的布局操作中,拦截处于隐藏 IFrame 中控件的布局操作:
doLayout: function () {
if(F.util.insideIFrame() && !$('body').is(':visible')) {
F.cmpsLayoutInHiddenIFrame = F.cmpsLayoutInHiddenIFrame || [];
F.cmpsLayoutInHiddenIFrame.push(cmp.id);
return;
}
// ...
}
其中 F.cmpsLayoutInHiddenIFrame 用来记录隐藏状态下进行布局的控件ID列表,以便在选项卡切换时重新布局。
2. 容器基类中定义一个函数,用来执行重新布局操作(redoLayoutInHiddenIFrame会遍历F.cmpsLayoutInHiddenIFrame并执行布局操作):
checkIFrameHiddenLayout: function() {
var me = this;
// 内部的iframe页面已经加载完毕
if(me.iframe && me.iframeLoaded) {
var iframeWnd = me.getIFrameWindow();
// 如果目标页面不可访问(跨域限制,或者目标页面没有引入FineUIPro),则不作处理
if(F.util.canIFrameWindowAccessed(iframeWnd)) {
iframeWnd.F.redoLayoutInHiddenIFrame();
}
}
}
3. 在激活选项卡时,检查是否有需要布局的控件:
setActiveTab: function(tab) {
if (tab.iframe) {
if(!tab.iframeLoaded) {
tab.setIFrameUrl(tab.iframeUrl);
} else {
tab.checkIFrameHiddenLayout();
}
}
// ...
}
注意:上述代码都是 FineUI 内部使用的,这里为了方便理解进行了改写和简化,并非实际使用的原始代码。
经过这个改造,上述客户提出的两个问题都能完美解决,请看下面两个对比图。
老版本:

新版本:

老版本:

新版本:

这样就搞定了,慢着.....
重新思考 & 新的解决方案
虽然上面的思路非常直观,代码实现也并不复杂,但是总有点打补丁的感觉,生怕哪天这个新打的补丁再破了。
我也一直在思考这个问题,为啥隐藏的IFrame页面,里面元素的宽度都计算不对?
有没有让隐藏状态的 IFrame 行为表现的就像一直显示的那样?这样,我们不需要这一堆补丁代码了,也就少了一个可能出错的点。
答案还真有!
一般我们控制页面上元素的显示隐藏有 3 种方法:
1. display: none/block:最常用显示隐藏元素的方法
2. visibility: hidden/visible:隐藏的元素还会占据原来的位置,只不过不可见而已,不常用。
3. position: absolute; top: -10000px; 通过将元素绝对定位,并远远的浮动到可见区域的外面,来实现元素的不可见,不常用。
而 FineUI 中一直用的就是第一种方法,也是最常用的做法:

而 display: none; 会导致其中的 IFrame 页面的宽度计算不对。如果我们采用第三种方式,问题是不是就迎刃而解了呢?
答案是肯定的。
因为将元素浮动到可视区域外面,虽然我们看不到这个元素,但是本质上这个元素的各种行为应该和可见元素一模一样!!
下个版本,我们会采用这个新的实现方式,来解决问题:

One more thing...
新的解决办法更加简洁,不仅减少了一堆补丁代码,而且还带来一个意想不到的好处,那就是切换选项卡时,IFrame页面的滚动条能保持位置了!!
老版本:

新版本:

道理也很简单,新的隐藏方式只是让元素距离可见区域远一点,其实元素还是显示的,所以之前的状态都能保持。
是不是很酷!
官网示例已更新,现在就可以访问了:
FineUIPro:https://pro.fineui.com/
FineUIMvc:https://mvc.fineui.com/
FineUICore:https://core.fineui.com/
FineUICore (Razor Pages & Tag Helpers):https://pages.fineui.com/
今天你上班了吗?来聊聊一个隐蔽了 5 年的BUG!的更多相关文章
- 怎样才能提交一个让开发人员拍手叫好的bug单
怎样才能提交一个让开发人员拍手叫好的bug单 软件测试人员写得最多的文档就是测试用例和BUG,现在测试用例和BUG都没有标准的模板,每个公司使用的缺陷管理工具都有可能不一样,如果你换了一家公司就有可能 ...
- Java中,一个存在十几年的bug...
今天,分享一个JDK中令人惊讶的BUG,这个BUG的神奇之处在于,复现它的用例太简单了,人肉眼就能回答的问题,JDK中却存在了十几年.经过测试,我们发现从JDK8到14都存在这个问题. 大家可以在自己 ...
- 面试题(造火箭必备技能):请举例一个最有成就感的性能bug
当前,绝大部分招聘都有性能要求或者把其作为加分项(会性能优先),哪怕你不是面试的性能,面试的时候可能会问性能,所以大家才会有"面试造火箭,进去拧螺丝"的共鸣.至于企业为什么重视性能 ...
- 记Windows的一个存在了十多年的bug
bug Windows有一个bug,持续了十多年,从Windows Visita开始(2007年),一直存在,直到Windows11(2021年)才修复(其实也不叫修复,后面我再具体说),而Windo ...
- Java中关于 BigDecimal 的一个导致double精度损失的"bug"
背景 在博客 恶心的0.5四舍五入问题 一文中看到一个关于 0.5 不能正确的四舍五入的问题.主要说的是 double 转换到 BigDecimal 后,进行四舍五入得不到正确的结果: public ...
- Oracle 11.2.0.1的又一个隐藏在ORA-03113后的bug: 通信通道的文件结尾
近期又一个项目反馈ORA-03113错误: 通信通道的文件结尾.(jdbc程序报出的错误是:无法从套接字读取更多的数据) 发送之前处理过类似问题的解决方法(http://www.cnblogs.com ...
- 记一个SwipeMenuListView侧滑删除错乱的Bug
做侧滑删除网上有很多方案,比如重写Listview实现滑动的监听,今天说下一个SwipeListView,这个是之前一个朋友在网上开源的一个封装组件,能够适用于多种情况,项目地址:https://gi ...
- 一个nginx 回源限速的bug处理过程记录
一个生产环境,nginx占用cpu很高. top - :: up day, :, users, load average: 13.26, 13.20, 13.20 Tasks: total, runn ...
- 一个可遇不可求的 bug 全局变量初始化顺序问题 哈哈
这是今天下午帮同事查的一个客户端 C++ 的 bug,前人留下的谜之代码.. 具体情况是,客户端实现了有一个简单的内存池,每次申请内存的时候会把新申请到的内存信息存到一个 map 里,据说是为了检查内 ...
随机推荐
- 20191017-8 alpha week 2/2 Scrum立会报告+燃尽图 07
此作业要求参见https://edu.cnblogs.com/campus/nenu/2019fall/homework/9804 小组名称:“组长”组 组长:杨天宇 组员:魏新,罗杨美慧,王歆瑶,徐 ...
- 信息管理java
代码: package 信息管理;//信1805-1 20183763 凌云 public class ScoreInformation { private String stunumber = &q ...
- Java工程师阅读源码的一些见解
一.为何阅读源码 就是说,通过阅读源码能给你带来什么好处. 学习如何从需求-设计-实现,开阔你的思维,提升你的架构设计能力: 帮助更好地理解原理和架构设计: 帮助更快地定位线上问题BUG 可以根据自己 ...
- 用Eclipse和Tomcat搭建一个本地服务器
服务器软件环境 Eclipse oxygen Tomcat 9.0 SQL Sever 2014 参考资料 https://blog.csdn.net/qq_21154101/article/deta ...
- 端口扫描器--利用python的nmap模块
安装nmap模块挺麻烦的,搞了半天 不仅要安装pip install nmap 还要sudo apt install nmap 给出代码,没有设多线程,有点慢,注意端口的类型转换,搞了很久 #!/us ...
- 原型,原型链,给予原型和class的继承
学习react的时候遇到了class方式的继承语法,原理和代码的解释很详细,值得一读. 原型每个函数(构造函数)都有一个 prototype 属性,指向该函数(构造函数)的原型对象.实例没有 prot ...
- es lucene搜索及聚合流程源码分析
本文以TermQuery,GlobalOrdinalsStringTermsAggregator为例,通过代码,分析es,lucene搜索及聚合流程.1:协调节点收到请求后,将search任务发到相关 ...
- vue根据选择的月份动态展示当前月份的每一天并展示每一天所对应的星期几
我们在开发过程中所遇到的所有的奇奇怪怪的交互美其名曰用(奇)户(葩)体(需)验(求),而产品所谓的良好的交互效果,在我等开发人员眼里不值一提.不屑一顾.讨厌至极! 对于这样的需求,我通常都是: 但胳膊 ...
- nginx负载均衡动态自动更新(微博开源模块nginx-upsync-module使用)
这几天项目有个需求:负载要求能根据节点健康状态动态的增减.nginx自带的upstram已经很强大,而且基于Nginx Upstream配置动态更新已经有很多开源方案,大多数都是基于生成配置文件后进行 ...
- 关于java php go 中AES加解密秘钥长度问题
今天心血来朝,想用go把php中的一个小功能重写一下,但在解密aes加密的数据时碰到了个坑! php的mcrypt拓展(貌似php7.1版本以上不支持了)提供了aes的加解密: 而且php aes 的 ...