Skip to content

Global Disabled state is ignored on watch pages; blocked videos pause and auto-skip (v3.3.6–3.3.7) #77

Description

@absurd

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

  1. Add a keyword that matches a particular video.
  2. Confirm that the rule blocks that video while FilterTube is Enabled.
  3. Use the FilterTube popup's global control to change the status to Disabled / "Filtering Paused."
  4. Open the matching video directly or select it from YouTube.
  5. 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:

  1. Check effectiveSettings.enabled.
  2. If Disabled, release any pending or blocked playback guard.
  3. Remove the direct-access overlay and other current-watch blocking state.
  4. 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.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions