正確來說,ZK-1309 應該稱之為 onFloatUp 的故事...... [淚目]
故事發生在 modal window 當中如果用了 Clients.showNotification(),
要再對 modal window 作 drag 的時候,覆蓋畫面的 mask 就會消失,
在 drop 的時候才又出現;令人傻眼的是,如果繼續 DnD,就又恢復正常行為。
因為搭配 Notification 才會出問題,所以看了一下 Notification 是不是有做什麼手腳......
根本就是浪費時間 [死]
既然時間都浪費掉了,就順便記一下。
Clients.showNotification() 最後負責呈現的 JS
是 Notification.js,在 show() 當中會去新增一個 zul.wgt.Notification(),原則上行為都是繼承自 Popup。
問題應該還是回歸到 Window 自己身上,不過先找到 mask 是誰處理再說。
用 css name 硬幹的結果找到 effect.js 中的 zk.eff.FullMask,
在 sync() 的時候會用 jqzk.isVisible()(在 dom.js 裡頭)去檢查 Window 是不是看得到
(要 css 的 display != "none" 而且 visibility != "hidden"),
如果看不到就把自己 hide 起來,故事的舞台就在這兒了。
於是在 debug 的過程中,難免會去印 sync() 的參數 el 的值,遇到一個靈異現象:
console.log(zk(el).jq[0]); //DOM 的 visibility 沒有值
console.log(zk(el).jq[0].style.visibility); //印出 hidden
//也有剛好顛倒過來的情形
真是有夠莫名的,完全不懂為什麼?一開始只用第一個寫法觀察值,就發生了結果跟過程對不起來的悲劇 [核爆]
從這往回推,是 Window.zsync() 呼叫這個 method,再往回推...
就被推倒了推不下去了,因為在重現問題的時候 FullMask.sync() 在一次動作中會被呼叫好幾次;
而 Window.zsync() 在 Window 裡頭遍地開花。
只好用 console.trace() 大法來慢慢看,最後發現在這個 case 中一致的起點是 Window.onFloatUp(),
整理如下:
- 單純 drag window
- 開始時:
Drag._startDrag()→Window._startmove()→zWatch.fire('onFloatUp, dg.control) - 結束時:
Drag._finishDrag()→Window._aftermove()→Window.zsync()
- 開始時:
- 叫出
Notification然後點 window:Widget.mimicMouseDown_() - 叫出
Notification然後 drag window:- 下列選一(原因不明,好像還有測出另一個只是忘了記......)
_domEvtProxy0()doMouseover()
- 回歸單純 drag window 流程
- 下列選一(原因不明,好像還有測出另一個只是忘了記......)
在發了 onFloatUp 的 event 之後,Window 方面流程大致上是:
Window.onFloatUp()
Widget.setTopmost()
Widget.setFloatZIndex_() :有給 opt = {fire:true},所以後面 onZIndex() 一定會 fire
Window.setZIndex()
Widget.setZIndex() : 上面有 super("setZIndex"),如果值沒變就不會往下作
Window.onZIndex() : 上面有 fire("onZIndex")
Window.zsync()
Window.zsync()
只要有進入 setTopmost(), 那麼 onZIndex() 跟 setZIndex() 會各 call 一次 zsync(),
也就導致 mask 也會 sync() 兩次,雖然很無奈,但反正不是要修 performance issue,所以不管他 XD。
那麼,問題是出在哪裡呢?
簡單地說,就是 Notification 與 modal window 爭奪「最高」的機制(可以說是 setTopmost())所延伸出來的問題。
一開始 modal window 出來時,他得是「全 DOM 最高」,所以 setTopmost() 計算出來的結果 zIndex 是 1800。
後來殺出的 Notification 必須高過 modal window,所以 setTopmost() 決定給他 zIndex 為 1801。
當 Notification 消失(無論是哪一種方式)的那個瞬間,modal window 觸發 onFloatUp(),
於是要再次奪回「全 DOM 最高」的地位,偏偏這時候 Notification 還沒有真正死掉,所以 setTopmost() 就給了 1802。
真正開始處理 modal window 的 DnD 時(也會觸發 onFloatUp()),又會再確保一次自己是「全 DOM 最高」,
但是這時候 setTopmost() 發現只要給 1800 就夠了,就真的給 1800。
結果往下處理到 Widget.setZIndex() 的時候發現前後兩次的 zIndex 值不一樣,
按照規定去作(兩次)Window.zsync(),在 FullMask.sync() 那裡檢查 modal window 是不是還在......
登登! [炸]
根據 DnD 的設計原則(細節略),在開始 drag 的時候,end-user 看到、拖曳的其實是假的鬼魂,
真正的本尊還留在原本的位置只是隱藏不見,所以 FullMask 覺得 modal window 看不見,自己也就乖乖地跟著不見。
在解 bug 的途中(?)嘗試撰寫一個 Widget.isTopmost() 來判斷是不是已經是「全 DOM 最高」,
沒有經過嚴格驗證,不過在「叫出 Notification 然後點 modal window」的情況下測試會過,
就保留一份在這灌水字數以供緬懷:
isTopmost: function () {
if (!this.desktop) return false;
for (var wgt = this; wgt; wgt = wgt.parent) {
if (wgt._floating) {
for (var j = _floatings.length; j--;) {
var w = _floatings[j].widget,
h = _floatings[j].node;
if (wgt == w) continue;
if (this.getFloatZIndex_(this.$n()) < w.getFloatZIndex_(h) && !zUtl.isAncestor(this, w)) {
return false;
}
}
}
}
return true;
},
最後的解法還是在 Window 的 _startmove() 時塞一個叫 isDragging 的 field,
在 _aftermove() 的時候拿掉。所以只要在 onFloatUp() 的時候檢查這個 field,
如果正在 drag 就不繼續往下作,bug 就圓滿地圓寂(?)了。
當然,不管是解 bug 的當下、或是在回顧這個故事時,
都不覺得這樣的作法有很好,總覺得應該去解決 setTopmost() 或是 onFloatUp() 機制著手來斬草除根......
這大概就是所謂 maintain 的情懷(?)吧......
No comments:
Post a Comment