Levi Cowan Levi Cowan - 1 month ago 12
Javascript Question

Javascript Keybindings "Interrupted" or "Lose Page Focus" After Advertisement Loads in Chrome

I have key event bindings for the arrow keys on a web page. These bindings work perfectly as the page loads, but suddenly stop working when the Google AdSense advertisement on the page loads. This doesn't happen on every page/ad load, but on about half of them.

When this happens, if I press the arrow keys repeatedly during page load, the bound event will fire successfully until the ad appears, at which point the event stops working, and the key's default action (scrolling the page) begins to occur as I keep pressing the key. This is strange since I have disabled the default action on these keys via:

window.onkeydown = function(e) {
// Spacebar, all four arrow keys
if (e.keyCode == 32 || e.keyCode == 37 || e.keyCode == 38 || e.keyCode == 39 ||
e.keyCode == 40) {
e.preventDefault();
return false;
}
};


If I subsequently click anywhere on the page body, the key bindings will start working again.

I'm guessing that perhaps when the ad finishes loading (which is after the rest of the page), it causes some kind of interrupt that "steals focus" from the page (but apparently not the window since the scrolling still happens?).

The actual keybindings are done using Mousetrap, though that doesn't seem to have anything to do with the problem, and I have only encountered this issue in Google Chrome. I do not get this behavior in Firefox. If I enable AdBlock on Chrome, the issue does not occur, further showing it's the advertisement triggering this "interruption."

Is there anything obvious that I'm unaware of that can completely interrupt keybindings in this way, while still allowing keys to scroll the page until the user clicks in the body again? Is there any way to prevent the advertisement from disrupting the user's interactivity with the page in this way?

Answer

One suggestion was to capture the AdSense ADS_LOADED event, but this appears to be available only when using the Google IMA SDK in videos.

The solution I came up with was to listen for all blur events, and when focus is stolen from the document body by an advertisement, return focus to the window. This only works when window.focus() is wrapped in a timeout, something I currently don't understand.

The isDescendant() function was taken from this answer.

function isDescendant(child, parent) {
    /*
    Test if child is contained within parent regardless of how many levels deep
    */
     var node = child.parentNode;
     while (node != null) {
         if (node == parent) {
             return true;
         }
         node = node.parentNode;
     }
     return false;
}

window.addEventListener('blur', function() {
    var ads = document.getElementsByClassName('ad');
    var n = ads.length;
    for (i=0; i<n; i++) {
        if (isDescendant(document.activeElement, ads[i])) {
            // This only works if wrapped in a timeout (why?)
            window.setTimeout(function () {
                window.focus();
            }, 0);
            break;
        }
    }
});