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;
|
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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user