前言

今天,我们要揭晓一个 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/

F.js:https://js.fineui.com/

今天你上班了吗?来聊聊一个隐蔽了 5 年的BUG!的更多相关文章

  1. 怎样才能提交一个让开发人员拍手叫好的bug单

    怎样才能提交一个让开发人员拍手叫好的bug单 软件测试人员写得最多的文档就是测试用例和BUG,现在测试用例和BUG都没有标准的模板,每个公司使用的缺陷管理工具都有可能不一样,如果你换了一家公司就有可能 ...

  2. Java中,一个存在十几年的bug...

    今天,分享一个JDK中令人惊讶的BUG,这个BUG的神奇之处在于,复现它的用例太简单了,人肉眼就能回答的问题,JDK中却存在了十几年.经过测试,我们发现从JDK8到14都存在这个问题. 大家可以在自己 ...

  3. 面试题(造火箭必备技能):请举例一个最有成就感的性能bug

    当前,绝大部分招聘都有性能要求或者把其作为加分项(会性能优先),哪怕你不是面试的性能,面试的时候可能会问性能,所以大家才会有"面试造火箭,进去拧螺丝"的共鸣.至于企业为什么重视性能 ...

  4. 记Windows的一个存在了十多年的bug

    bug Windows有一个bug,持续了十多年,从Windows Visita开始(2007年),一直存在,直到Windows11(2021年)才修复(其实也不叫修复,后面我再具体说),而Windo ...

  5. Java中关于 BigDecimal 的一个导致double精度损失的"bug"

    背景 在博客 恶心的0.5四舍五入问题 一文中看到一个关于 0.5 不能正确的四舍五入的问题.主要说的是 double 转换到 BigDecimal 后,进行四舍五入得不到正确的结果: public ...

  6. Oracle 11.2.0.1的又一个隐藏在ORA-03113后的bug: 通信通道的文件结尾

    近期又一个项目反馈ORA-03113错误: 通信通道的文件结尾.(jdbc程序报出的错误是:无法从套接字读取更多的数据) 发送之前处理过类似问题的解决方法(http://www.cnblogs.com ...

  7. 记一个SwipeMenuListView侧滑删除错乱的Bug

    做侧滑删除网上有很多方案,比如重写Listview实现滑动的监听,今天说下一个SwipeListView,这个是之前一个朋友在网上开源的一个封装组件,能够适用于多种情况,项目地址:https://gi ...

  8. 一个nginx 回源限速的bug处理过程记录

    一个生产环境,nginx占用cpu很高. top - :: up day, :, users, load average: 13.26, 13.20, 13.20 Tasks: total, runn ...

  9. 一个可遇不可求的 bug 全局变量初始化顺序问题 哈哈

    这是今天下午帮同事查的一个客户端 C++ 的 bug,前人留下的谜之代码.. 具体情况是,客户端实现了有一个简单的内存池,每次申请内存的时候会把新申请到的内存信息存到一个 map 里,据说是为了检查内 ...

随机推荐

  1. 大白话讲解Spring的@bean注解

    1.Spring注解分类 从广义上Spring注解可以分为两类: 一类注解是用于注册Bean 假如IOC容器就是一间空屋子,首先这间空屋子啥都没有,我们要吃大餐,我们就要从外部搬运食材和餐具进来.这里 ...

  2. $loj6043$ [雅礼集训 $2017\ Day7$] 蛐蛐国的修墙方案 搜索

    正解:搜索 解题报告: 传送门$QwQ$ 首先由$p_i$是一个序列得,每个点的度数为2.且一定形成若干个环. 考虑先对每个环做,发现若要有解必须是偶环,且一定是隔一条边选一条边的,所以对每个环其实只 ...

  3. $Noip2011/Luogu1315$ 观光公交 贪心

    $Luogu$ $Sol$ 觉得这题贪心要想很多事情,不适合我这种没脑子选手$ovo$.看题解还理解了很久. 最开始是这样想的:把所有的路段上的乘客按大小排个序用加速器就好了,这个想法被自己轻松$ha ...

  4. 基于GMC/umat的复合材料宏细观渐近损伤分析(一)

    近期在开展基于GMC/umat的复合材料宏细观渐近损伤分析,一些技术细节分享如下: 1.理论基础 针对连续纤维增强复合材料,可以通过离散化获得如下的模型: (a)(b)(c) 图1 连续纤维增强复合材 ...

  5. 计算n的阶乘

    题目描述 定义一个函数,传入一个整数n,打印n!的值比如:传入3打印:6  <====1*2*3 输入 整数n    输出 整数n的阶乘 样例输入 Copy 3 样例输出 Copy 6 x=in ...

  6. Java类成员之方法

    方法含义: 1. 方法是类或对象行为特征的抽象,用来完成某个功能操作. 2.在某些语言中也称为函数或过程. 3.将功能封装为方法的目的是简化代码,可以实现代码重用. 4.在Java里的方法不能独立存在 ...

  7. Ubuntu 19.10 安装 jupyter

    安装pip3 ubuntu 19.10 已经没有python了,取代的是python3. 执行sudo apt install python3-pip安装pip3 安装jupyter 执行sudo p ...

  8. flask部署深度学习模型

    flask部署深度学习模型 作为著名Python web框架之一的Flask,具有简单轻量.灵活.扩展丰富且上手难度低的特点,因此成为了机器学习和深度学习模型上线跑定时任务,提供API的首选框架. 众 ...

  9. Lambda表达式(lambda expression)⭐⭐⭐⭐⭐

    原作者 lambda表达式(lambda expression)实际上是匿名函数一种表示形式, 即没有函数名的函数:参数列表=>表达式或语句块,在我看来主要目是为了简化代码编写,提高代码可读性而 ...

  10. SPSS 相关性的选择

    在SPSS中导入数据,analyze-correlate-bivariate-选择变量 OK 输出的是相关系数矩阵 相关系数下面的Sig.是显著性检验结果的P值,越接近0越显著. 同样的数据,我们接着 ...