Make tracker anchoring self-healing against comment lazy-load
This commit is contained in:
@@ -309,6 +309,13 @@ function getTrackerInsertionAnchorInPost(postElement, buttonBar) {
|
||||
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) {
|
||||
if (!node || !node.querySelector) {
|
||||
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) {
|
||||
if (!node || !node.querySelector) {
|
||||
return 0;
|
||||
@@ -364,13 +399,28 @@ function findActionBarByDataRoles(postElement) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const commentBoundary = findCommentComposerAnchorInPost(postElement)
|
||||
|| findCommentSortAnchorInPost(postElement);
|
||||
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"]'
|
||||
));
|
||||
|
||||
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;
|
||||
for (let depth = 0; depth < 8 && current && current !== postElement; depth++) {
|
||||
if (commentBoundary && isNodeAfter(current, commentBoundary)) {
|
||||
current = current.parentElement;
|
||||
continue;
|
||||
}
|
||||
if (isReliableActionBarCandidate(postElement, current)) {
|
||||
return current;
|
||||
}
|
||||
@@ -528,6 +578,21 @@ function findCommentSortAnchorInPost(postElement) {
|
||||
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() {
|
||||
const marker = document.createElement('div');
|
||||
marker.className = 'fb-tracker-insertion-anchor';
|
||||
@@ -552,10 +617,16 @@ function positionTrackerInsertionMarker(postElement, marker, buttonBar, postNum
|
||||
|
||||
const resolvedButtonBar = resolveActionButtonBar(postElement, buttonBar);
|
||||
const actionAnchor = getDirectActionAnchorInPost(postElement, resolvedButtonBar);
|
||||
const preferredAnchor = actionAnchor;
|
||||
const commentBoundary = findCommentBoundaryAnchorInPost(postElement, actionAnchor);
|
||||
|
||||
if (preferredAnchor && preferredAnchor.parentElement) {
|
||||
preferredAnchor.parentElement.insertBefore(marker, preferredAnchor.nextSibling);
|
||||
if (actionAnchor && commentBoundary && commentBoundary.parentElement && isNodeAfter(commentBoundary, actionAnchor)) {
|
||||
commentBoundary.parentElement.insertBefore(marker, commentBoundary);
|
||||
setTrackerInsertionMarkerLocked(marker, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (actionAnchor && actionAnchor.parentElement) {
|
||||
actionAnchor.parentElement.insertBefore(marker, actionAnchor.nextSibling);
|
||||
setTrackerInsertionMarkerLocked(marker, true);
|
||||
return true;
|
||||
}
|
||||
@@ -572,11 +643,10 @@ function positionTrackerInsertionMarker(postElement, marker, buttonBar, postNum
|
||||
return true;
|
||||
}
|
||||
|
||||
const fallbackSortAnchor = findCommentSortAnchorInPost(postElement);
|
||||
if (fallbackSortAnchor && fallbackSortAnchor.parentElement) {
|
||||
fallbackSortAnchor.parentElement.insertBefore(marker, fallbackSortAnchor);
|
||||
if (commentBoundary && commentBoundary.parentElement) {
|
||||
commentBoundary.parentElement.insertBefore(marker, commentBoundary);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -586,6 +656,25 @@ function positionTrackerInsertionMarker(postElement, marker, buttonBar, postNum
|
||||
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 = '?') {
|
||||
if (!postElement) {
|
||||
return null;
|
||||
@@ -607,9 +696,20 @@ function ensureTrackerInsertionMarker(postElement, buttonBar, postNum = '?') {
|
||||
}
|
||||
|
||||
if (marker.isConnected && postElement.contains(marker) && isTrackerInsertionMarkerLocked(marker)) {
|
||||
if (!isTrackerInsertionMarkerPlacementValid(postElement, marker, buttonBar)) {
|
||||
setTrackerInsertionMarkerLocked(marker, false);
|
||||
} else {
|
||||
setTrackerAnchorForPost(postElement, 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);
|
||||
if (!positioned || !marker.isConnected) {
|
||||
@@ -630,6 +730,19 @@ function insertTrackerAtMarker(marker, trackerElement) {
|
||||
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 aiCredentialCache = {
|
||||
data: null,
|
||||
@@ -3492,10 +3605,7 @@ async function createTrackerUI(postElement, buttonBar, postNum = '?', options =
|
||||
}
|
||||
|
||||
if (existingUI) {
|
||||
const marker = ensureTrackerInsertionMarker(postElement, buttonBar, postNum);
|
||||
if (marker) {
|
||||
insertTrackerAtMarker(marker, existingUI);
|
||||
}
|
||||
realignExistingTrackerUI(postElement, buttonBar, existingUI, postNum);
|
||||
console.log('[FB Tracker] Post #' + postNum + ' - UI already exists on normalized element');
|
||||
postElement.setAttribute(PROCESSED_ATTR, '1');
|
||||
return;
|
||||
@@ -3531,10 +3641,7 @@ async function createTrackerUI(postElement, buttonBar, postNum = '?', options =
|
||||
postElement.setAttribute(PROCESSED_ATTR, '1');
|
||||
return;
|
||||
}
|
||||
const marker = ensureTrackerInsertionMarker(postElement, buttonBar, postNum);
|
||||
if (marker) {
|
||||
insertTrackerAtMarker(marker, scopedTracker);
|
||||
}
|
||||
realignExistingTrackerUI(postElement, buttonBar, scopedTracker, postNum);
|
||||
console.log('[FB Tracker] Post #' + postNum + ' - Reusing existing tracker UI in same scope');
|
||||
setTrackerElementForPost(postElement, scopedTracker);
|
||||
postElement.setAttribute(PROCESSED_ATTR, '1');
|
||||
@@ -4386,6 +4493,7 @@ function findPosts() {
|
||||
|
||||
if (isInDialog) {
|
||||
if (trackerInSameDialog) {
|
||||
realignExistingTrackerUI(container, precomputedButtonBar || null, existingTracker, 'existing');
|
||||
seenSet.add(container);
|
||||
continue;
|
||||
}
|
||||
@@ -4400,6 +4508,12 @@ function findPosts() {
|
||||
}
|
||||
} else {
|
||||
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);
|
||||
continue;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user