Identifying window focus/blur events with iframes - javascript

I'm trying to reliably identify when a browser window/tab is activated and deactivated. Normally, window's focus and blur events would do, but the document contains several iframes.
When an iframe is focused, the main window gets unfocused and vice versa, so we have the following possibilities of focus events [(none) means the window/tab is deactivated]:
current focus new focus events
----------------------------------------------------------------------
window (none) window:blur
window iframe window:blur + iframe:focus
iframe (none) iframe:blur
iframe window iframe:blur + window:focus
iframe another iframe iframe:blur + iframe:focus
(none) window window:focus
(none) iframe iframe:focus
It is no problem to register all of these events, as shown by this fiddle. But whenever we switch from the main window to an iframe or vice versa, or between two iframes, the respective blur and focus events both fire; and they fire with a small delay at that.
I am worried about the concurrency here, since the blur handler could go and start doing stuff, but it should have never started because the user actually just switched focus somewhere in between the frames.
Example: A page should do some AJAX requests periodically whenever it is currently not active. That is, it should start requesting whenever the user deactivates the tab and stop requesting as soon as it's activated again. So we bind a function to the blur event that initiates the requests. If the user just clicks on another iframe, blur, and shortly after that, focus is triggered. But the blur handler already fires away, making at least one request before it can be stopped again.
And that's my problem: How can I reliably detect when a user actually (de-)activates a browser window containing iframes, without risking to get a false alarm caused by two immediate blur and focus events?
I wrote a half-baked solution that uses a timeout after a blur event in order to determine if there was an immediate focus event after it (fiddle):
var active = false,
timeout = 50, // ms
lastBlur = 0,
lastFocus = 0;
function handleBlur() {
if (lastBlur - lastFocus > timeout) {
active = false;
}
}
function handleFocus() {
if (lastFocus - lastBlur > timeout) {
active = true;
}
}
$(window).on('focus', function () {
lastFocus = Date.now();
handleFocus();
}).on('blur', function () {
lastBlur = Date.now();
window.setTimeout(handleBlur, timeout);
});
$('iframe').each(function () {
$(this.contentWindow).on('focus', function () {
lastFocus = Date.now();
handleFocus();
}).on('blur', function () {
lastBlur = Date.now();
window.setTimeout(handleBlur, timeout);
});
});
But I believe this could be very problematic, especially on slower machines. Increasing the timeout is also not acceptable to me, 50 ms is really my pain threshold.
Is there a way that doesn't depend on the client to be fast enough?

you could poll for the document.hasFocus() value, which should be true if either an iframe or the main window are focused
setInterval(function checkFocus(){
if( checkFocus.prev == document.hasFocus() ) return;
if(document.hasFocus()) onFocus();
else onBlur();
checkFocus.prev = document.hasFocus();
},100);
function onFocus(){ console.log('browser window activated') }
function onBlur(){ console.log('browser window deactivated') }

I was trying to do it without polling, but the iframe doesn't fire an onblur event (if the browser window is deactivated when the iframe was on focus, I get no events fired), so I ended up needing polling for half of it anyway, but maybe someone can figure something out with this code
function onFocus(){ console.log('browser window activated'); }
function onBlur(){ console.log('browser window deactivated'); }
var inter;
var iframeFocused;
window.focus(); // I needed this for events to fire afterwards initially
addEventListener('focus', function(e){
console.log('global window focused');
if(iframeFocused){
console.log('iframe lost focus');
iframeFocused = false;
clearInterval(inter);
}
else onFocus();
});
addEventListener('blur', function(e){
console.log('global window lost focus');
if(document.hasFocus()){
console.log('iframe focused');
iframeFocused = true;
inter = setInterval(()=>{
if(!document.hasFocus()){
console.log('iframe lost focus');
iframeFocused = false;
onBlur();
clearInterval(inter);
}
},100);
}
else onBlur();
});

Related

Checking Page Visibility and Page Focus for a page

I want to make sure when a user is on the page.
Hence, when a user clicks on another window (looses focus) or changes tab, I should stop playing video on my page.
The problem is trying to do both simultaneously.
For example, through this JS plugin (JQuery Visbility), I am able to check whether the tab/window of my page is open.
Here's how it's doing it:
$(document).on({
'show': function() {
console.log('The page gained visibility; the `show` event was triggered.');
},
'hide': function() {
console.log('The page lost visibility; the `hide` event was triggered.');
}
});
But it can't detect whether the page has focus or not. For example, the page might be open, but I may be opening another window separately and keeping my focus there.
The following code takes care of that (taken from here):
function check()
{
if(document.hasFocus() == lastFocusStatus) return;
lastFocusStatus = !lastFocusStatus;
statusEl.innerText = lastFocusStatus ? 'with' : 'without';
}
window.statusEl = document.getElementById('status');
window.lastFocusStatus = document.hasFocus();
check();
setInterval(check, 200);
Now, I am trying to do both simultaneously. Is it possible?
You can add event listeners for the window's focus and blur events.
var hasFocus = true;
$(window).focus(function(){
hasFocus = true;
});
$(window).blur(function(){
hasFocus = false;
});
//check the hasFocus variable to see if the window has focus

