diff --git a/src/background.ts b/src/background.ts index 5a7bacd5..d17c1e16 100644 --- a/src/background.ts +++ b/src/background.ts @@ -23,14 +23,35 @@ if (utils.isFirefox()) { }); } -chrome.tabs.onUpdated.addListener(function(tabId) { - chrome.tabs.sendMessage(tabId, { +function onTabUpdatedListener(tabId: number) { + chrome.tabs.sendMessage(tabId, { message: 'update', - }, () => void chrome.runtime.lastError ); // Suppress error on Firefox -}); + }, () => void chrome.runtime.lastError ); // Suppress error on Firefox +} -chrome.runtime.onMessage.addListener(function (request, sender, callback) { - switch(request.message) { +function onNavigationApiAvailableChange(changes: {[key: string]: chrome.storage.StorageChange}) { + if (changes.navigationApiAvailable) { + if (changes.navigationApiAvailable.newValue) { + chrome.tabs.onUpdated.removeListener(onTabUpdatedListener); + } else { + chrome.tabs.onUpdated.addListener(onTabUpdatedListener); + } + } +} + +// If Navigation API is not supported, then background has to inform content script about video change. +// This happens on Safari, Firefox, and Chromium 101 (inclusive) and below. +utils.wait(() => Config.local !== null).then(() => { + if (!Config.local.navigationApiAvailable) + chrome.tabs.onUpdated.addListener(onTabUpdatedListener); +}) + +if (!Config.configSyncListeners.includes(onNavigationApiAvailableChange)) { + Config.configSyncListeners.push(onNavigationApiAvailableChange); +} + +chrome.runtime.onMessage.addListener(function (request, _, callback) { + switch(request.message) { case "openConfig": chrome.tabs.create({url: chrome.runtime.getURL('options/options.html' + (request.hash ? '#' + request.hash : ''))}); return; @@ -61,7 +82,7 @@ chrome.runtime.onMessage.addListener(function (request, sender, callback) { case "unregisterContentScript": unregisterFirefoxContentScript(request.id) return false; - } + } }); //add help page on install diff --git a/src/config.ts b/src/config.ts index 5a0fe5be..14034f09 100644 --- a/src/config.ts +++ b/src/config.ts @@ -101,9 +101,11 @@ export type VideoDownvotes = { segments: { uuid: HashedValue, hidden: SponsorHid interface SBStorage { /* VideoID prefixes to UUID prefixes */ downvotedSegments: Record, + navigationApiAvailable: boolean, } export interface SBObject { + configLocalListeners: Array<(changes: StorageChangesObject) => unknown>; configSyncListeners: Array<(changes: StorageChangesObject) => unknown>; syncDefaults: SBConfig; localDefaults: SBStorage; @@ -120,6 +122,7 @@ const Config: SBObject = { /** * Callback function when an option is updated */ + configLocalListeners: [], configSyncListeners: [], syncDefaults: { userID: null, @@ -283,7 +286,8 @@ const Config: SBObject = { } }, localDefaults: { - downvotedSegments: {} + downvotedSegments: {}, + navigationApiAvailable: null, }, cachedSyncConfig: null, cachedLocalStorage: null, @@ -310,6 +314,10 @@ function configProxy(): { sync: SBConfig, local: SBStorage } { for (const key in changes) { Config.cachedLocalStorage[key] = changes[key].newValue; } + + for (const callback of Config.configLocalListeners) { + callback(changes); + } } }); diff --git a/src/content.ts b/src/content.ts index fd842ef4..af5715f6 100644 --- a/src/content.ts +++ b/src/content.ts @@ -2204,3 +2204,18 @@ function checkForPreloadedSegment() { Config.forceSyncUpdate("unsubmittedSegments"); } } + +// Register listener for URL change via Navigation API +const navigationApiAvailable = "navigation" in window; +if (navigationApiAvailable) { + // TODO: Remove type cast once type declarations are updated + (window as unknown as { navigation: EventTarget }).navigation.addEventListener("navigate", () => videoIDChange(getYouTubeVideoID(document))); +} + +// Record availability of Navigation API +utils.wait(() => Config.local !== null).then(() => { + if (Config.local.navigationApiAvailable !== navigationApiAvailable) { + Config.local.navigationApiAvailable = navigationApiAvailable; + Config.forceLocalUpdate("navigationApiAvailable"); + } +});