9

IE 都更筆記 - 為 Chrome/Edge 加上 showModalDialog Polyfill

 2 years ago
source link: https://blog.darkthread.net/blog/showmodaldialog-polyfill/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
為 Chrome/Edge 加上 showModalDialog Polyfill-黑暗執行緒

隨著 IE 即將 EOS (野生 IE 將於 2022/6/15 滅絕,企業人工飼養 IE 則到 2029),IE Only 網頁都更如火如荼。而 showModalDialog 問題也來到第三篇,足見這議題還挺煩人的。

先整理之前的研究心得:

基本上第二種方法已大致滿足需求,但逐一改寫所有網頁裡的 showModalDialog 有點麻煩,於是我想到 Pollyfill

Polyfill 原指為舊瀏覽器實現或模擬新版瀏覽器才有的函式,讓程式不需修改也能在舊瀏覽器運行。但這回我們反向操作,試著為新式瀏覽器加上古董功能,減少古蹟網頁支援新瀏覽器的難度。

我的構想是偵測 window.showModalDialog 是否已存在,若不存在就加上自製 showModalDialog 函式。自製 showModalDialog 函式將遮蔽原網頁禁止操作,用 window.open 開啟網址,等新視窗關閉再移除遮罩。為了盡可能貼近 IE showModalDialog 行為,自製版也接收 url、dialogArguments、options 三組參數,並識別 dialogWidth/dialogHeight/dialogTop/dialogLeft 等設定,讓新視窗與原本 IE showModalDialog 顯示的大小及位置相近。

另外,由於 window.open 開啟視窗無法永遠保持在最上方,可能會被藏到後面(而且還很難找),往留被遮罩無法操作的原網頁不知如何是好。我動了一點手腳,當滑鼠點選遮罩時呼叫 newWin.focus() 將新視窗重新推到最上層,稍稍彌補無法設定永遠最上層的缺憾。至於原本透過 dialogArguments、returnValue 傳遞資料,以及阻斷程式執行直到新視窗關閉的行為,這部分非得改寫不可。採行做法是將要傳遞參數存入 window._dialogArguments,讓新視窗透過 opener._dialogArguments 抓取,要傳回結果將放入 opener._returnValue,新視窗關閉後要執行的程式寫在 Promise.then() 裡,_returnValue 則會是 onFullfilled 事件的傳入參數,讓程式更直覺。(請看範例 TEST 6 示範) 另外,為了讓程式能在 IE 編譯,要避免 let、() => 等 IE 不支援的寫法。

範例程式如下:

<html>
<head>
	<meta charset="utf-8">
	<style>
		button { margin: 3px; }
	</style>
</head>
<body>
	<script>
		var u = 'newwin.html';
		function test(dialogFeatures, dialogArguments) {
			return window.showModalDialog(u, dialogArguments, dialogFeatures);
		}
	</script>
	<div>
		<button onclick="test('')">TEST 1</button>
		no options
	</div>
	<div>
		<button onclick="test('dialogWidth:480px ;dialogHeight= 320px')">TEST 2</button>
		dialogWidth:480px ;dialogHeight= 320px
	</div>
	<div>
		<button onclick="test('dialogWidth:480px;dialogHeight:320px;center:no')">TEST 3</button>
		dialogWidth:480px;dialogHeight:320px;center:no
	</div>
	<div>
		<button onclick="test('dialogWidth:480px;dialogHeigth:320px;dialogTop:50px')">TEST 4</button>
		dialogWidth:480px;dialogHeigth:320px;dialogTop:50px
	</div>
	<div>
		<button onclick="test('dialogWidth:480px;dialogHeigth:320px;dialogTop:50px;dialogLeft:50px')">TEST 5</button>
		dialogWidth:480px;dialogHeigth:320px;dialogTop:50px;dialogLeft:50px
	</div>
	<div>
		<button onclick="test('','FROM OPENER').then(function(res) { alert(res); })">TEST 6</button>
		pass "FROM OPENER" and get return value (for Chrome/Edge)
	</div>
	<script>
		if (!window.showModalDialog) {
			window.showModalDialog = function (url, dialogArguments, options) {
				//using var instead of let for IE compatible
				var mT = /dialogTop[=:]\s*(\d+)/i.exec(options);
				var mL = /dialogLeft[=:]\s*(\d+)/i.exec(options);
				var mW = /dialogWidth[=:]\s*(\d+)/i.exec(options);
				var mH = /dialogHeight[=:]\s*(\d+)/i.exec(options);
				var mC = /center[=:]\s*(off|no|0)/i.exec(options);
				options = {
					width: mW && parseInt(mW[1]), height: mH && parseInt(mH[1]),
					top: mT && parseInt(mT[1]), left: mL && parseInt(mL[1]),
					center: mC ? false : true
				};
				window._dialogArguments = dialogArguments;
				var maskLayer = null;
				maskLayer = document.createElement('div');
				maskLayer.style.position = 'absolute';
				maskLayer.style.zIndex = 65535;
				maskLayer.style.opacity = 0.5;
				maskLayer.style.top = 0;
				maskLayer.style.left = 0;
				maskLayer.style.width = '100vw';
				maskLayer.style.height = '100vh';
				maskLayer.style.backgroundColor = '#444';
				document.body.appendChild(maskLayer);
				var promise = new Promise(function (resolve, reject) {
					var w = Math.round(options.width || 500);
					var h = Math.round(options.height || 500);
					var t = (options.top || (options.center ? (window.screen.availHeight - h) / 2 : 10)) + window.screen.availTop;
					var l = (options.left || (options.center ? (window.screen.availWidth - w) / 2 : 10)) + window.screen.availLeft;
					var newWin = window.open(url, '_blank', 'width=' + w + ',height=' + h + ',top=' + t + ',left=' + l);
					maskLayer.addEventListener('click',function() { newWin.focus(); });
					var hnd = setInterval(function () {
						try {
							if (newWin.closed) {
								clearInterval(hnd);
								//popup window can assing opener._dialogReturnValue for return value
								resolve(window._dialogReturnValue);
								maskLayer && maskLayer.remove();
							}
						}
						catch (e) {
							clearInterval(hnd);
							reject();
							maskLayer && maskLayer.remove();
						}
					}, 100);
				});
				return promise;
			}
		}
	</script>
</body>

</html>

為展示 dialogArguments 與 returnValue 傳遞,我寫了一個簡單的 newwin.html 負責傳收及回傳結果。

<!DOCTYPE html>
<html>
<body>
	dialogArguments = <span id=a></span> <br />
	Return Value = <span id=s></span> <button onclick='closeWin()'>Close</button>
	<script>
		try {
			document.getElementById('a').innerText = opener._dialogArguments;
		} catch(e) { }
		var t = new Date().getTime();
		document.getElementById('s').innerText = t;
		function closeWin() {
			try {
				opener._dialogReturnValue = t;
			}
			catch(e) { }
			window.close();
		}
	</script>
</body>
</html>

展示影片:(先用 IE 示範原生 showModalDialog,後改用 Edge 測試對照 Polyfill 效果,最後示範若新視窗被其他視窗覆蓋,點一下遮罩會跳回最上層)

操作展示

這個小程式估計可節省一些翻修 IE Only 網頁的時間,提供從事維護古蹟的同學參考並歡迎回饋意見。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK