js检测浏览器开发者工具打开状态

4,789 阅读4分钟

本篇文章基于github上disable-devtool项目做的分析。

我这里只对项目中的部分代码进行分析,能力有限,望见谅。

项目结构方面我就不进行赘述了,仅对项目src文件夹中的文件进行分析。

src中的main.js文件中,首先引入的是disableKeyAndMenu方法,是用来阻止鼠标右键和键盘打开开发者工具的。

function disableKeyAndMenu () {
    window.addEventListener('keydown', (e) => {
        e = e || window.event;
        const keyCode = e.keyCode || e.which;// 
        alert(e.keyCode);
        if (keyCode === 123 || (e.shiftKey && e.ctrlKey && e.keyCode === 73)) {
            e.returnValue = false;
            e.preventDefault();
            return false;}
        }, false);
        if (config.disableMenu) {
            window.addEventListener('contextmenu', (e) => {
            e = e || window.event;
            e.returnValue = false;
            e.preventDefault();
            return false;
        }, false);
    }
}

这个就很明显了,监听按键事件,如果按到可以打开开发者工具的按键就阻止默认事件。同时对页面进行监听,阻止鼠标右键事件。contextmenu就是用来监听触发内容菜单的,鼠标右键或者笔记本键盘的两指点击都可以监听到。

然后引入的是initInterval方法,该方法是用来初始化事件循环,防止用户在使用中使用其他方法打开开发者工具时页面无法得知。

export function initInterval () {
    let _pause = false;
    const pause = () => {_pause = true;};
    const goon = () => {_pause = false;};
    hackAlert(pause, goon); // 防止 alert等方法触发了debug延迟计算
    onPageShowHide(goon, pause); // 防止切后台触发了debug延迟计算
    interval = window.setInterval(() => {
        if (_pause) return;
        calls.forEach(fn => {fn(time++);
    });
        console.clear();
    }, config.interval);
    // 两秒之后判断 如果不是pc去掉定时器interval,为了优化移动端的性能
    // 如果控制面板被打开了该定时器timer会被清除
    timer = setTimeout(() => {
        if (!isPC()) {
            clearDDInterval();
        }
    }, config.stopIntervalTime);
}

其中isPc, hackAlter, onPageShowHIde是项目中封装的代码,都比较简单,我就不细说了。
简而言之这部分代码的功能就是如果是pc端打开网页,就执行事件循环检测开发者工具的运行状态。

然后就是核心部分的代码,initDetectors.

这个方法里面调用了三种方法去判断开发者工具的打开状态。

1:toString

function detector () {
    const isQQ = isQQBrowser();
    const isFF = isFirefox();
    //因为这个方法在chrome中执行有点问题,所以如果是chrome浏览器则直接返回,不再执行。
    if (!isQQ && !isFF) return;
    let lastTime = 0;
    const reg = /./;
    console.log(reg);
    reg.toString = function () {
        if (isQQ) { 
            // ! qq浏览器在控制台没有打开的时候也会触发 打开的时候会连续触发两次 使用这个来判断
            const time = new Date().getTime();
            if (lastTime && time - lastTime < 100) {
                triggerOnDevOpen(DETECTOR_TYPE.TO_STRING);
            } else {
                lastTime = time;
            }
        }else if (isFF) {    
            triggerOnDevOpen(DETECTOR_TYPE.TO_STRING);
        }
        return '';
    };
    registInterval(() => {
        console.log(reg);
    });
}

这个方法是基于console.log的工作原理去执行的。因为console.log的方法是根据不同浏览器去制定的,所以导致了不同的浏览器有不同的执行逻辑。我就以chrome举例来说吧。

chrome的控制台中console.log()是在打印对象的时候采用了懒加载的思路,在用户点击展开对象属性的时候,才会去获取该属性。但是chrome在执行console.log的时候就会执行一次reg的toString方法,这也是为什么无论这个开发者工具打开与否,如果toString方法里面和火狐浏览器一样去判断的话都会执行后面的代码。凭借着chrome的console.log的懒加载和直接执行的toString,可以进行判断,执行两次就是打开了开发者工具,执行一次就是没打开开发者工具。

当然,不同的浏览器有不同的情况,具体情况还需要在调试中不断修改。

2:defind-id

function detector () {
    const div = document.createElement('div');
    div.defineGetter('id', function () {
        triggerOnDevOpen(DETECTOR_TYPE.DEFINE_ID);
    });
    Object.defineProperty(div, 'id', {
        get: function () {
            triggerOnDevOpen(DETECTOR_TYPE.DEFINE_ID);
        },
    });
    registInterval(() => {
        console.log(div);
    });
}

通过创建一个div,并对div的id进行get监听,在控制台打印这个div的时候就可以得知开发者工具的打开状态。而且由于打印的懒加载,也可以确定如果开发者工具没有打开,div的id的get方法不会执行。

3:size

function checkWindowSizeUneven () {
    const threshold = 160;
    const widthUneven = window.outerWidth - window.innerWidth > threshold;
    const heightUneven = window.outerHeight - window.innerHeight > threshold;
    if (widthUneven || heightUneven) {
        triggerOnDevOpen(DETECTOR_TYPE.SIZE);
        return false;
    }
    return true;
}
export default function detector () {
    checkWindowSizeUneven();
    window.addEventListener('resize', () => {
        setTimeout(checkWindowSizeUneven, 100);
    }, true);
}

第三种方法是最简单的,就是当页面打开了开发者工具,开发者工具就会占据页面的一部分,同时监听resize时间,当页面尺寸有变化的时候会做出反映。160的参数可以保证下载和一些其他情况下不会误判断。

但是如果开发者工具是单独的页面这部分就很难去有一个精准的判断。而且chrome浏览器把开发者工具放到页面下方,最小的时候可以达到153像素(版本 94.0.4606.71(正式版本) (x86_64)),有可能会影响结果。

这三种是用户无感知的三种方法,还有一种用户可以感知到的方法。

!function () {
    const handler = setInterval(() => {
        const before = new Date();
        debugger;
        const after = new Date();
        const cost = after.getTime() - before.getTime();
        if (cost > 100) {
            consoleOpenCallback();
            clearInterval(handler);
        }
    }, 1000);
}();

方法比较简单,就是比较两个date的时间差,如果debugger执行了,就是打开了开发者工具。但是这个方法也会受到影响,如果用户在sources中关闭了调试模式就无法做出正确的判断了。