4

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?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
MCL
  • 3,985
  • 3
  • 27
  • 39
  • what about changing blur with focusout? https://api.jquery.com/focusout/ – Cristo Jun 23 '16 at 17:58
  • And if you only need to do that when the window focus is off, why involve 'iframe' in the event, not just window? – Cristo Jun 23 '16 at 18:05
  • @Cristo `focusout` and `blur` only differ in the fact that `focusout` supports event bubbling. That is, a focus loss of parent elements will be registered, too. However, the own `window` is the limit to event bubbling, rendering `focusout` identical to `blur` in this case. Check out [this fiddle](https://jsfiddle.net/oLzah7p2/). If the focus switches from the main window (around the iframes) to an iframe, the focus of the main window will be lost, even with `focusout`. That does not correpond to my requirement, since the focus is still on the page (just in an iframe of that page). – MCL Jun 24 '16 at 10:03
  • I thought maybe blur was bubbling when you trigger iframe.blur and this bubbling up to window, causing it to trigger twice. I see your question better in the last fiddle. window.focusout triggers when clicking from iframes to window – Cristo Jun 24 '16 at 18:25

2 Answers2

2

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') }
aljgom
  • 7,879
  • 3
  • 33
  • 28
1

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();
});
aljgom
  • 7,879
  • 3
  • 33
  • 28
  • Thanks, that helped a lot. However, in order to also get the "onFocus()" function also to work then the situation is "iFrame focused, leaving tab, returning to tab" I had to add window.focus(); in the interval before onBlur(). – Christian Rauchenwald May 01 '21 at 11:08