Skip to content

Instantly share code, notes, and snippets.

@jamesreggio
Last active September 28, 2018 08:42
Show Gist options
  • Save jamesreggio/eb17f74f8d76c401564f to your computer and use it in GitHub Desktop.
Save jamesreggio/eb17f74f8d76c401564f to your computer and use it in GitHub Desktop.
Script to help diagnose popup blocker issues
// This script will help to diagnose popup blocker issues.
// Copy-paste it into your console, or include it in your development build.
//
// When you make a call to window.open(), this script will check whether the
// most recent user input event happened on the same tick. (If not, a
// breakpoint is triggered and an explanation is written to the console.)
//
// You can manually call window.debugOpen() at any time to determine the
// whether it's safe to insert a call to window.open() in the current code.
//
// This script tracks the presence of user input events on every tick by
// attaching some capturing-phase event handlers to the document. If you're
// using a capturing-phase document event handler (highly unlikely), this will
// produce inaccurate results.
(function() {
var userEvents = [];
var activeTick = false;
// List of 'user event' types that are exempt from popup blocking.
// I couldn't find a formal listing, so this is likely incomplete.
var USER_EVENTS = ['click', 'touchstart', 'touchend'];
// If the most recent user event was within this many milliseconds, there's
// likely some unintentional asynchronicity in the code. If it's been longer,
// the developer is probably calling window.open in an inappropriate place.
var RECENCY_THRESHOLD = 1200;
window.debugOpen = function () {
if (activeTick) {
console.group('window.open should succeed');
console.log('The following user input event occurred on this tick:');
console.log(userEvents[0].event);
console.groupEnd();
} else {
console.group('window.open will be blocked');
if (userEvents[0] &&
userEvents[0].time + RECENCY_THRESHOLD > Date.now()) {
console.log('The most recent user event occurred on a prior tick:');
console.log(userEvents[0].event);
console.log(
'Find your event handler in the call stack. If you can\'t find it,'
+ ' or if the current statement is inside a callback function within'
+ ' your handler, there\'s some unintended asynchronous code running'
+ ' between your handler\'s initial invocation and the call to'
+ ' window.open (or call to a third-party library that creates a'
+ ' popup).'
);
} else {
console.log('There has not been a recent user input event.');
console.log(
'In order to avoid popup blockers, window.open must be called in the'
+ ' event handler for user input (e.g., click, touchend), not during'
+ ' a setTimeout or AJAX callback.'
);
console.log(
'If you\'re calling window.open in an AJAX callback, try performing'
+ ' the AJAX call earlier, before the click or touchend. That way,'
+ ' you can immediately call window.open (or the third-party library'
+ ' that produces a popup) in the click or touchend event handler.'
);
}
}
};
var open = window.open;
window.open = function () {
if (!activeTick) {
window.debugOpen();
// window.open was called but will be blocked,
// because there wasn't a user input event on this tick.
// Look in your console and call stack for more information.
debugger;
}
open.apply(window, arguments);
};
function userInput(e) {
if (e.isTrusted === false) {
// Events dispatched with dispatchEvent() do not qualify as user input
// and will not be able to circumvent the popup blocker.
debugger;
return;
}
activeTick = true;
userEvents.unshift({
event: e,
time: Date.now(),
});
setTimeout(function() {
activeTick = false;
});
}
USER_EVENTS.forEach(function(event) {
document.addEventListener(event, userInput, true);
});
}());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment