
这个bug发生在一个纯脚本的TreeView控件上面,其异常表现为: 当使用键盘Up&Down键移动TreeView上面被选中的Item时,IE报告currentNode为空的脚本错误。关于这个功能的详细设计,可以参看本文,示例效果为:

上图中的node就是程序中的currentNode,Up&Down操作就是根据这个node来进行计算并移动的。在TreeView控件中,为了处理TreeView节点被多选时找到一个确切的currentNode,在每次选择操作时,我在TreeView实例的全局cache中纪录了一个叫m_LastSelected的值,保存最后一次选择动作中的第一个节点。如果是单选就是被选中的节点本身,如果是多选,就是多选时第一个被选中的节点。










Review上面这段代码,取currentNode的地方似乎是已经做了安全检查,可是怎么还是出错了呢?其实除了本身程序有错,上面这个校验代码也是不对的,我检查的是m_Selecteds这个集合里面有没有东西,然后却是用m_LastSelected赋的值,这之间就可能出现m_Selecteds和m_LastSelected不同步的问题。结果确实是m_Selecteds和m_LastSelected之间的关联出了问题!看下面的代码:











这个逻辑似乎是没有什么问题,反正只要是选择了TreeView上的节点,就会有m_LastSelected的存在,我们也就会得到Up&Down操作的计步节点currentNode。可是就是这里漏掉了一个情况的考虑,如果被选中节点是在initialize我们的TreeView时,由调用代码来设置给节点的(示例如下)。那么由于实现时的疏忽,就出现了m_Selecteds非空,而m_LastSelected为空的情况。





SetSelected(isSelected)方法实现如下:

























这个bug如果是Review代码可能细心能找出来,可是直接被QA通过黑合测试给提了出来,确实是相当相当不容易的。因为刚初始化好的TreeView是没有获得焦点的,这时不管怎么按Up&Down键都不会有反映,也就看起来并没有什么错误。如果使用鼠标点击一下TreeView内的任何区域,让其获得焦点,那么至少都会选中一个TreeNode,这样一来m_LastSelected也就有值了。那么这个如此隐蔽的bug是怎么发现的呢?这需要在页面载入并在TreeView生成后,立即使用Tab键把焦点移到TreeView的container元素上,这时候按Up&Down键,如果TreeView上面有初始花时被SetSelected(isSelected)选中的节点,那么就出错了
