Make tracker anchoring self-healing against comment lazy-load

This commit is contained in:
2026-02-26 00:49:38 +01:00
parent b175603b54
commit c0efa3f1fc

View File

@@ -309,6 +309,13 @@ function getTrackerInsertionAnchorInPost(postElement, buttonBar) {
return anchor && anchor.parentElement === postElement ? anchor : null; return anchor && anchor.parentElement === postElement ? anchor : null;
} }
function isNodeAfter(node, referenceNode) {
if (!node || !referenceNode || node === referenceNode) {
return false;
}
return Boolean(node.compareDocumentPosition(referenceNode) & Node.DOCUMENT_POSITION_PRECEDING);
}
function hasCommentComposerSignal(node) { function hasCommentComposerSignal(node) {
if (!node || !node.querySelector) { if (!node || !node.querySelector) {
return false; return false;
@@ -321,6 +328,34 @@ function hasCommentComposerSignal(node) {
); );
} }
function findCommentComposerAnchorInPost(postElement) {
if (!postElement) {
return null;
}
const composerCandidates = Array.from(
postElement.querySelectorAll(
'div[role="textbox"][contenteditable="true"], [role="textbox"][aria-label*="komment" i], [role="textbox"][aria-label*="comment" i], [role="textbox"][aria-placeholder*="komment" i], [role="textbox"][aria-placeholder*="comment" i]'
)
);
for (const composer of composerCandidates) {
if (!composer || composer.closest('.fb-tracker-ui')) {
continue;
}
const composerRoot = composer.closest('form[role="presentation"]')
|| composer.closest('form')
|| composer;
const topLevelAnchor = getTopLevelAnchorWithinPost(postElement, composerRoot) || composerRoot;
if (topLevelAnchor && topLevelAnchor.parentElement && postElement.contains(topLevelAnchor)) {
return topLevelAnchor;
}
}
return null;
}
function countActionRoleSignals(node) { function countActionRoleSignals(node) {
if (!node || !node.querySelector) { if (!node || !node.querySelector) {
return 0; return 0;
@@ -364,13 +399,28 @@ function findActionBarByDataRoles(postElement) {
return null; return null;
} }
const commentBoundary = findCommentComposerAnchorInPost(postElement)
|| findCommentSortAnchorInPost(postElement);
const markers = Array.from(postElement.querySelectorAll( const markers = Array.from(postElement.querySelectorAll(
'[data-ad-rendering-role="like_button"], [data-ad-rendering-role*="gefällt" i], [data-ad-rendering-role="comment_button"], [data-ad-rendering-role="share_button"]' '[data-ad-rendering-role="like_button"], [data-ad-rendering-role*="gefällt" i], [data-ad-rendering-role="comment_button"], [data-ad-rendering-role="share_button"]'
)); ));
for (const marker of markers) { for (const marker of markers) {
if (
marker.closest('[aria-roledescription*="comment" i], [aria-roledescription*="kommentar" i], [data-testid*="comment" i], [data-scope="comment"]')
) {
continue;
}
if (commentBoundary && isNodeAfter(marker, commentBoundary)) {
continue;
}
let current = marker; let current = marker;
for (let depth = 0; depth < 8 && current && current !== postElement; depth++) { for (let depth = 0; depth < 8 && current && current !== postElement; depth++) {
if (commentBoundary && isNodeAfter(current, commentBoundary)) {
current = current.parentElement;
continue;
}
if (isReliableActionBarCandidate(postElement, current)) { if (isReliableActionBarCandidate(postElement, current)) {
return current; return current;
} }
@@ -528,6 +578,21 @@ function findCommentSortAnchorInPost(postElement) {
return null; return null;
} }
function findCommentBoundaryAnchorInPost(postElement, actionAnchor = null) {
if (!postElement) {
return null;
}
const adjacentSortAnchor = actionAnchor
? findAdjacentCommentSortAnchor(postElement, actionAnchor)
: null;
if (adjacentSortAnchor) {
return adjacentSortAnchor;
}
return findCommentSortAnchorInPost(postElement) || findCommentComposerAnchorInPost(postElement);
}
function createTrackerInsertionMarker() { function createTrackerInsertionMarker() {
const marker = document.createElement('div'); const marker = document.createElement('div');
marker.className = 'fb-tracker-insertion-anchor'; marker.className = 'fb-tracker-insertion-anchor';
@@ -552,10 +617,16 @@ function positionTrackerInsertionMarker(postElement, marker, buttonBar, postNum
const resolvedButtonBar = resolveActionButtonBar(postElement, buttonBar); const resolvedButtonBar = resolveActionButtonBar(postElement, buttonBar);
const actionAnchor = getDirectActionAnchorInPost(postElement, resolvedButtonBar); const actionAnchor = getDirectActionAnchorInPost(postElement, resolvedButtonBar);
const preferredAnchor = actionAnchor; const commentBoundary = findCommentBoundaryAnchorInPost(postElement, actionAnchor);
if (preferredAnchor && preferredAnchor.parentElement) { if (actionAnchor && commentBoundary && commentBoundary.parentElement && isNodeAfter(commentBoundary, actionAnchor)) {
preferredAnchor.parentElement.insertBefore(marker, preferredAnchor.nextSibling); commentBoundary.parentElement.insertBefore(marker, commentBoundary);
setTrackerInsertionMarkerLocked(marker, true);
return true;
}
if (actionAnchor && actionAnchor.parentElement) {
actionAnchor.parentElement.insertBefore(marker, actionAnchor.nextSibling);
setTrackerInsertionMarkerLocked(marker, true); setTrackerInsertionMarkerLocked(marker, true);
return true; return true;
} }
@@ -572,11 +643,10 @@ function positionTrackerInsertionMarker(postElement, marker, buttonBar, postNum
return true; return true;
} }
const fallbackSortAnchor = findCommentSortAnchorInPost(postElement); if (commentBoundary && commentBoundary.parentElement) {
if (fallbackSortAnchor && fallbackSortAnchor.parentElement) { commentBoundary.parentElement.insertBefore(marker, commentBoundary);
fallbackSortAnchor.parentElement.insertBefore(marker, fallbackSortAnchor);
setTrackerInsertionMarkerLocked(marker, true); setTrackerInsertionMarkerLocked(marker, true);
console.log('[FB Tracker] Post #' + postNum + ' - Marker fallback inserted before comment sort'); console.log('[FB Tracker] Post #' + postNum + ' - Marker fallback inserted before comment boundary');
return true; return true;
} }
@@ -586,6 +656,25 @@ function positionTrackerInsertionMarker(postElement, marker, buttonBar, postNum
return true; return true;
} }
function isTrackerInsertionMarkerPlacementValid(postElement, marker, buttonBar) {
if (!postElement || !marker || !marker.isConnected || !postElement.contains(marker)) {
return false;
}
const actionAnchor = getDirectActionAnchorInPost(postElement, buttonBar);
const commentBoundary = findCommentBoundaryAnchorInPost(postElement, actionAnchor);
if (actionAnchor && isNodeAfter(actionAnchor, marker)) {
return false;
}
if (commentBoundary && isNodeAfter(marker, commentBoundary)) {
return false;
}
return true;
}
function ensureTrackerInsertionMarker(postElement, buttonBar, postNum = '?') { function ensureTrackerInsertionMarker(postElement, buttonBar, postNum = '?') {
if (!postElement) { if (!postElement) {
return null; return null;
@@ -607,9 +696,20 @@ function ensureTrackerInsertionMarker(postElement, buttonBar, postNum = '?') {
} }
if (marker.isConnected && postElement.contains(marker) && isTrackerInsertionMarkerLocked(marker)) { if (marker.isConnected && postElement.contains(marker) && isTrackerInsertionMarkerLocked(marker)) {
if (!isTrackerInsertionMarkerPlacementValid(postElement, marker, buttonBar)) {
setTrackerInsertionMarkerLocked(marker, false);
} else {
setTrackerAnchorForPost(postElement, marker); setTrackerAnchorForPost(postElement, marker);
return marker; return marker;
} }
}
if (marker.isConnected && postElement.contains(marker) && !isTrackerInsertionMarkerLocked(marker)) {
if (isTrackerInsertionMarkerPlacementValid(postElement, marker, buttonBar)) {
setTrackerAnchorForPost(postElement, marker);
return marker;
}
}
const positioned = positionTrackerInsertionMarker(postElement, marker, buttonBar, postNum); const positioned = positionTrackerInsertionMarker(postElement, marker, buttonBar, postNum);
if (!positioned || !marker.isConnected) { if (!positioned || !marker.isConnected) {
@@ -630,6 +730,19 @@ function insertTrackerAtMarker(marker, trackerElement) {
return true; return true;
} }
function realignExistingTrackerUI(postElement, buttonBar, trackerElement, postNum = '?') {
if (!postElement || !trackerElement || !trackerElement.isConnected) {
return false;
}
const marker = ensureTrackerInsertionMarker(postElement, buttonBar, postNum);
if (!marker) {
return false;
}
return insertTrackerAtMarker(marker, trackerElement);
}
const AI_CREDENTIAL_CACHE_TTL = 60 * 1000; // 1 minute cache const AI_CREDENTIAL_CACHE_TTL = 60 * 1000; // 1 minute cache
const aiCredentialCache = { const aiCredentialCache = {
data: null, data: null,
@@ -3492,10 +3605,7 @@ async function createTrackerUI(postElement, buttonBar, postNum = '?', options =
} }
if (existingUI) { if (existingUI) {
const marker = ensureTrackerInsertionMarker(postElement, buttonBar, postNum); realignExistingTrackerUI(postElement, buttonBar, existingUI, postNum);
if (marker) {
insertTrackerAtMarker(marker, existingUI);
}
console.log('[FB Tracker] Post #' + postNum + ' - UI already exists on normalized element'); console.log('[FB Tracker] Post #' + postNum + ' - UI already exists on normalized element');
postElement.setAttribute(PROCESSED_ATTR, '1'); postElement.setAttribute(PROCESSED_ATTR, '1');
return; return;
@@ -3531,10 +3641,7 @@ async function createTrackerUI(postElement, buttonBar, postNum = '?', options =
postElement.setAttribute(PROCESSED_ATTR, '1'); postElement.setAttribute(PROCESSED_ATTR, '1');
return; return;
} }
const marker = ensureTrackerInsertionMarker(postElement, buttonBar, postNum); realignExistingTrackerUI(postElement, buttonBar, scopedTracker, postNum);
if (marker) {
insertTrackerAtMarker(marker, scopedTracker);
}
console.log('[FB Tracker] Post #' + postNum + ' - Reusing existing tracker UI in same scope'); console.log('[FB Tracker] Post #' + postNum + ' - Reusing existing tracker UI in same scope');
setTrackerElementForPost(postElement, scopedTracker); setTrackerElementForPost(postElement, scopedTracker);
postElement.setAttribute(PROCESSED_ATTR, '1'); postElement.setAttribute(PROCESSED_ATTR, '1');
@@ -4386,6 +4493,7 @@ function findPosts() {
if (isInDialog) { if (isInDialog) {
if (trackerInSameDialog) { if (trackerInSameDialog) {
realignExistingTrackerUI(container, precomputedButtonBar || null, existingTracker, 'existing');
seenSet.add(container); seenSet.add(container);
continue; continue;
} }
@@ -4400,6 +4508,12 @@ function findPosts() {
} }
} else { } else {
if (alreadyProcessed || (existingTracker && existingTracker.isConnected)) { if (alreadyProcessed || (existingTracker && existingTracker.isConnected)) {
if (existingTracker && existingTracker.isConnected) {
realignExistingTrackerUI(container, precomputedButtonBar || null, existingTracker, 'existing');
setTrackerElementForPost(container, existingTracker);
} else if (alreadyProcessed) {
container.removeAttribute(PROCESSED_ATTR);
}
seenSet.add(container); seenSet.add(container);
continue; continue;
} }