通过内容脚本设置的间隔被网页清除

Interval set through content script being cleared by webpage

在我的 chrome 扩展程序中,我在内容脚本中有一个 setInterval,它每 3 秒检查一次网页更改。

setInterval(detectChange, 3000)

function detectChange(){
    ...
}

除了一个 (www.rdio.com) 之外,这对所有网站都非常有效。网页脚本以某种方式清除了通过内容脚本设置的间隔。

我想到将 setInterval 放在后台脚本中,并在每个时间间隔向内容脚本发送一条消息。但这需要我跟踪内容脚本为 运行 的所有选项卡,这似乎不是一个好主意。

如果有办法请告诉我。

可取消任务调度程序(setTimeoutsetIntervalrequestAnimationFrame 等)显然与文档相关联。尽管内容脚本的脚本执行上下文与页面隔离,但文档却不是。

站点清除并非由站点本身创建的计时器似乎很奇怪。您可以尝试调试问题,并通过覆盖 clearTimeout / clearInterval 方法来检查站点为何完全清除计时器。

下面是一个捕获代码的示例,该代码清除了脚本本身未安装的计时器:

// Run this content script at document_start
var s = document.createElement('script');
s.textContent = '(' + function() {
    var clearTimeout = window.clearTimeout;
    var setTimeout = window.setTimeout;
    var setInterval = window.setInterval;
    // NOTE: This list of handles is NEVER cleared, because it is the
    // only way to keep track of the complete history of timers.
    var handles = [];
    window.setTimeout = function() {
        var handle = setTimeout.apply(this, arguments);
        if (handle) handles.push(handle);
        return handle;
    };
    window.setInterval = function() {
        var handle = setInterval.apply(this, arguments);
        if (handle) handles.push(handle);
        return handle;
    };
    window.clearTimeout = window.clearInterval = function(handle) {
        clearTimeout(handle);
        if (handle && handles.indexOf(handle) === -1) {
            // Print a stack trace for debugging
            console.trace('Cleared non-owned timer!');
            // Or trigger a breakpoint so you can follow the call
            // stack to identify which caller is responsible for
            // clearing unknown timers.
            debugger;
        }
    };
} + ')();';
(document.head || document.documentElement).appendChild(s);
s.remove();

如果这表明该站点存在错误,并且(例如)清除了每个偶数编号的计时器,那么您只需调用 setTimeout 两次即可解决问题。

例如:

Promise.race([
    new Promise(function(resolve) {
        setTimeout(resolve, 3000);
    }),
    new Promise(function(resolve) {
        setTimeout(resolve, 3000);
    });
}).then(function() {
    // Any of the timers have fired
});

如果一切都失败了...

如果发现站点以不可预知的方式清除计时器,您可以尝试使用其他异步方法或事件来安排任务,并测量调用之间的时间。当一定时间过去后,只需触发您的回调。例如,使用requestAnimationFrame(通常每秒调用几次):

function scheduleTask(callback, timeout) {
    timeout = +timeout || 0;
    var start = performance.now();
    function onDone(timestamp) {
        if (timestamp - start >= timeout) callback();
        else requestAnimationFrame(onDone);
    }
    requestAnimationFrame(onDone);
}

// Usage example:
console.time('testScheduler');
scheduleTask(function() {
    console.timeEnd('testScheduler');
}, 1000);

或者插入一个 <iframe> 并在框架的上下文中创建计时器。