Keep the JS/jQuery code working in Safari when the tab is not active
Posted
技术标签:
【中文标题】Keep the JS/jQuery code working in Safari when the tab is not active【英文标题】: 【发布时间】:2020-11-11 11:32:26 【问题描述】:我有一个如下所示的 JS/jQuery 代码,其中我希望在会话选项卡不活动时保持 JS/jQuery 代码工作。
以下代码在 Google Chrome 中完全正常,但在 Safari 中不起作用。
jQuery(document).ready(function ($)
let lastActivity = <?php echo time(); ?>; // Line A
let now = <?php echo time(); ?>;
let logoutAfter = 3600; // page will logout after 1800 seconds if there is no activity
let userName = "<?php echo $_SESSION['user_name']; ?>";
let timer = setInterval(function ()
now++;
let delta = now - lastActivity;
console.log(delta); // Line A
if (delta > logoutAfter)
clearInterval(timer);
//DO AJAX REQUEST TO close.php
$.ajax(
url: "/control/admin.php",
type: 'GET', // GET also fine
data: action: 'logout', user_name: userName,
success: function (data)
window.location.href = "admin.php";
,
error: function (jqXHR, textStatus, errorThrown)
alert(textStatus);
);
, 1000); //<-- you can increase it( till <= logoutAfter ) for better performance as suggested by @"Space Coding"
);
Line A
的值在标签页处于非活动状态时不会在 Safari 中增加,但在 Google Chrome 中可以正常工作。在 Google Chrome 中,它按预期工作。
【问题讨论】:
在您的站点上加载任何其他 js 之前包含此脚本就足够了:github.com/turuslan/HackTimer/blob/master/HackTimer.min.js(我在这里找到:***.com/questions/5927284/…) 明确地说,是桌面 Safari 无法正常工作还是移动 (iPad/iPhone) Safari? @ChrisHaas 其桌面 safari 无法正常运行。 【参考方案1】:您可以用计算时间差来替换计数器(它计算秒数)。
let lastActivity = new Date();
let logoutAfter = 3600;
...
let delta = (new Date()).getTime() - lastActivity.getTime();
if (delta > logoutAfter)
...
附:因此,即使在选项卡处于非活动状态时脚本本身被冻结,它也必须工作。用户激活此选项卡时将调用间隔处理程序。
【讨论】:
您好,您已在let lastActivity = <?php echo time(); ?>; // Line A
这一行进行了更改?我不能在该行中进行更改,因为它存储了最后一个活动。
@flash 好吧,就和以前一样, - 我认为应该也可以。
@flash 忘了说,把 lastActivity.getTime() 换成 lastActivity
这个jsfiddle.net/zsr8nd4p 是我尝试过的,但它仍然无法在 safari 中使用。让我知道我需要在小提琴中进行哪些更改才能使其正常工作。【参考方案2】:
此方法在打开多个选项卡时无法正常工作。如果用户打开新标签并开始在其中工作,则较早的标签将注销用户,因为他在该标签中不活动。
为了克服这个问题,我建议使用 ajax 调用检查服务器的最后活动时间,而不是仅使用 javascript。
【讨论】:
我认为这将是另一个问题。我想知道您是否知道我上面的代码在 safari 中不起作用的任何原因。 在 WebExtension 背景页面中,计时器无法正常工作。这是因为后台页面实际上并不是一直处于加载状态:如果不使用它们,浏览器可以卸载它们,并在需要时恢复它们。也可能有其他原因,请参考链接:developer.mozilla.org/en-US/docs/Web/API/… 你也可以使用这个:***.com/a/5927432/1808009【参考方案3】:根据this very thorough (but old) answer,setInterval()
在 Safari 和 Chrome 上的非活动选项卡上的执行限制为最大 1/s - 但不会停止。这里还有很多关于 Javascript 在非活动选项卡上暂停或取消优先级的问题,其中一些包括解决方案:
可能最好的选择是use Web workers:
Web Workers 是一种让 Web 内容在后台线程中运行脚本的简单方法。工作线程可以在不干扰用户界面的情况下执行任务。
在上述问题之一的答案中有an example of how to do that。
但还有一个更简单的选择,但考虑到您依赖此方法来注销用户,您应该评估它是否安全。
我对您的代码的测试反映了the question I linked to earlier,它描述了setInterval()
正在减慢,但并未停止。对我来说,Safari(v 13.1,macOS 10.14.6)实际上并没有完全暂停 Javascript,而是通过增加数量来减慢循环的执行速度。我通过打开开发控制台并观察console.log(delta)
消息的输出来看到这一点 - 它们会变慢,首先每 2 秒运行一次,然后每 4 秒运行一次,依此类推,尽管有时速度更快。但他们并没有停下来。
该输出还提供了有关问题和解决方案的提示。控制台上显示的delta
值不代表自lastActivity
以来的实际 时间差。他们只是增加数字。如果您在最后一个值 10 秒后看到控制台上出现delta
值,那么它在逻辑上应该是+10
,对吧?但事实并非如此,它只是更高一级。
这就是问题所在——代码没有计算真正的时间差,它只是计算循环的迭代次数:
let timer = setInterval(function ()
now++; // <-- problem
此代码正确地将now
设置为当前时间仅如果setInterval()
每秒运行一次。但我们知道,当标签处于非活动状态时,它不会。在这种情况下,它只是计算循环运行的次数,与实际经过的时间无关。
为了解决这个问题,我们必须根据实时确定now
。为此,让我们切换到使用 JS 来计算我们的时间戳(PHP 仅在页面加载时呈现一次,因此如果您在循环中使用它,它将保持在初始值不变):
// Note that JS gives us milliseconds, not seconds
let lastActivity = Date.now();
let now = Date.now();
let logoutAfter = 3600 * 1000;
let timer = setInterval(function ()
// PHP won't work, time() is rendered only once, on page load
// let now = <?php echo time(); ?>;
now = Date.now();
let delta = now - lastActivity;
console.log('New timer loop, now:', now, '; delta:', delta);
现在,即使迭代之间有 10 秒的暂停,delta
也将是自页面加载以来经过的时间的true 度量。因此,即使用户切换到另一个选项卡,每次循环运行时,它都会正确跟踪时间,即使它不是每秒都发生。
那么这对你来说意味着什么?
根据您的报告,JS 根本没有在非活动选项卡中运行。在这种情况下,选项卡可能会一直处于登录状态,而用户应该已经注销了很久。但是,假设当您切换回选项卡时 JS 再次启动,循环的第一次迭代将正确计算经过的时间。如果它大于您的注销期限,您将被注销。因此,即使选项卡的登录时间超过了应有的时间,用户也无法使用它,因为一旦切换到它,他们就会被注销。请注意,“尽快”实际上是指“在 1 秒内加上 AJAX 查询成功注销用户所需的时间”。
在我的测试中,JS 不会在不活动的 Safari 选项卡中停止,而是会减慢速度。在这种情况下,这意味着用户将在非活动选项卡上自动注销,尽管不是在他们应该在的时间。如果循环每 8 秒运行一次,则可能意味着用户将在 7 秒后注销。如果迭代速度更慢,延迟可能会更大。假设一旦用户切换回选项卡,JS 会再次正常启动,行为将与上述完全相同,在这种情况下,第一次迭代会将它们注销。
编辑
这里是简化的完整代码和a JSFiddle showing it running and working。
jQuery(document).ready(function($)
let lastActivity = Date.now();
let now = Date.now();
let logoutAfter = 3600 * 1000;
let timer = setInterval(function()
now = Date.now();
let delta = now - lastActivity;
console.log('New timer loop, now:', now, '; delta:', delta);
if (delta > logoutAfter)
alert('logout!');
, 1000);
);
【讨论】:
感谢您的详细解释。我将您的代码复制粘贴到fiddledelta
的值在控制台中始终为 0,now
打印当前时间戳,但在控制台上计时器的值继续,除非有活动或用户刷新页面。
我想知道是否有任何方法可以检查当前时间戳何时比用户的最后一次活动大 3600 秒。我希望在当前时间戳比上次活动大 3600 秒时发生特定操作。
@flash 您的小提琴缺少我对您的代码所做的唯一真正更改-确定now
的行! :-) now = Date.now();
。无需更改 now
,是的,您只需在每个循环中减去相同的 2 个数字。 Here's a (simplified) JSFiddle of my code, working。我也将它添加到问题中。
@flash 这有意义吗?
@flash 我会让你知道我现在遇到了什么问题。以上是关于Keep the JS/jQuery code working in Safari when the tab is not active的主要内容,如果未能解决你的问题,请参考以下文章
You have to specify ‘-keep‘ options for the shrinking step
vim - save current file with a new name but keep editing current file
UVA 1153 Keep the Customer Satisfied