diff --git a/src/content.ts b/src/content.ts index 4f87f39a..e839f359 100644 --- a/src/content.ts +++ b/src/content.ts @@ -92,8 +92,8 @@ const playerButtons: Record Config.config !== null, 1000, 1).then(() => videoIDChange(getYouTubeVideoID(document))); -// wait infinitely for hover preview -utils.wait(() => getHoverPreview(), 0, 500).then(() => refreshVideoAttachments()) +// wait for hover preview to appear, and refresh attachments if ever found +window.addEventListener("DOMContentLoaded", () => utils.waitForElement(".ytp-inline-preview-ui").then(() => refreshVideoAttachments())); addPageListeners(); addHotkeyListener(); diff --git a/src/utils.ts b/src/utils.ts index 29a3b382..e3c612bc 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -21,6 +21,10 @@ export default class Utils { "popup.css" ]; + /* Used for waitForElement */ + waitingMutationObserver:MutationObserver = null; + waitingElements: { selector: string, callback: (element: Element) => void }[] = []; + constructor(backgroundScriptContainer: BackgroundScriptContainer = null) { this.backgroundScriptContainer = backgroundScriptContainer; } @@ -29,6 +33,41 @@ export default class Utils { return GenericUtils.wait(condition, timeout, check); } + /* Uses a mutation observer to wait asynchronously */ + async waitForElement(selector: string): Promise { + return await new Promise((resolve) => { + this.waitingElements.push({ + selector, + callback: resolve + }); + + if (!this.waitingMutationObserver) { + this.waitingMutationObserver = new MutationObserver(() => { + const foundSelectors = []; + for (const { selector, callback } of this.waitingElements) { + const element = document.querySelector(selector); + if (element) { + callback(element); + foundSelectors.push(selector); + } + } + + this.waitingElements = this.waitingElements.filter((element) => !foundSelectors.includes(element.selector)); + + if (this.waitingElements.length === 0) { + this.waitingMutationObserver.disconnect(); + this.waitingMutationObserver = null; + } + }); + + this.waitingMutationObserver.observe(document.body, { + childList: true, + subtree: true + }); + } + }); + } + containsPermission(permissions: chrome.permissions.Permissions): Promise { return new Promise((resolve) => { chrome.permissions.contains(permissions, resolve) diff --git a/src/utils/genericUtils.ts b/src/utils/genericUtils.ts index ad44827b..b146e57a 100644 --- a/src/utils/genericUtils.ts +++ b/src/utils/genericUtils.ts @@ -1,11 +1,10 @@ /** Function that can be used to wait for a condition before returning. */ async function wait(condition: () => T | false, timeout = 5000, check = 100): Promise { return await new Promise((resolve, reject) => { - const failTimeout = setTimeout(() => { + setTimeout(() => { clearInterval(interval); reject("TIMEOUT"); }, timeout); - if (timeout === 0) clearTimeout(failTimeout); const intervalCheck = () => { const result = condition(); diff --git a/src/utils/pageUtils.ts b/src/utils/pageUtils.ts index 0a5d7a37..28ac37d8 100644 --- a/src/utils/pageUtils.ts +++ b/src/utils/pageUtils.ts @@ -19,11 +19,6 @@ export function getControls(): HTMLElement | false { return false; } -export function getHoverPreview(): Element | false { - const hoverPreview = document.querySelector(".ytp-inline-preview-ui"); - return hoverPreview || false; -} - export function isVisible(element: HTMLElement): boolean { return element && element.offsetWidth > 0 && element.offsetHeight > 0; }