Page visibility API in Google Chrome

When using the page visibility API in Google Chrome the event is fired twice.
This is some thing that happen only in Chrome.
document.addEventListener('visibilitychange', function(e) {
if (!document.hidden) {
console.log(e);
}
});
By what I know of the API the event is suppose to fire once.
I am trying to fire it once regardless the browser.
You have to do two different methods to detect browser window and browser tab.
For a cross-browser solution check out this example:
Using HTML5 Visibility API to manage the focus of browser tabs and windows:
For detecting if the browser tab is active or not, use the HTML5 Visibility API:
/////////////////////////////////////////
// main visibility API function
// check if current tab is active or not
var vis = (function(){
var stateKey,
eventKey,
keys = {
hidden: "visibilitychange",
webkitHidden: "webkitvisibilitychange",
mozHidden: "mozvisibilitychange",
msHidden: "msvisibilitychange"
};
for (stateKey in keys) {
if (stateKey in document) {
eventKey = keys[stateKey];
break;
}
}
return function(c) {
if (c) document.addEventListener(eventKey, c);
return !document[stateKey];
}
})();
/////////////////////////////////////////
// check if current tab is active or not
vis(function(){
if(vis()){
// the setTimeout() is used due to a delay
// before the tab gains focus again, very important!
setTimeout(function(){
// browser tab gains focus
// code goes here
},300);
} else {
// browser tab gains focus
// code goes here
}
});
For detecting the browser window you just check the window blur, focus, focusin, and focusout depending on what browser it is:
/////////////////////////////////////////
// check if browser window has focus
var notIE = (document.documentMode === undefined),
isChromium = window.chrome;
if (notIE && !isChromium) {
// checks for Firefox and other NON IE Chrome versions
$(window).on("focusin", function () {
setTimeout(function(){
// browser window gains focus
// code goes here
},300);
}).on("focusout", function () {
// browser window loses focus
// code goes here
});
} else {
// checks for IE and Chromium versions
if (window.addEventListener) {
// bind focus event
window.addEventListener("focus", function (event) {
// the timeout is due to a slight delay when a browser tab regains focus
setTimeout(function(){
// browser window gains focus
// code goes here
},300);
}, false);
// bind blur event
window.addEventListener("blur", function (event) {
// browser window loses focus
// code goes
}, false);
} else {
// bind focus event
window.attachEvent("focus", function (event) {
// the timeout is due to a slight delay when a browser tab regains focus
setTimeout(function(){
// browser window gains focus
// code goes here
},300);
});
// bind focus event
window.attachEvent("blur", function (event) {
// browser window loses focus
// code goes here
});
}
}
Resources:
MDN - visibilitychange
MDN - Using_the_Page_Visibility_API
Working example of the above code: http://codepen.io/jonathan/pen/sxgJl
I also converted this into a jQuery plugin if needed. You can download it on the GreenSock GSAP Forum, at this link here.
Or download the TabWindowVisibilityManager jQuery Plugin directly.
I hope you find this helpful!
Looks like it is a bug in Chrome:
https://code.google.com/p/chromium/issues/detail?id=409467
The workaround seems to be to attach to the window:
Workaround: use window to attach the event to. This seems
non-standard.

IE doesn't execute my code 'onunload'

I need to execute some code before leaving the browser, I implemented this:
window.onbeforeunload = function () { return 'Are you sure?'; };
window.onunload = function () { releaseLocking(); };
It works pretty well with Google Chrome. When closing GC: a message is shown,
if I click on the button 'stay on the page', nothing will happen. Perfect.
if I click on the button 'leave the page', the code under releaseLocking will be executed. Perfect.
I have problems with Internet Explorer.
if I click on the button 'stay on the page', nothing will happen. Perfect.
if I click on the button 'leave the page', the code under releaseLocking won't get executed.
Any idea?
I searched a lot but didnt found a solution.
Thanks.
UPDATE
var releaseLocking = function() {
// Release only if lock is set on the current user
if (transport().lockedById() == 5) { // 5 is dummy (Jean Dupont for testing)
transport().lockedById(null);
transport().lockedTime(null);
return ctxTransport.saveChanges(SILENTSAVE);
}
};
It's seems IE have bug where the unload event wouldn't fire on a specific page on a site.
the unload event never fired since all the contents of the page hadn't finished loading before it's navigated to another page.
try stopping it before you unload
try this:
function fixUnload() {
// Is there things still loading, then fake the unload event
if (document.readyState == 'interactive') {
function stop() {
// Prevent memory leak
document.detachEvent('onstop', stop);
// Call unload handler
unload();
};
// Fire unload when the currently loading page is stopped
document.attachEvent('onstop', stop);
// Remove onstop listener after a while to prevent the unload function
// to execute if the user presses cancel in an onbeforeunload
// confirm dialog and then presses the stop button in the browser
window.setTimeout(function() {
document.detachEvent('onstop', stop);
}, 0);
}
};
function unload() {
alert('Unload event occured.');
};
window.attachEvent('onunload', unload);
window.attachEvent('onbeforeunload', fixUnload);
These types of events are very unreliable.. what if the user's browser crashes (lol ie!) I was able to achieve the same outcome by using a heartbeat and polling the server every 20-30 seconds (or longer idk what your duration may be) and using that to handle the exiting event.. I had a locking mechanism in this app too.
Good luck.

