今天你上班了吗?来聊聊一个隐蔽了 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 里,据说是为了检查内 ...
随机推荐
- centos安装pip,zipimport.ZipImportError报错
安装pip下载pip安装包wget https://bootstrap.pypa.io/get-pip.py 执行以下报错 python get-pip.py ##报错内容(zipimport.Zip ...
- 20191121-4 Final发布用户使用报告
此作业要求参见:https://edu.cnblogs.com/campus/nenu/2019fall/homework/10064 队名:组长 组长:杨天宇 组员:罗杨美慧,王歆瑶,魏鑫,梅小雨 ...
- 【题解】HDU4689 Derangement(有技巧的计数DP)
[题解]HDU4689 Derangement(有技巧的计数DP) 传送门 呵呵没告诉我多测组数,然后\(n\le 20,7000\mathrm{ms}\)我写了个状压上去T了 题目大意: 要你求错排 ...
- 信息管理java
代码: package 信息管理;//信1805-1 20183763 凌云 public class ScoreInformation { private String stunumber = &q ...
- appium获取toast和操作webview实例
从testerhome上看到很多测试同学分享了toast的获取方式,其中大部分是java版本的,但也有个python版本的:Appium1.7.2 android toast 消息测试 Appium获 ...
- Jmeter-Ant 生成测试报告配置步骤
1.配置java环境变量(不会的可以自行百度) 2.安装jmeter 3.安装ant,配置ant环境变量 4.将JMeter所在目录下extras子目录里的ant-JMeter-1.1.1.jar复制 ...
- 洛谷P1029 最大公约数和最小公倍数问题 题解
题目链接:https://www.luogu.com.cn/problem/P1029 题目描述 输入 \(2\) 个正整数 \(x_0,y_0(2 \le x_0 \lt 100000,2 \le ...
- C Primer Plus(二)
重读C Primer Plus ,查漏补缺 重读C Primer Plus,记录遗漏的.未掌握的.不清楚的知识点 分支和跳转 1.ctype.h头文件里包含了一些列用于字符判断的函数,包括判断数字.大 ...
- .net core3.1项目在centos7.6上部署经验
0x00环境搭建 1)使用PuTTY远程登录你的centos 2)yum -y update 更新系统 3)安装宝塔面板: yum install -y wget && wget -O ...
- 一个.NET程序员 "2019" 跳槽3次的悲惨故事
2019年是值得深思的一年,在找工作上没有那么用心,导致碌碌无为,在这里我建议大家找工作的时候不要太着急...要不然会被逼疯的,一定不能被“工作”挑,一定要做到挑"工作".:那我就 ...