解析網頁如何推測 DevTools 已開啟:視窗尺寸差、debugger 陳述式計時與主控台 getter,各自為何誤判,以及為何只是弱訊號。
有些網頁在你按下 F12 的瞬間就變得古怪:內容被清空,分頁卡死,或者跳出一條怎麼也關不掉的「偵錯工具已暫停」提示。這並不神祕:頁面上的腳本正在猜測 DevTools 是否已開啟,依據是瀏覽器無法對頁面 JavaScript 隱藏的副作用。本文介紹三種經典手法、各自的失效方式,以及為什麼它們單獨都算不上可靠訊號。
核心要點
- 沒有任何瀏覽器會告訴頁面「DevTools 已開啟」。 所有偵測都是啟發式推斷,依據是視窗尺寸變化、腳本被暫停,或某次主控台呼叫的表現不同。
- 視窗尺寸差比較
outerWidth與innerWidth(以及高度)。停靠式 DevTools 會佔用視口空間,差值變大;獨立視窗式 DevTools 則不改變差值。 debugger陳述式只在 DevTools 開啟時才會暫停執行,因此腳本可以為暫停計時,或迴圈觸發該陳述式來「懲罰」開啟的面板。這正是某些頁面會卡住的原因。- 主控台 getter 會在主控台格式化物件時觸發,與 CDP 序列化洩漏同屬一類技巧,行為隨瀏覽器版本而變。
- 這是弱訊號。 開發者、客服人員和好奇的使用者都會正當地開啟 DevTools,縮放或側邊欄也會造成誤報,所以嚴肅的系統只把它當作權重很低的輸入。
網站為什麼要找開發者工具
目的幾乎從來不是抓住閱讀頁面的人。常見動機有:
- 反爬蟲。 查看網路請求或 DOM 往往是爬取的第一步,網站可能因此提高風險評分。
- 保護前端程式碼。 付費牆邏輯、混淆腳本和廣告驗證程式碼,掛上偵錯工具後更容易被研究。
- 詐欺與濫用評分。 開啟檢查器是又一個弱輸入,與機器人偵測體系中更強的證據並列使用。
請注意範圍:本文討論的是人手動開啟 DevTools。透過 DevTools 背後的協定來驅動瀏覽器的自動化框架是另一個問題,見偵測 CDP 自動化和無頭瀏覽器偵測。
手法一:視窗尺寸差
瀏覽器暴露兩個尺寸。window.outerWidth 是包含工具列和側邊欄在內的整個瀏覽器視窗寬度,window.innerWidth 則是頁面視口寬度。正常情況下,寬度只差幾像素的視窗邊框,高度則差出分頁列和工具列的高度,所以偵測腳本會把閾值設得明顯高於這個數。把 DevTools 停靠到側邊或底部後,視口縮小而外層視窗不變,差值會跳升數百像素。
const threshold = 160; // px
const widthGap = window.outerWidth - window.innerWidth > threshold;
const heightGap = window.outerHeight - window.innerHeight > threshold;
const looksOpen = widthGap || heightGap;
它開銷小、完全被動、無需任何權限,所以是最常見的檢查。它也會以可預見的方式失效:
- 獨立視窗式 DevTools 在自己的視窗中開啟,頁面視口不變,因此偵測不到。
- 瀏覽器側邊欄(閱讀清單、AI 助理、垂直分頁列)同樣會縮小視口,看起來像停靠的檢查器。
- 頁面縮放和系統顯示縮放在某些平台上會改變兩個值之間的關係,沒有 DevTools 也會出現差值。
- 行動端和嵌入式檢視常常回報奇怪或相等的數值,這項檢查在那裡沒有意義。
這兩個值同樣會用於螢幕解析度指紋中的螢幕與視窗檢查。
手法二:debugger 陳述式
debugger 陳述式會呼叫目前可用的偵錯功能,如果沒有則什麼也不做。DevTools 關閉時它是耗時微秒的空操作;DevTools 開啟(且中斷點生效)時,執行會停在那一行,直到使用者按下繼續。
這給了頁面兩種用法。第一種是計時:取一個時間戳,執行 debugger,再取一個,如果暫停超過幾十毫秒,就認定「有偵錯工具附加」。
const t0 = performance.now();
debugger;
const paused = performance.now() - t0 > 100; // human-scale pause
第二種是陷阱:在計時器或迴圈中反覆呼叫 debugger,每次你按繼續它都會再次暫停。這就是 DevTools 開啟時頁面看起來卡死,或不斷跳出 Sources 面板的原因。瀏覽器允許你停用中斷點或略過某個腳本,所以這只是惱人而不是屏障,而只想看看主控台的使用者反而得到了更差的頁面。
失效方式:分頁暫停也可能是筆電休眠、裝置負載過高或背景分頁被節流,這些都會拉長計時;停用中斷點後,該陳述式又成了空操作。
手法三:主控台 getter
DevTools 開啟時,輸出一個物件會讓主控台為它建立預覽。頁面可以輸出一個帶 getter 屬性的物件,看 getter 是否被執行。
let opened = false;
const probe = new Error();
Object.defineProperty(probe, "stack", {
get() { opened = true; return ""; }
});
console.log(probe);
getter 是否觸發取決於瀏覽器、版本以及目前顯示的主控台面板,引擎也會不時改變這一行為,所以去年有效的檢查今年可能毫無反應。相關的 Chromium 機制(附加的自動化用戶端會讓每次 console.* 呼叫序列化其參數)在 CDP 偵測指南中有詳細說明;這裡要記住的是,getter 觸發既可能意味著「有人開啟了主控台」,也可能意味著「有協定用戶端已附加」,頁面分不清是哪種。
為什麼它只是弱訊號
上面每種方法都是猜測,而每種猜測都有無辜的解釋。網頁開發者、QA 測試人員、查證消息的記者、照著客服文章操作的使用者都會開啟 DevTools,封鎖他們的網站會損失真實客戶。與此同時,任何決心檢查頁面的人都可以把面板獨立出來或停用中斷點,所以這些檢查是啟發式的,也很容易繞開。因此它們只作為權重很低的特徵,與行為訊號等更強的證據並用,絕不單獨定論。
BrowserInsight 的檢測怎麼做
指紋檢測頁面在瀏覽器詳情中顯示 DevTools「已開啟」或「已關閉」的標記。它只使用視窗尺寸差,閾值為 160 px,因此表現與上文描述完全一致:
- 停靠式 DevTools(側邊或底部)能被偵測到,因為視口縮小超過 160 px。
- 獨立視窗式 DevTools 偵測不到。
- 較大的瀏覽器側邊欄即使在 DevTools 關閉時也可能觸發它。
機器人偵測頁面裡有一列「開發者工具檢測」,同樣基於 160 px 的尺寸差,風險等級為低。發現尺寸差後它還會執行一次主控台 getter 探測,但這次探測從不單獨決定結果。另有一列「行動端模擬檢測」專門辨識 DevTools 的裝置模擬模式:桌面平台上出現行動版 User-Agent,或瀏覽器視窗尺寸與回報的手機螢幕對不上。
兩項檢測都不使用 debugger 陳述式,所以 BrowserInsight 絕不會暫停你的頁面。你可以先把 DevTools 停靠、再改成獨立視窗,分別開啟工具看標記如何變化:它展示的是頁面能推斷出什麼,而不是任何事情的證明。
常見問題
網站真的能看到我開啟了 DevTools 嗎?
不能直接看到。它只能從視口尺寸變化或腳本被暫停等副作用中推斷,而且推斷常常出錯。
為什麼我開啟 DevTools 頁面就卡住?
通常是腳本在迴圈中呼叫 debugger,每次繼續都會再次暫停。在 Sources 面板中停用中斷點即可停止暫停。
偵測 DevTools 等於偵測機器人嗎?
不等於。這裡說的是人開啟了檢查器。自動化工具留下的痕跡不同,見無頭瀏覽器偵測。


