How can I build a webpage which is able to monitor when the page gets the focus, especially when Safari is in the background and the user switches Safari back to the foregro
I wrote a little test page to see what events are being sent to the window on iOS.
The page is "Apple web app capable", so you can save it to the home screen and test it in standalone mode.
Here's the page: Test of Window Events
The code:
$('#btnClear').on('click', function() {
$('#olEvents').empty();
});
var eventNames = [
'load',
'focus',
'blur',
'change',
'close',
'error',
'haschange',
'message',
'offline',
'online',
'pagehide',
'pageshow',
'popstate',
'resize',
'submit',
'unload',
'beforeunload'
];
eventNames.forEach(function(eventName) {
$(window).on(eventName, function(evt) {
let now = new Date()
$('#olEvents').append('<li>' + now.toTimeString() + ' - ' + evt.type + '</li>');
// scroll to bottom div
$(window).scrollTop($('#divBottom').offset().top);
});
});
<!DOCTYPE html>
<!--
Test of Window Events - displays all the events sent to document.window, except for 'scroll'
-->
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, maximum-scale=1, minimum-scale=1, shrink-to-fit=no, user-scalable=no">
<!-- apple web app meta tags -->
<meta name="apple-mobile-web-app-title" content="WinEvents">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<link rel="apple-touch-icon" sizes="180x180" href="./testing-180x180.png">
<title>Test of Window Events</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
</head>
<body>
<div id="wrapper" class="container">
<h1>Test of Window Events</h1>
<p><button id="btnClear" class="btn btn-sm btn-outline-danger">Clear History</button></p>
<h4>Events Caught:</h4>
<div id="divEvents" style="border: 1px solid gray; min-height: 200px; padding: 2rem">
<ol id="olEvents"></ol>
</div>
<div id="divBottom" style="height: 40px"></div>
</div>
<!-- /wrapper -->
<!-- jquery-->
<script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
<script>
</script>
</body>
</html>
Depending on what you need to support, you need a variety of different techniques to detect when a page becomes visible. Variations occur due to browser vendor, browser version, OS, running within WebView/UIWebView/WKWebView, etc.
You can view which events are occurring by using this page. I have found that to detect when the page "wakes up" on all combinations I needed to register all of the following events:
I used to use webkitRequestAnimationFrame as well, but I removed that because it could cause jank (AFAIK the rendering engine does a blocking call to the main thread for it).
Things to try:
You can see which events are happening:
iOS: beware if using a timer to detect if an iOS UIWebView has gone to sleep, you need to measure the difference using new Date.getNow()
and not performance.now()
. That is because performance.now()
stops counting time when the page is put to sleep also iOS was slow to implement performance.now()... (Aside: you may be able to measure the amount of time the page was asleep for by detecting the discrepency of differences for new Date.getNow()
and performance.now()
. Look for the != on the test page).
If you are using UIWebView then there are two techniques that work (You must use UIWebViewif you support an iOS7 App). WKWebView has the visibilitychange event so workarounds are not required.
==Technique 1.
When the applicationWillEnterForeground event occurs in the app, call UIWebView stringByEvaluatingJavaScriptFromString to call your JavaScript pageAwakened().
Benefits: clean, accurate.
Downside: needs Objective-C code. Called function needs to be accessable from global scope.
==Technique 2.
Use webkitRequestAnimationFrame and detect a time lag.
Benefits: JavaScript only. Works for mobile Safari on iOS7.
Downside: ugly risk of jank and using webkitRequestAnimationFrame is a severe hack.
// iOS specific workaround to detect if Mobile App comes back to focus. UIWebView and old iOS don't fire any of: window.onvisibilitychange, window.onfocus, window.onpageshow
function iosWakeDetect() {
function requestAnimationFrameCallback() {
webkitRequestAnimationFrame(function() {
// Can't use timestamp from webkitRequestAnimationFrame callback, because timestamp is not incremented while app is in background. Instead use UTC time. Also can't use performance.now() for same reason.
var thisTime = (new Date).getTime();
if (lastTime && (thisTime - lastTime) > 60000) { // one minute
// Very important not to hold up browser within webkitRequestAnimationFrame() or reference any DOM - zero timeout so shoved into event queue
setTimeout(pageAwakened, 0);
}
lastTime = thisTime;
requestAnimationFrameCallback();
});
}
var lastTime;
if (/^iPhone|^iPad|^iPod/.test(navigator.platform) && !window.indexedDB && window.webkitRequestAnimationFrame) { // indexedDB sniff: it is missing in UIWebView
requestAnimationFrameCallback();
}
}
function pageAwakened() {
// add code here to remove duplicate events. Check !document.hidden if supported
};
window.addEventListener('focus', pageAwakened);
window.addEventListener('pageshow', pageAwakened);
window.addEventListener('visibilitychange', function() {
!document.hidden && pageAwakened();
});
Since the problem is with mobile safari and it supports popstate event, you can use this event to detect when the user is back
The focus and blur events on the window are good to detect if the browser is going to or back from background. This worked for me on iOS8 Safari:
window.addEventListener("focus", function(evt){
console.log('show');
}, false);
window.addEventListener("blur", function(evt){
console.log('hide');
}, false);
I believe timers (setInterval()) are suspended when the app enters the background. You could do something like:
var lastFired = new Date().getTime();
setInterval(function() {
now = new Date().getTime();
if(now - lastFired > 5000) {//if it's been more than 5 seconds
alert("onfocus");
}
lastFired = now;
}, 500);
You may need to adjust those time intervals to suite your needs.
But, most likely, if it has been long enough to need a refresh (a few days) safari will probably reload the page because it is out of memory.
Use the pageshow
and pagehide
events.
<script type="text/javascript">
window.addEventListener("pageshow", function(evt){
alert('show');
}, false);
window.addEventListener("pagehide", function(evt){
alert('hide');
}, false);
</script>
https://developer.apple.com/library/mac/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html