問題描述
當選項卡不活動時,保持 JS/jQuery 代碼在 Safari 中工作 (Keep the JS/jQuery code working in Safari when the tab is not active)
我有一個如下所示的 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 中,它按預期工作。
參考解法
方法 1:
You can replace counter (it counts seconds) with calculating time difference.
let lastActivity = new Date();
let logoutAfter = 3600;
...
let delta = (new Date()).getTime() ‑ lastActivity.getTime();
if (delta > logoutAfter) {
...
}
P.S. So it must work even if the script itself is frozen when tab is inactive. Interval handler will be called at the moment when user activate this tab.
方法 2:
This approach will not work properly with multiple tabs opened. If user open new tab and started working in it, the earlier tab will logout the user as he is not active in that tab.
To overcome this, I will suggest to check the last active time from server using ajax call instead of doing it with javascript only.
方法 3:
According to this very thorough (but old) answer, setInterval()
execution on inactive tabs is limited to max 1/s, on both Safari and Chrome ‑ but not stopped. There are also plenty of questions here on SO about Javascript getting paused or de‑prioritised on inactive tabs, some of which include solutions:
- How can I make setInterval also work when a tab is inactive in Chrome?
- iOS 5 pauses JavaScript when tab is not active
- Safari JavaScript setTimeout stops when minimized
- Chrome: timeouts/interval suspended in background tabs?
According to your report, JS is not running at all in the inactive tab. In that case, it can happen that the tab stays in the logged‑in state, long past the time the user should have been logged out. However, assuming JS starts up again when you switch back the tab, the very first iteration of the loop will correctly calculate the time elapsed. If it is greater than your logout period, you will be logged out. So even though the tab stayed logged in longer than it should have, the user can't use it, since as soon as they switch to it they will be logged out. Note that "as soon" actually means "within 1 second plus the time it takes for the AJAX query to successfully log the user out".
In my testing, JS does not stop in an inactive Safari tab, but slows right down. In this case, it would mean that the user would be automatically logged out on the inactive tab, though not right at the time they should be. If the loop runs say every 8s, it could mean that the user would be logged out up to 7s later than they should have been. If iterations slow down even more, the delay can potentially be even more. Assuming JS starts up again as normal as soon as the user switches back the tab, behaviour will be exactly as above, the first iteration in that case will log them out.
</ul>
Probably the best option to do what you are trying is to use Web workers:
Web Workers are a simple means for web content to run scripts in background threads. The worker thread can perform tasks without interfering with the user interface.
</blockquote>There is an example of how to do that in an answer to one of the questions above.
But there is also a much simpler option, though you should evaluate if it is safe considering you are relying on this to log users out.
My testing of your code reflects the question I linked to earlier which describes
setInterval()
being slowed, but not stopped. For me, Safari (v 13.1, macOS 10.14.6) does not actually fully pause Javascript, but slows down execution of the loop, by increasing amounts. I see this by opening the dev console, and watching the output of theconsole.log(delta)
messages ‑ they slow right down, first running only every 2s, then 4s, and so on, though sometimes faster. But they do not stop.That output also gives a hint about the problem, and the solution. The
delta
values shown on the console do not represent the real time difference sincelastActivity
. They are just incrementing numbers. If you see adelta
value appear on the console 10 seconds after the last one, it should logically be+10
, right? But it is not, it is just one higher.And that's the problem here ‑ the code is not counting the true time difference, it is just counting iterations of the loop:
let timer = setInterval(function () { now++; // <‑‑ problem
This code correctly sets
now
to the current time only ifsetInterval()
runs exactly every second. But we know that when the tab is inactive, it does not. In that case it is just counting the number of times the loop runs, which has no relation to the real time elapsed.To solve this problem, we have to determine
now
based on the real time. To do that, let's switch to using JS to calculate our timestamps (PHP is rendered only once, on page load, so if you use it inside the loop it will just stay fixed at the initial value):// 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);
Now, even if there is a pause of 10s between iterations,
delta
will be the true measure of time elapsed since the page was loaded. So even if the user switches away to another tab, every time the loop runs, it will correctly track time, even if it doesn't happen every second.So what does this mean in your case?
EDIT
Here's simplified, complete code, and 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); });
(by flash、vdem、Akshay Vanjare、Don't Panic)
參考文件