From 26c552aab57f672b473e22c5c88efbfe27d20b2c Mon Sep 17 00:00:00 2001 From: Meik Date: Thu, 26 Feb 2026 08:57:41 +0100 Subject: [PATCH] Stabilize tracker bar placement above Facebook action row --- extension/content.js | 171 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 136 insertions(+), 35 deletions(-) diff --git a/extension/content.js b/extension/content.js index 12e75a0..e6ee4e8 100644 --- a/extension/content.js +++ b/extension/content.js @@ -368,6 +368,82 @@ function countActionRoleSignals(node) { return [hasLike, hasComment, hasShare].filter(Boolean).length; } +function hasCommentSortSignal(node) { + if (!node || !node.querySelector) { + return false; + } + + return Boolean( + node.querySelector( + '[aria-label*="relevantes zuerst" i], [aria-label*="most relevant" i], [aria-label*="neueste" i], [aria-label*="newest" i]' + ) + ); +} + +function isActionRowShapeCandidate(postElement, candidate) { + if (!postElement || !candidate || !candidate.isConnected || !postElement.contains(candidate)) { + return false; + } + + if (candidate.closest('.fb-tracker-ui')) { + return false; + } + + if ( + hasCommentComposerSignal(candidate) + || hasCommentContentSignal(candidate) + || hasCommentSortSignal(candidate) + ) { + return false; + } + + const buttonCount = candidate.querySelectorAll('[role="button"], button').length; + if (buttonCount < 3 || buttonCount > 24) { + return false; + } + + return true; +} + +function isCompactActionBarCandidate(postElement, candidate) { + if (!isActionRowShapeCandidate(postElement, candidate)) { + return false; + } + + const actionRoleSignals = countActionRoleSignals(candidate); + if (actionRoleSignals >= 2) { + return true; + } + + return hasInteractionButtons(candidate); +} + +function normalizeActionBarCandidate(postElement, candidate) { + if (!postElement || !candidate || !candidate.isConnected || !postElement.contains(candidate)) { + return null; + } + + let fallback = null; + let current = candidate; + for (let depth = 0; depth < 12 && current && current !== postElement; depth++) { + if (isCompactActionBarCandidate(postElement, current)) { + return current; + } + + if (!fallback && isActionRowShapeCandidate(postElement, current)) { + fallback = current; + } + + current = current.parentElement; + } + + if (fallback) { + return fallback; + } + + return isReliableActionBarCandidate(postElement, candidate) ? candidate : null; +} + function isReliableActionBarCandidate(postElement, candidate) { if (!postElement || !candidate || !candidate.isConnected || !postElement.contains(candidate)) { return false; @@ -377,7 +453,11 @@ function isReliableActionBarCandidate(postElement, candidate) { return false; } - if (hasCommentComposerSignal(candidate)) { + if ( + hasCommentComposerSignal(candidate) + || hasCommentContentSignal(candidate) + || hasCommentSortSignal(candidate) + ) { return false; } @@ -436,18 +516,20 @@ function resolveActionButtonBar(postElement, buttonBar) { return null; } - const dataRoleResolved = findActionBarByDataRoles(postElement); - if (dataRoleResolved) { - return dataRoleResolved; - } + const candidates = [ + findActionBarByDataRoles(postElement), + buttonBar, + findButtonBar(postElement) + ]; - if (buttonBar && isReliableActionBarCandidate(postElement, buttonBar)) { - return buttonBar; - } - - const resolved = findButtonBar(postElement); - if (resolved && isReliableActionBarCandidate(postElement, resolved)) { - return resolved; + for (const candidate of candidates) { + if (!candidate) { + continue; + } + const normalized = normalizeActionBarCandidate(postElement, candidate); + if (normalized) { + return normalized; + } } return null; @@ -661,6 +743,28 @@ function insertMarkerAfterAnchor(postElement, marker, anchor) { return true; } +function getNextNonTrackerSibling(node) { + if (!node) { + return null; + } + + let current = node.nextElementSibling; + while (current) { + if ( + !current.classList + || ( + !current.classList.contains('fb-tracker-ui') + && !current.classList.contains('fb-tracker-insertion-anchor') + ) + ) { + return current; + } + current = current.nextElementSibling; + } + + return null; +} + function positionTrackerInsertionMarker(postElement, marker, buttonBar, postNum = '?') { if (!postElement || !marker) { return false; @@ -668,7 +772,6 @@ function positionTrackerInsertionMarker(postElement, marker, buttonBar, postNum const resolvedButtonBar = resolveActionButtonBar(postElement, buttonBar); const actionAnchor = getDirectActionAnchorInPost(postElement, resolvedButtonBar); - const commentBoundary = findCommentBoundaryAnchorInPost(postElement, actionAnchor); if (actionAnchor && insertMarkerBeforeAnchor(postElement, marker, actionAnchor)) { setTrackerInsertionMarkerLocked(marker, true); @@ -682,28 +785,13 @@ function positionTrackerInsertionMarker(postElement, marker, buttonBar, postNum return true; } - const commentComposerAnchor = findCommentComposerAnchorInPost(postElement); - if (commentComposerAnchor && insertMarkerBeforeAnchor(postElement, marker, commentComposerAnchor)) { - setTrackerInsertionMarkerLocked(marker, true); - console.log('[FB Tracker] Post #' + postNum + ' - Marker inserted before comment composer'); - return true; + if (postElement.firstChild) { + postElement.insertBefore(marker, postElement.firstChild); + } else { + postElement.appendChild(marker); } - - if (commentBoundary && insertMarkerBeforeAnchor(postElement, marker, commentBoundary)) { - setTrackerInsertionMarkerLocked(marker, true); - console.log('[FB Tracker] Post #' + postNum + ' - Marker inserted before comment boundary'); - return true; - } - - if (buttonBar && buttonBar.parentElement && buttonBar.parentElement.parentElement && !postElement.contains(buttonBar.parentElement.parentElement)) { - buttonBar.parentElement.parentElement.insertBefore(marker, buttonBar.parentElement.nextSibling); - setTrackerInsertionMarkerLocked(marker, false); - return true; - } - - postElement.appendChild(marker); setTrackerInsertionMarkerLocked(marker, false); - console.log('[FB Tracker] Post #' + postNum + ' - Marker fallback appended to post'); + console.log('[FB Tracker] Post #' + postNum + ' - Marker fallback prepended to post'); return true; } @@ -717,8 +805,21 @@ function isTrackerInsertionMarkerPlacementValid(postElement, marker, buttonBar) } const actionAnchor = getDirectActionAnchorInPost(postElement, buttonBar); - if (actionAnchor && isNodeAfter(marker, actionAnchor)) { - return false; + if (actionAnchor) { + if (isNodeAfter(marker, actionAnchor)) { + return false; + } + + if (actionAnchor.contains(marker) || marker.contains(actionAnchor)) { + return false; + } + + if (marker.parentElement === actionAnchor.parentElement) { + const nextSibling = getNextNonTrackerSibling(marker); + if (nextSibling && nextSibling !== actionAnchor) { + return false; + } + } } const commentBoundary = findCommentBoundaryAnchorInPost(postElement);