Description
When FilterTube's global popup status is set to Disabled ("Filtering Paused"), keyword rules can still be enforced on YouTube watch pages.
If the current video matches a blocked term, it tries to load for several seconds and is then skipped in favor of another video. The popup continues to show FilterTube as Disabled.
Disabling the entire Firefox add-on stops the behavior; FilterTube's own Enabled/Disabled control does not.
Environment
- FilterTube version: 3.3.7
- Browser: Firefox 154.0.1 (build 15426.8.24), desktop
- OS: macOS 26.4 (build 25E246)
- The affected code is also present in FilterTube 3.3.6, but not 3.3.5.
Steps to reproduce
- Add a keyword that matches a particular video.
- Confirm that the rule blocks that video while FilterTube is Enabled.
- Use the FilterTube popup's global control to change the status to Disabled / "Filtering Paused."
- Open the matching video directly or select it from YouTube.
- Wait several seconds.
Expected behavior
While FilterTube is Disabled, no filtering rules should affect playback. The matching video should load and play normally.
Actual behavior
The video is paused while FilterTube evaluates the retained rules, then playback advances to another video. The FilterTube popup still reports that filtering is Disabled.
The problem can appear as though the extension has entered a persistent bad state.
Source analysis
Source inspection points to the direct-access watch-page enforcement added in commit 0920969.
In applyDOMFallback(), enforceCurrentWatchOwnerBlock(effectiveSettings) is called before either the active-work check or the Disabled-state cleanup:
|
if (enforceCurrentChannelPageDirectAccess(effectiveSettings)) return; |
|
enforceCurrentWatchOwnerBlock(effectiveSettings); |
|
syncRouteScopedContentControls(effectiveSettings); |
|
applyFilterTubeHomeFeedPolish(); |
|
|
|
const hasActiveFallbackWork = hasActiveDOMFallbackWork(effectiveSettings); |
|
perfRun.activeWork = hasActiveFallbackWork || onlyWhitelistPending; |
|
if (!hasActiveFallbackWork && !onlyWhitelistPending) { |
|
const state = window.__filtertubeDomFallbackPerfState || (window.__filtertubeDomFallbackPerfState = { |
|
hadActiveWork: false, |
|
lastCleanupTs: 0 |
|
}); |
|
const cleanupDue = state.hadActiveWork || forceReprocess || (Date.now() - (state.lastCleanupTs || 0) > 5000); |
|
if (cleanupDue) { |
|
clearStaleDOMFallbackVisibility(); |
|
state.lastCleanupTs = Date.now(); |
|
} |
|
state.hadActiveWork = false; |
|
return; |
enforceCurrentWatchOwnerBlock() does not check settings.enabled. It therefore continues evaluating filterKeywords, filterChannels, and the other retained rules. When it finds a match, it pauses the player:
|
function enforceCurrentWatchOwnerBlock(settings) { |
|
try { |
|
const path = String(document.location?.pathname || ''); |
|
const isShortRoute = /^\/shorts\/[a-zA-Z0-9_-]{11}(?:\/|$)/.test(path); |
|
const isEmbedRoute = /^\/embed\/[a-zA-Z0-9_-]{11}(?:\/|$)/.test(path); |
|
if (!path.startsWith('/watch') && !isShortRoute && !isEmbedRoute) { |
|
releaseDirectAccessGuard('', false); |
|
clearCurrentShortAdmissionOverlay(); |
|
return; |
|
} |
|
if (String(document.location?.hostname || '').includes('youtubekids.com')) return; |
|
const listMode = (settings && settings.listMode === 'whitelist') ? 'whitelist' : 'blocklist'; |
|
|
|
const routeVideoId = getCurrentWatchVideoId(); |
|
if (!routeVideoId) return; |
|
const requirements = getDirectAccessRuleRequirements(settings); |
|
if (!requirements.hasExplicitVideoRules && !requirements.needsIdentity && !requirements.needsText && listMode !== 'whitelist') { |
|
releaseDirectAccessGuard(routeVideoId, true); |
|
return; |
|
if (isShortRoute) { |
|
pauseCurrentWatchForDirectAccess(ownerMeta.videoId, 'blocked'); |
|
pauseCurrentShortPlayer(); |
|
setCurrentShortAdmissionOverlay( |
|
'blocked', |
|
`Blocked by channel or video rule${ownerName ? `\n${ownerName}` : ''}` |
|
); |
|
return; |
|
} |
|
|
|
pauseCurrentWatchForDirectAccess(ownerMeta.videoId, 'blocked'); |
|
setDirectAccessOverlay( |
|
'blocked', |
|
`Blocked by FilterTube${ownerName ? `\n${ownerName}` : ''}` |
|
); |
|
|
|
try { |
|
ownerMeta.ownerRoot?.setAttribute?.('data-filtertube-current-watch-blocked', 'true'); |
|
ownerMeta.ownerRoot?.setAttribute?.('data-filtertube-channel-id', ownerMeta.id || ''); |
|
} catch (e) { |
|
} |
|
try { |
|
const video = document.querySelector('video.html5-main-video'); |
|
if (video && typeof video.pause === 'function') { |
|
video.pause(); |
|
} |
It then clicks a candidate next-video link or, after retries, YouTube's Next button:
|
if (isEmbedRoute) return; |
|
|
|
const targetLink = findNextAllowedWatchPlaylistLink(settings, ownerMeta.videoId); |
|
if (targetLink) { |
|
setTimeout(() => { |
|
try { |
|
targetLink.click(); |
|
} catch (e) { |
|
} |
|
}, 60); |
|
return; |
|
} |
|
|
|
if (openWatchPlaylistPanelIfCollapsed()) { |
|
setTimeout(() => { |
|
try { |
|
if (typeof applyDOMFallback === 'function') { |
|
applyDOMFallback(settings, { preserveScroll: true, forceReprocess: true }); |
|
} |
|
} catch (e) { |
|
} |
|
}, 260); |
|
return; |
|
} |
|
|
|
state.retryVideoId = state.retryVideoId || ''; |
|
state.retryCount = Number.isFinite(Number(state.retryCount)) ? Number(state.retryCount) : 0; |
|
if (state.retryVideoId !== ownerMeta.videoId) { |
|
state.retryVideoId = ownerMeta.videoId; |
|
state.retryCount = 0; |
|
} |
|
if (state.retryCount < 3) { |
|
state.retryCount += 1; |
|
setTimeout(() => { |
|
try { |
|
if (typeof applyDOMFallback === 'function') { |
|
applyDOMFallback(settings, { preserveScroll: true, forceReprocess: true }); |
|
} |
|
} catch (e) { |
|
} |
|
}, 1300); |
|
return; |
|
} |
|
|
|
const nextButton = document.querySelector('.ytp-next-button:not([disabled])'); |
|
if (nextButton) { |
|
setTimeout(() => { |
|
try { |
|
nextButton.click(); |
|
} catch (e) { |
|
} |
|
}, 80); |
There is a later effectiveSettings.enabled === false cleanup, but it appears unreachable here: hasActiveDOMFallbackWork() returns false when filtering is disabled, causing the earlier return at line 4530.
|
if (effectiveSettings.enabled === false) { |
|
try { |
|
clearContentControlStyles(); |
|
document.querySelectorAll('[data-filtertube-hidden], .filtertube-hidden, [data-filtertube-pending-category], [data-filtertube-pending-language], [data-filtertube-category-unavailable], [data-filtertube-pending-upload-date]').forEach(el => { |
|
toggleVisibility(el, false, '', true); |
|
try { |
|
el.removeAttribute('data-filtertube-pending-category'); |
|
el.removeAttribute('data-filtertube-pending-language'); |
|
el.removeAttribute('data-filtertube-pending-language-ts'); |
|
el.removeAttribute('data-filtertube-category-unavailable'); |
|
el.removeAttribute('data-filtertube-category-unavailable-message'); |
|
el.removeAttribute('data-filtertube-pending-upload-date'); |
|
el.removeAttribute('data-filtertube-pending-category-ts'); |
|
el.removeAttribute('data-filtertube-pending-upload-date-ts'); |
|
} catch (e) { |
|
} |
|
}); |
|
} catch (e) { |
|
} |
Possible fix
Before performing direct-access enforcement:
- Check
effectiveSettings.enabled.
- If Disabled, release any pending or blocked playback guard.
- Remove the direct-access overlay and other current-watch blocking state.
- Return without evaluating the stored rules.
It may also be worth applying the same Enabled-state guard to enforceCurrentChannelPageDirectAccess(), since that function is called at the same pre-gate location.
Regression information
The pre-gate call to enforceCurrentWatchOwnerBlock() was introduced by commit 0920969. The affected dom_fallback.js code shipped in both Firefox versions 3.3.6 and 3.3.7. It is absent from 3.3.5.
Related issues
This issue is specifically about the global Disabled state being ignored by watch-page direct-access enforcement.
Description
When FilterTube's global popup status is set to Disabled ("Filtering Paused"), keyword rules can still be enforced on YouTube watch pages.
If the current video matches a blocked term, it tries to load for several seconds and is then skipped in favor of another video. The popup continues to show FilterTube as Disabled.
Disabling the entire Firefox add-on stops the behavior; FilterTube's own Enabled/Disabled control does not.
Environment
Steps to reproduce
Expected behavior
While FilterTube is Disabled, no filtering rules should affect playback. The matching video should load and play normally.
Actual behavior
The video is paused while FilterTube evaluates the retained rules, then playback advances to another video. The FilterTube popup still reports that filtering is Disabled.
The problem can appear as though the extension has entered a persistent bad state.
Source analysis
Source inspection points to the direct-access watch-page enforcement added in commit
0920969.In
applyDOMFallback(),enforceCurrentWatchOwnerBlock(effectiveSettings)is called before either the active-work check or the Disabled-state cleanup:FilterTube/js/content/dom_fallback.js
Lines 4512 to 4530 in cdb956d
enforceCurrentWatchOwnerBlock()does not checksettings.enabled. It therefore continues evaluatingfilterKeywords,filterChannels, and the other retained rules. When it finds a match, it pauses the player:FilterTube/js/content/dom_fallback.js
Lines 1517 to 1535 in cdb956d
FilterTube/js/content/dom_fallback.js
Lines 1703 to 1728 in cdb956d
It then clicks a candidate next-video link or, after retries, YouTube's Next button:
FilterTube/js/content/dom_fallback.js
Lines 1776 to 1827 in cdb956d
There is a later
effectiveSettings.enabled === falsecleanup, but it appears unreachable here:hasActiveDOMFallbackWork()returns false when filtering is disabled, causing the earlier return at line 4530.FilterTube/js/content/dom_fallback.js
Lines 4747 to 4765 in cdb956d
Possible fix
Before performing direct-access enforcement:
effectiveSettings.enabled.It may also be worth applying the same Enabled-state guard to
enforceCurrentChannelPageDirectAccess(), since that function is called at the same pre-gate location.Regression information
The pre-gate call to
enforceCurrentWatchOwnerBlock()was introduced by commit0920969. The affecteddom_fallback.jscode shipped in both Firefox versions 3.3.6 and 3.3.7. It is absent from 3.3.5.Related issues
This issue is specifically about the global Disabled state being ignored by watch-page direct-access enforcement.