今天你上班了吗?来聊聊一个隐蔽了 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 里,据说是为了检查内 ...
随机推荐
- 洛谷$P$2286 宠物收养场 $[HNOI2004]$ $splay$
正解:$splay$ 解题报告: 传送门! $splay$板子,,,? 先考虑这题要实现些什么东西嘛$QwQ$ 其实只要实现一个东西?就查询数列中与给定数字相差最小的数,显然用$splay$查询前驱后 ...
- $Luogu2512/CH122/AcWing122$糖果传递 模拟
$Luogu$ $AcWing$ $Description$ 有$n$个小朋友坐成一圈,每人有$a_i$个糖果. 每人只能给左右两人传递糖果. 每人每次传递一个糖果代价为$1$. 求使所有人获得均等 ...
- TemplateMethodPattern(模板方法模式)-----Java/.Net
一个抽象类公开定义了执行它的方法的方式/模板.它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行.这种类型的设计模式属于行为型模式
- docker-管理数据
管理Docker中的数据 默认情况下,在容器内创建的所有文件都存储在可写容器层中.这意味着: 当该容器不再运行时,数据不会持久存在,如果另一个进程需要,则可能很难从容器中获取数据. 容器的可写层紧密耦 ...
- Go中的Package和Module分析
Package 所谓package(包)其实就是代码的一种组织管理方式,代码多了就需要放入文件,文件多了就需要归类放入文件夹,就好比我们在给电脑装软件时会进行归类安装,其实也是有意无意对电脑软件安装的 ...
- GXOI&GZOI
T1 与或和 2s&&512MB 简明题意:求一个矩阵的所有子序列的 \(and\)和 和\(or\)和: 子矩阵的\(and\)和就是所有值\(and\)起来:\(or\)类 ...
- css label两端对齐
上面这种效果很常见,实现的代码如下: html部分 <ul> <li class="detail_item"> <span class="d ...
- vue状态管理vuex从浅入深详细讲解
1.vuex简介以及创建一个简单的仓库 vuex是专门为vue框架而设计出的一个公共数据管理框架,任何组件都可以通过状态管理仓库数据沟通,也可以统一从仓库获取数据,在比较大型的应用中,数据交互庞大的情 ...
- 2D地图擦除算法
. 关于2D地图擦除算法,去年我写过一个实现,勉强实现了地形擦除,但跟最终效果还相差甚远,这次我写了一个完整的实现,在此记录,留个印象. . 去年的版本<<算法 & 数据结构--裁 ...
- document.visibilityState 监听浏览器
document.hidden:表示页面是否隐藏的布尔值.页面隐藏包括 页面在后台标签页中 或者 浏览器最小化 (注意,页面被其他软件遮盖并不算隐藏,比如打开的 sublime 遮住了浏览器). do ...