JavaScript: clear setInterval on blur, and resume on focus

I have a very basic slideshow cycling some div containers. This is my code:
function cycle(){
var $first = $('.panes .pane1');
var $active = $('.panes .active');
var $next = $active.next();
if($next.length != 0){
$active.removeClass('active').addClass('inactive');
$('.panes').animate({left: "-=430px"}, 400);
setTimeout(function(){
$next.removeClass('inactive').addClass('active');
}, 400);
} else {
$active.removeClass('active').addClass('inactive');
$('.panes').animate({left: "0px"}, 400);
setTimeout(function(){
$first.removeClass('inactive').addClass('active');
}, 400);
}
}
$(window).blur(function(){window.clearInterval(cycling)});
$(window).focus(function(){cycling = setInterval('cycle()', 5001);});
Then in my document.ready function, I'm calling the above with cycling = setInterval('cycle()', 5001);
When the page become inactive, the setInterval should clear, then resume when the page regains focus. This works correctly in Chrome, but not IE. I'm not really concerned about IE because it doesn't need to be cleared there.
In Firefox, with the above code, the div container just switches back and forth with each interval, or sometimes it slides to the left twice as far as it is supposed to.
When I remove the $(window).focus(function(){cycling = setInterval('cycle()', 5001);}); line, then it behaves correctly with the exception of that it does not resume when the page regains focus.
What is causing this and how do I resolve the issue?
You are calling setInterval twice, once on focus and once on document ready. The blur handler only clears whichever of those happened second. So the cycle() function gets called twice as often as you expect.
One way to get around that is to initialise cycling to null, assign cycling = null in the blur handler, and then in the ready and focus handlers only create a new interval if cycling === null.
Another way is to only call setInterval from document ready and just leave it running forever (i.e. until the user navigates away from your page), but add an "infocus" flag that you set and clear from the focus and blur handlers:
var infocus = false;
$(window).focus(function(){ infocus = true; })
.blur(function (){ infocus = false; });
Then check the flag:
function cycle() {
if (!infocus)
return;
// rest of your code here...
}

Closing a pop up window when it loses focus

I am wondering if it is possible to create a pop up window with javascript, and then close that window when it loses focus.
Here is my code:
var theWindow;
function launchWindow() {
theWindow=window.open('www.domain.com');
theWindow.onblur = function() {
this.close();
};
}
But that doesn't work at all. Any suggestions?
EDIT: I have discovered a solution that works for me, hopefully it will work for someone else:
var theWindow;
var windows = [];
function launchWindow() {
theWindow=window.open('www.domain.com');
windows.push(theWindow);
window.onfocus = function() {
for (x in windows) {
windows[x].close();
}
};
}
It's not an exact solution to my original problem (It doesn't close the window when it loses focus, but rather it closes it when the main window regains focus) but it works for me.
Is the URL of the popup window from the same domain as the parent? If not, you will likely not be able to attach an event to the new window's onblur event.
Run this from your browser console while viewing StackOverflow to see that it does in fact work when the popup is on the same domain as the originating window:
var theWindow = window.open ("http://www.stackoverflow.com","theWindow");
theWindow.onblur = function() { this.close(); };
window does not have the onblur event
Try to call it's closing by focusing on the <body> of the main window
The problem you may be having is that you are binding the onblur handler before the window has begun loading the page. Once the page is loaded, your onblur handler is gone. You'll need to defer binding long enough to give the page a chance to start loading before binding your event handler:
function launchWindow() {
var theWindow = window.open('www.domain.com');
setTimeout(function() {
theWindow.onblur = function() {
this.close();
};
}, 2000);
}
If you are loading a page in a different domain, you won't be able to bind the onblur handler at all. You'll need to stick to your solution using onfocus.

Categories

Resources