由于公司产品定制了devtools,而我们的产品又是作用于任意网站的,对于一些网站(例如科大讯飞)是不能打开devtools或者说打开后立刻关闭,所以这篇文章总结一下有哪些实用的方法可以监听到devtools的打开。
快捷键方式阻止打开devtools
我们检测浏览器开发者工具的打开,首先我们需要知道有哪些方式可以打开浏览器开发者工具,然后如何避免这些方式的打开。
- F12
- Ctrl + Shift + I
- Ctrl + Shift + C
- Shift + F10, 可以打开右键菜单
这些情况很好处理就是监听用户键盘按下事件keydown
,然后进行默认事件阻止就行。
js
document.addEventListener('keydown', (e) => {
// 禁止f12,禁用ctrl+shift+i,c,禁用shift+f10
const code = e.code
const ctrl = e.ctrlKey
const shift = e.shiftKey
const isCSI = ctrl && shift && code === 'KeyI'
const isF12 = code === 'F12'
const isCSC = ctrl && shift && code === 'KeyC'
const isSF10 = shift && code === 'F10'
if ( isF12 || isCSI || isCSC || isSF10) {
e.preventDefault();
}
})
- 右键菜单
js
// 禁用菜单
document.addEventListener('contextmenu', (e) => {
e.preventDefault();
})
- 浏览器设置 - 更多工具 - 开发者工具 (这种就没办法阻止了)
- 先打开一个正常页面,打开开发者工具,然后再访问禁止打开开发者工具的网站 (这种就没办法阻止了)
打开devtools,如何关闭
了解了如何打开开发者工具后,我们知道还有一些情况是我们开发无法阻止的,所以我们就需要等到用户打开开发者后,做一些操作来让开发者工具关闭掉。
这里最简单的一种做法就是监听当前可视区域的宽高,来判断是否打开了浏览器开发者工具。
js
window.addEventListener('resize', function () {
// outerWidth 整个浏览器宽度 高度 103
if (window.outerWidth - window.innerWidth > 0 || window.outerHeight - window.innerHeight > 130) {
console.log("开发者工具已打开!");
// TODO: 做一些操作,例如重定向,重写innerHTML,关闭页面等等
location.href = "about:blank"
}
});
这种方式对于将开发者工具分离出一个窗口 和先打开一个正常页面,打开开发者工具,然后再访问禁止打开开发者工具的网站 这两种无法进行操作。这种方式也是devtools-detect
的原理。
我们来看看disable-devtool
和 devtools-detector
的实现原理吧。
目前有很多网站都使用dsiable-devtool
来实现的。例如科大讯飞。
他主要是通过log
, table
来分别打印一下大对象数组,看打印相差的时间。其原理就是通过浏览器打印大对象数组的性能判断是否是打开了devtools。如果说我们打开浏览器开发者工具控制台,那么打印的内容将会展开照成内存大量占用,所以时间差就会变大。不打开浏览器开发者工具控制台将不会对对象展开。
js
function now() {
return new Date().getTime();
}
// 创建大对象数组
function createLargeObject() {
const largeObject = {};
for (let i = 0; i < 500; i++) {
largeObject[`${i}`] = `${i}`;
}
return largeObject;
}
// 创建大对象数组
function createLargeObjectArray() {
const largeObject = createLargeObject();
const largeObjectArray = [];
for (let i = 0; i < 50; i++) {
largeObjectArray.push(largeObject);
}
return largeObjectArray;
}
// 计算打印执行时间
function calculateTime(func) {
const start = now();
func();
return now() - start;
}
const largeObjectArray = createLargeObjectArray()
let maxPrintTime = 0
setInterval(() => {
// table 打印时间
const tablePrintTime = calculateTime(() => { console.table(largeObjectArray); });
// 普通输出时间
const printLogTime = calculateTime(() => { console.log(largeObjectArray)})
maxPrintTime = Math.max(maxPrintTime, printLogTime)
if (tablePrintTime === 0 || maxPrintTime === 0) {
return
}else {
// 如果打印表格的时间是普通打印的10倍,那么就关闭
if (tablePrintTime > maxPrintTime * 10) { // 如果当前表格打印时间大于指定时间,那么将表示打开了devtools
console.log("时间对比", tablePrintTime, maxPrintTime)
window.close()
}
}
}, 500)
具体可以看这两篇文章了console.log
是否会照成内存泄漏
- console.log 一定会导致内存泄漏?不打开 devtools 就不会, 他主要讲打开控制台会造成内存瞬间增加,不打开就不会。
- console 内存占用的误解 他主要是反驳上面一篇文章的说法,原因是打开控制台会展开对象(默认展开一层),所以也增加了内存占用。
js
<!DOCTYPE html>
<html lang="en">
<body>
<button id="btn">点我</button>
<div id="box"></div>
<script>
const btn = document.getElementById('btn');
const box = document.getElementById('box');
btn.addEventListener('click', function () {
const MB = 1024 * 1024;
log();
function log() {
const memory = performance.memory.totalJSHeapSize;
const usagedMemory = Math.floor(memory / MB);
box.insertAdjacentHTML('beforeend', `<span>${usagedMemory} </span>`);
// const obj = { usagedMemory, str: 'g'.repeat(50 * MB) }; // 这种就会造成内存激增
const obj = { usagedMemory, wrapper: {str: 'g'.repeat(50 _ MB)} }; // 这种就不会造成内存激增
console.log(obj);
setTimeout(() => log(), 50);
}
});
</script>
</body>
</html>
还有一种补充的方式就是通过debugger
来判断是否打开devtools, 这种方式并不是打开开发者工具立刻就监听到,而是通过用户触发断点计算时间,间接判断。但是我们知道我们设置断点后,需要刷新浏览器后才会生效。chrome高版本(v121.0.6167.85)可以通过异步递归的方式来使打开开发者工具后立刻执行断点,而无需刷新浏览器。 这种方式也是看devtools-detector
源码发现的。
js
function now() {
return new Date().getTime();
}
const debuggerChecker = {
name: 'debugger-checker',
async isOpen() {
const startTime = now();
(function () {}).constructor('debugger')();
return now() - startTime > 100;
},
async isEnable(){
return true;
},
};
async function _detectLoop() {
let isOpen = false;
let checkerName = '';
checkerName = this._checkers[0].name;
isOpen = await this._checkers[0].isOpen();
isOpen && (function (isOpen, detail) {
console.log('isOpen', isOpen, detail);
// TODO: 这里执行关闭操作
window.close()
})(isOpen, {checkerName})
setTimeout(_detectLoop, 500);
}
window._checkers = [debuggerChecker]
window._isOpen = false
_detectLoop()
这种方式我们也是可以通过一些方法避免的。
- 重写console对象中的方法。
js
Object.defineProperty(window, 'console', {
value: {
table: () => {},
log: () => {},
clear: () => {},
debug: () => {},
warn:() => {}
}
});
- 浏览器禁用js脚本
- 重写source
- 使用浏览器插件注入脚本。
- ...
其实前端并没有提供一种api去监听devtools的打开与关闭,所以的方式都是奇淫巧技。都有一定的局限性,只能说尽可能的去监听。
注意以上是对chrome浏览器的分析,还有一些方式适用于其他浏览器。
参考
- 前端开发中如何在JS文件中检测用户浏览器是否打开了调试面板(F12打开开发者工具)?
- Find out whether Chrome console is open
- devtools-detector
- disable-devtool
往期年度总结
往期文章
- 因为原生,选择一家公司(前端如何防笔试作弊)
- 结合开发,带你熟悉package.json与tsconfig.json配置
- 如何优雅的在项目中使用echarts
- 如何优雅的做项目国际化
- 近三个月的排错,原来的憧憬消失喽
- 带你从0开始了解vue3核心(运行时)
- 带你从0开始了解vue3核心(computed, watch)
- 带你从0开始了解vue3核心(响应式)
- 3w+字的后台管理通用功能解决方案送给你
- 入职之前,狂补技术,4w字的前端技术解决方案送给你(vue3 + vite )