Fix duplicate labels from synthetic Shadow DOM traversal

Some frameworks (e.g. Salesforce LWC) expose a .shadowRoot on elements
whose content is actually the same light-DOM subtree rather than a
real encapsulated tree. deepQueryAll's recursive shadow traversal
revisited that subtree once per nested "shadow" ancestor, so a single
image could get processed and labeled multiple times (observed: one
footer logo produced 5 identical OK badges). deepQueryAll now tracks
visited roots and elements so each is only ever processed once.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Sascha 2026-07-13 11:38:54 +02:00
parent c6a92ea26c
commit 7a73a68998
3 changed files with 21 additions and 7 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -110,15 +110,29 @@
// Hilfsfunktionen // Hilfsfunktionen
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
// Durchsucht auch offene Shadow-DOM-Bäume // Durchsucht auch offene Shadow-DOM-Bäume.
function deepQueryAll(selector, root, callback) { // Manche Frameworks (z.B. Salesforce LWC mit "synthetischem" Shadow DOM)
// hängen ein .shadowRoot an Elemente, dessen Inhalt in Wirklichkeit derselbe
// Light-DOM-Teilbaum ist statt einer echten Kapselung. Ohne Dedupe würde ein
// einzelnes Bild dann einmal pro verschachteltem Fake-Shadow-Level erneut
// gefunden und mit mehreren identischen Labels versehen. seenRoots/seenElements
// sorgen dafür, dass jeder Wurzelknoten und jedes Element nur einmal zählt.
function deepQueryAll(selector, root, callback, seenRoots, seenElements) {
root = root || document;
seenRoots = seenRoots || new Set();
seenElements = seenElements || new Set();
if (seenRoots.has(root)) return 0;
seenRoots.add(root);
let count = 0; let count = 0;
Array.from((root || document).querySelectorAll(selector)).forEach((el) => { Array.from(root.querySelectorAll(selector)).forEach((el) => {
if (seenElements.has(el)) return;
seenElements.add(el);
callback(el); callback(el);
count += 1; count += 1;
}); });
Array.from((root || document).querySelectorAll('*')).forEach((el) => { Array.from(root.querySelectorAll('*')).forEach((el) => {
if (el.shadowRoot) count += deepQueryAll(selector, el.shadowRoot, callback); if (el.shadowRoot) count += deepQueryAll(selector, el.shadowRoot, callback, seenRoots, seenElements);
}); });
return count; return count;
} }