/* eslint-disable */
// wait-helper.js
// Listen for text appearance/disappearance in the current document using MutationObserver.
// Returns a stable ref (compatible with accessibility-tree-helper) for the first matching element.
(function () {
if (window.__WAIT_HELPER_INITIALIZED__) return;
window.__WAIT_HELPER_INITIALIZED__ = true;
// Ensure ref mapping infra exists (compatible with accessibility-tree-helper.js)
if (!window.__claudeElementMap) window.__claudeElementMap = {};
if (!window.__claudeRefCounter) window.__claudeRefCounter = 0;
function isVisible(el) {
try {
if (!(el instanceof Element)) return false;
const style = getComputedStyle(el);
if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0')
return false;
const rect = el.getBoundingClientRect();
if (rect.width <= 0 || rect.height <= 0) return false;
return true;
} catch {
return false;
}
}
function normalize(str) {
return String(str || '')
.replace(/\s+/g, ' ')
.trim()
.toLowerCase();
}
function matchesText(el, needle) {
const t = normalize(needle);
if (!t) return false;
try {
if (!isVisible(el)) return false;
const aria = el.getAttribute('aria-label');
if (aria && normalize(aria).includes(t)) return true;
const title = el.getAttribute('title');
if (title && normalize(title).includes(t)) return true;
const alt = el.getAttribute('alt');
if (alt && normalize(alt).includes(t)) return true;
const placeholder = el.getAttribute('placeholder');
if (placeholder && normalize(placeholder).includes(t)) return true;
// input/textarea value
if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
const value = el.value || el.getAttribute('value');
if (value && normalize(value).includes(t)) return true;
}
const text = el.innerText || el.textContent || '';
if (normalize(text).includes(t)) return true;
} catch {}
return false;
}
function findElementByText(text) {
// Fast path: query common interactive elements first
const prioritized = Array.from(
document.querySelectorAll('a,button,input,textarea,select,label,summary,[role]'),
);
for (const el of prioritized) if (matchesText(el, text)) return el;
// Fallback: broader scan with cap to avoid blocking on huge pages
const walker = document.createTreeWalker(
document.body || document.documentElement,
NodeFilter.SHOW_ELEMENT,
);
let count = 0;
while (walker.nextNode()) {
const el = /** @type {Element} */ (walker.currentNode);
if (matchesText(el, text)) return el;
if (++count > 5000) break; // Hard cap to avoid long scans
}
return null;
}
function ensureRefForElement(el) {
// Try to reuse an existing ref
for (const k in window.__claudeElementMap) {
const weak = window.__claudeElementMap[k];
if (weak && typeof weak.deref === 'function' && weak.deref() === el) return k;
}
const refId = `ref_${++window.__claudeRefCounter}`;
window.__claudeElementMap[refId] = new WeakRef(el);
return refId;
}
function centerOf(el) {
const r = el.getBoundingClientRect();
return { x: Math.round(r.left + r.width / 2), y: Math.round(r.top + r.height / 2) };
}
function waitFor({ text, appear = true, timeout = 5000 }) {
return new Promise((resolve) => {
const start = Date.now();
let resolved = false;
const check = () => {
try {
const match = findElementByText(text);
if (appear) {
if (match) {
const ref = ensureRefForElement(match);
const center = centerOf(match);
done({ success: true, matched: { ref, center }, tookMs: Date.now() - start });
}
} else {
// wait for disappearance
if (!match) {
done({ success: true, matched: null, tookMs: Date.now() - start });
}
}
} catch {}
};
const done = (result) => {
if (resolved) return;
resolved = true;
obs && obs.disconnect();
clearTimeout(timer);
resolve(result);
};
const obs = new MutationObserver(() => check());
try {
obs.observe(document.documentElement || document.body, {
subtree: true,
childList: true,
characterData: true,
attributes: true,
});
} catch {}
// Initial check
check();
const timer = setTimeout(
() => {
done({ success: false, reason: 'timeout', tookMs: Date.now() - start });
},
Math.max(0, timeout),
);
});
}
function waitForSelector({ selector, visible = true, timeout = 5000 }) {
return new Promise((resolve) => {
const start = Date.now();
let resolved = false;
const isMatch = () => {
try {
const el = document.querySelector(selector);
if (!el) return null;
if (!visible) return el;
return isVisible(el) ? el : null;
} catch {
return null;
}
};
const done = (result) => {
if (resolved) return;
resolved = true;
obs && obs.disconnect();
clearTimeout(timer);
resolve(result);
};
const check = () => {
const el = isMatch();
if (el) {
const ref = ensureRefForElement(el);
const center = centerOf(el);
done({ success: true, matched: { ref, center }, tookMs: Date.now() - start });
}
};
const obs = new MutationObserver(check);
try {
obs.observe(document.documentElement || document.body, {
subtree: true,
childList: true,
characterData: true,
attributes: true,
});
} catch {}
// initial check
check();
const timer = setTimeout(
() => done({ success: false, reason: 'timeout', tookMs: Date.now() - start }),
Math.max(0, timeout),
);
});
}
chrome.runtime.onMessage.addListener((request, _sender, sendResponse) => {
try {
if (request && request.action === 'wait_helper_ping') {
sendResponse({ status: 'pong' });
return false;
}
if (request && request.action === 'waitForText') {
const text = String(request.text || '').trim();
const appear = request.appear !== false; // default true
const timeout = Number(request.timeout || 5000);
if (!text) {
sendResponse({ success: false, error: 'text is required' });
return true;
}
waitFor({ text, appear, timeout }).then((res) => sendResponse(res));
return true; // async
}
if (request && request.action === 'waitForSelector') {
const selector = String(request.selector || '').trim();
const visible = request.visible !== false; // default true
const timeout = Number(request.timeout || 5000);
if (!selector) {
sendResponse({ success: false, error: 'selector is required' });
return true;
}
waitForSelector({ selector, visible, timeout }).then((res) => sendResponse(res));
return true; // async
}
} catch (e) {
sendResponse({ success: false, error: String(e && e.message ? e.message : e) });
return true;
}
return false;
});
})();