Skip to content

Instantly share code, notes, and snippets.

@tannerlinsley
Last active January 30, 2024 09:37
Show Gist options
  • Star 28 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save tannerlinsley/1d3a2122332107fcd8c9cc379be10d88 to your computer and use it in GitHub Desktop.
Save tannerlinsley/1d3a2122332107fcd8c9cc379be10d88 to your computer and use it in GitHub Desktop.
A utility function to detect window focusing without false positives from iframe focus events
type State = {
added: boolean;
interval: false | ReturnType<typeof setInterval>;
inFrame: boolean;
callbacks: Array<SetFocusedCallback>;
};
type EnrichedHTMLIFrameElement = HTMLIFrameElement & { ___onWindowFocusHandled: boolean };
type SetFocusedCallback = (focused?: boolean) => void;
const state: State = {
added: false,
interval: false,
inFrame: false,
callbacks: [],
};
export const onWindowFocus = (newCallback: SetFocusedCallback) => {
state.callbacks.push(newCallback);
start();
return () => {
state.callbacks = state.callbacks.filter(
(registeredCallback) => registeredCallback !== newCallback
);
stop();
};
};
const runIFrameCheck = () => {
const iframes = Array.from(document.getElementsByTagName('iframe'));
console.debug('Polling iframes... found: ', iframes.length);
(iframes as EnrichedHTMLIFrameElement[]).forEach((iframe) => {
if (iframe.___onWindowFocusHandled) {
return;
}
iframe.___onWindowFocusHandled = true;
iframe.addEventListener('touchend', () => {
state.inFrame = true;
});
iframe.addEventListener('mouseup', () => {
state.inFrame = true;
});
iframe.addEventListener('focus', () => {
state.inFrame = true;
});
});
};
const start = () => {
if (state.interval) {
clearInterval(state.interval);
}
if (!state.added) {
state.added = true;
window.addEventListener('focus', () => {
if (state.inFrame) {
state.inFrame = false;
return;
} else {
state.callbacks.forEach((callback) => callback(true));
}
});
}
state.interval = setInterval(runIFrameCheck, 500);
};
const stop = () => {
if (!state.callbacks.length && state.interval) {
clearInterval(state.interval);
}
};
@sudazzle
Copy link

sudazzle commented Nov 15, 2022

const start = () => {
  if (state.interval) {
    clearInterval(state.interval)
  }

  if (!state.added) {
    state.added = true
    window.addEventListener('focus', () => {
      if (state.inFrame) {
        state.inFrame = false
        state.callbacks.forEach((callback) => callback(true))
      } else {
        state.callbacks.forEach((callback) => callback(false))
      }
    })
  }

  state.interval = setInterval(runIFrameCheck, 1000)
}

I tweaked above code a bit and it works on react-query v2.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment