在新窗口中打開頁面?小心有坑!
- 2019 年 12 月 4 日
- 筆記
1. 背景
產品需求來啦:點擊頁面上某個東西,要在新窗口中打開一個頁面,注意!要在新窗口中打開。你呵呵一笑,太簡單了:
- 打開的頁面地址是固定的?直接a標籤加上
target="_blank"
屬性搞定。 - 打開的頁面地址是動態計算的?使用js進行
window.open(url)
搞定。
如果你人品比較好,你的頁面可以順利地運行到下線為止。但如果你臉比較黑,可能會遇到以下問題:
- 用戶投訴:我在你們頁面上進行的操作,怎麼賬號被盜了!!
- 用戶吐槽:頁面卡得不行了。。。
WTF?
2. 來兩個例子
2.1 例子1:
步驟:
- 進入這個微博帖子頁面: http://blog.sina.com.cn/s/blog_c3a321040102wdq4.html
- 點擊正文的」點擊有驚喜哦「鏈接。
- 看了下新打開的頁面,什麼驚喜都沒有啊。。。回到上一個剛才的頁面窗口。
- 嗯?登錄態丟了,重新登錄一下吧。
- 靠,中招了!
2. 例子2:
步驟:
- 使用chrome打開這個頁面: http://coolriver.github.io/blog/pages/openerTest/origin.html, 你會看到有5個可點的鏈接,還有一個鬼畜的隨機數。
- 點擊第一個鏈接,也就是『target _blank』字樣的那個。
- 新頁面顯示'HACK成功,再看看上個TAB?'。然後你忍不住看回上一個頁面。
- 看到第一行鮮紅的提示:'你被HACK了啊!這個頁面的地址已經變了!',同時,最下面一行的鬼畜隨機數時不時地有些卡頓。
3. 新窗口中打開頁面的問題
用簡單地方式(背景中提到的)在新窗口中打開新頁面會有一些問題。問題分為安全和性能兩方面。機智的讀者會發現上面的兩個例子中分別復現了安全和性能問題(講道理,第2個例子同時展現了安全和性能問題)
3.1 安全問題
使用a標籤的target="_blank"
屬性,或者window.open(url)
在新窗口中打開頁面時,會存在潛在的安全問題。為什麼呢?這個鍋是一個叫opener
的全局對象的鍋。
回到例子1,可以自己動手嘗試,在新打開的那個頁面中,打開console, 輸入opener
,可以看到這個對象,正是打開本頁面的父頁面的窗口對象。如果父頁面和新開窗口中的頁面是不同域名的,瀏覽器會禁止新窗口訪問opener中的內容。但是有一個操作除外:可以通過window.opener.location = newURL
來重寫父頁面的url,即使與父窗口的頁面不同域。
例子1就是利用這個方式,將父窗口的鏈接悄悄地替換成了釣魚頁面的地址。剛好父窗口的原始頁面沒有做防止被iframe嵌入,可以簡單地通過iframe做一個極真實的釣魚頁面。如果不看url根本區分不出來是釣魚頁面(父窗口剛打開的時候好好的,誰會關注到這個url居然悄悄地變了呢?)
3.2 性能問題
除了安全問題,例子2中還展示了簡單地在新窗口中打開頁面的性能問題。源頁面中鬼畜的隨機數之所以會卡頓,也是受新打開的窗口中的頁面影響。在例子2中,新頁面中有一個定時器,每隔一段時間就有一個持續的循環,這個循環在阻塞新頁面本身的js線程的同時,也阻塞了opener(也就是打開新頁面的父窗口)里的js線程。如果再搞得狠一些,父窗口中的頁面交互可以寸步難行。
為什麼新窗口中的頁面會影響父頁面的線程呢?chrome不是每個標籤頁一個單獨的進程?然後進程內包含若干線程嗎?
確實,chrome有不同的標籤頁面使用不同進程和線程,但是有個例外,通過a標籤的target="_blank"
屬性,或者window.open(url)
在新窗口中打開頁面, 會與父窗口共用進程和線程。為什麼呢?還是因為opener。。。。因為opener里有DOM信息。兩個進程中同時hold住了DOM信息,在多進程下很難道控制,所以乾脆就放在一個進程里了。這個算是chrome的一個小缺陷(firefox也有,ie沒有),不過chrome目前正在跟進和優化這裡,詳情可參考這裡。
4. 解決方案
4.1 使用noopener屬性
通過在a標籤上添加這個noopener屬性,可以將新打開窗口的opner置為空。特點:
- 可解決除IE外的安全問題,和所有現代瀏覽器的性能問題
4.2 window.open並設置opner為空
var otherWindow= window.open(); otherWindow.opener = null; other = 'http://newurl';
特點:
- 可解決所有除safari外,所有瀏覽器的安全問題,無法解決性能問題
4.3 新建Iframe中打開新窗口,然後關掉iframe
特點:
- 可解決safari下的安全問題,無法解決性能問題
4.4 推薦方案
- 如果是a標籤要在新窗口中打開,添加noopener屬性
- 如果是js中打開新窗口,手動將新窗口的opener置為null