在新窗口中打開頁面?小心有坑!

  • 2019 年 12 月 4 日
  • 筆記

1. 背景

產品需求來啦:點擊頁面上某個東西,要在新窗口中打開一個頁面,注意!要在新窗口中打開。你呵呵一笑,太簡單了:

  1. 打開的頁面地址是固定的?直接a標籤加上target="_blank"屬性搞定。
  2. 打開的頁面地址是動態計算的?使用js進行window.open(url)搞定。

如果你人品比較好,你的頁面可以順利地運行到下線為止。但如果你臉比較黑,可能會遇到以下問題:

  1. 用戶投訴:我在你們頁面上進行的操作,怎麼賬號被盜了!!
  2. 用戶吐槽:頁面卡得不行了。。。

WTF?

2. 來兩個例子

2.1 例子1:

步驟:

  1. 進入這個微博帖子頁面: http://blog.sina.com.cn/s/blog_c3a321040102wdq4.html
  2. 點擊正文的」點擊有驚喜哦「鏈接。
  3. 看了下新打開的頁面,什麼驚喜都沒有啊。。。回到上一個剛才的頁面窗口。
  4. 嗯?登錄態丟了,重新登錄一下吧。
  5. 靠,中招了!

2. 例子2:

步驟:

  1. 使用chrome打開這個頁面: http://coolriver.github.io/blog/pages/openerTest/origin.html, 你會看到有5個可點的鏈接,還有一個鬼畜的隨機數。
  2. 點擊第一個鏈接,也就是『target _blank』字樣的那個。
  3. 新頁面顯示'HACK成功,再看看上個TAB?'。然後你忍不住看回上一個頁面。
  4. 看到第一行鮮紅的提示:'你被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置為空。特點:

  1. 可解決除IE外的安全問題,和所有現代瀏覽器的性能問題

4.2 window.open並設置opner為空

var otherWindow= window.open();  otherWindow.opener = null;  other = 'http://newurl';

特點:

  1. 可解決所有除safari外,所有瀏覽器的安全問題,無法解決性能問題

4.3 新建Iframe中打開新窗口,然後關掉iframe

特點:

  1. 可解決safari下的安全問題,無法解決性能問題

4.4 推薦方案

  1. 如果是a標籤要在新窗口中打開,添加noopener屬性
  2. 如果是js中打開新窗口,手動將新窗口的opener置為null