From 059a674ae1f7db9868e3d4af59c3770a54a2e721 Mon Sep 17 00:00:00 2001 From: Michael C Date: Sat, 15 Jan 2022 22:49:22 -0500 Subject: [PATCH 1/5] fix hover preview segments --- src/content.ts | 2 +- src/utils.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/content.ts b/src/content.ts index 81ba097b..0936d238 100644 --- a/src/content.ts +++ b/src/content.ts @@ -945,7 +945,7 @@ function getYouTubeVideoID(document: Document): string | boolean { // skip to document if matches pattern if (url.includes("/channel/") || url.includes("/user/") || url.includes("/c/")) return getYouTubeVideoIDFromDocument(document); // not sure, try URL then document - return getYouTubeVideoIDFromURL(url) || getYouTubeVideoIDFromDocument(document); + return getYouTubeVideoIDFromURL(url) || getYouTubeVideoIDFromDocument(document, false); } function getYouTubeVideoIDFromDocument(document: Document, hideIcon = true): string | boolean { diff --git a/src/utils.ts b/src/utils.ts index cbbf03a3..a2687f3d 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -331,7 +331,7 @@ export default class Utils { findReferenceNode(): HTMLElement { const selectors = [ - "#player-container-id", + "#player-container", "#movie_player", "#c4-player", // Channel Trailer "#main-panel.ytmusic-player-page", // YouTube music @@ -347,7 +347,7 @@ export default class Utils { let index = 1; //find the child that is the video player (sometimes it is not the first) - while (index < player.children.length && (!referenceNode.classList.contains("html5-video-player") || !referenceNode.classList.contains("ytp-embed"))) { + while (index < player.children.length && (!referenceNode.classList?.contains("html5-video-player") || !referenceNode.classList?.contains("ytp-embed"))) { referenceNode = player.children[index] as HTMLElement; index++; From e4aa7efd3c72bc32050ee48d0cc7688a50b86383 Mon Sep 17 00:00:00 2001 From: Michael C Date: Wed, 19 Jan 2022 01:47:43 -0500 Subject: [PATCH 2/5] add infinite wait for hover preview --- src/content.ts | 4 +++- src/utils/genericUtils.ts | 3 ++- src/utils/pageUtils.ts | 5 +++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/content.ts b/src/content.ts index 57877380..4f87f39a 100644 --- a/src/content.ts +++ b/src/content.ts @@ -17,7 +17,7 @@ import { getCategoryActionType } from "./utils/categoryUtils"; import { SkipButtonControlBar } from "./js-components/skipButtonControlBar"; import { Tooltip } from "./render/Tooltip"; import { getStartTimeFromUrl } from "./utils/urlParser"; -import { findValidElement, getControls, getHashParams, isVisible } from "./utils/pageUtils"; +import { findValidElement, getControls, getHashParams, isVisible, getHoverPreview } from "./utils/pageUtils"; import { CategoryPill } from "./render/CategoryPill"; import { AnimationUtils } from "./utils/animationUtils"; import { GenericUtils } from "./utils/genericUtils"; @@ -92,6 +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()) addPageListeners(); addHotkeyListener(); diff --git a/src/utils/genericUtils.ts b/src/utils/genericUtils.ts index b146e57a..ad44827b 100644 --- a/src/utils/genericUtils.ts +++ b/src/utils/genericUtils.ts @@ -1,10 +1,11 @@ /** 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) => { - setTimeout(() => { + const failTimeout = 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 28ac37d8..0a5d7a37 100644 --- a/src/utils/pageUtils.ts +++ b/src/utils/pageUtils.ts @@ -19,6 +19,11 @@ 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; } From bf735f47b039ca3ea0276541354207ab6c8e2f05 Mon Sep 17 00:00:00 2001 From: Ajay Date: Wed, 19 Jan 2022 10:57:28 -0500 Subject: [PATCH 3/5] Fix reference node order to fix issue with player-container always being visible --- src/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.ts b/src/utils.ts index a2687f3d..29a3b382 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -331,9 +331,9 @@ export default class Utils { findReferenceNode(): HTMLElement { const selectors = [ - "#player-container", "#movie_player", "#c4-player", // Channel Trailer + "#player-container", // Preview on hover "#main-panel.ytmusic-player-page", // YouTube music "#player-container .video-js", // Invidious ".main-video-section > .video-container" // Cloudtube From 002f22c040746d6d7c843ec278131c2321cf260e Mon Sep 17 00:00:00 2001 From: Ajay Date: Wed, 19 Jan 2022 11:27:19 -0500 Subject: [PATCH 4/5] Wait for hover preview using mutation observer --- src/content.ts | 4 ++-- src/utils.ts | 39 +++++++++++++++++++++++++++++++++++++++ src/utils/genericUtils.ts | 3 +-- src/utils/pageUtils.ts | 5 ----- 4 files changed, 42 insertions(+), 9 deletions(-) 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; } From 41a8b2718a00c6af16928bc55e908d5ca4e25e59 Mon Sep 17 00:00:00 2001 From: Ajay Date: Wed, 19 Jan 2022 11:30:04 -0500 Subject: [PATCH 5/5] Remove deleted import --- src/content.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/content.ts b/src/content.ts index e839f359..14f1f95d 100644 --- a/src/content.ts +++ b/src/content.ts @@ -15,9 +15,8 @@ import { Message, MessageResponse, VoteResponse } from "./messageTypes"; import * as Chat from "./js-components/chat"; import { getCategoryActionType } from "./utils/categoryUtils"; import { SkipButtonControlBar } from "./js-components/skipButtonControlBar"; -import { Tooltip } from "./render/Tooltip"; import { getStartTimeFromUrl } from "./utils/urlParser"; -import { findValidElement, getControls, getHashParams, isVisible, getHoverPreview } from "./utils/pageUtils"; +import { findValidElement, getControls, getHashParams, isVisible } from "./utils/pageUtils"; import { CategoryPill } from "./render/CategoryPill"; import { AnimationUtils } from "./utils/animationUtils"; import { GenericUtils } from "./utils/genericUtils";