already handled
if (el.closest && el.closest('a[' + DONE_ATTR + ']')) return;
var label = el.getAttribute('aria-label') || el.getAttribute('aria-description') || '';
var m = label.match(EMAIL_RE);
if (!m) return;
var email = cleanEmail(m[0]);
if (!email) return;
el.setAttribute(DONE_ATTR, '1');
el.insertAdjacentElement('afterend', makeBtn(email));
}
// -- Walk the DOM -----------------------------------------------------
function walkNode(root) {
// FIX v2.6: bail out entirely on editable roots (e.g. an entire Gmail
// compose iframe body, or a CRM note ) before
// doing any work.
if (isInEditableRegion(root)) return;
// FIX v2.6: dedicated pass for INPUT/TEXTAREA .value, since TEXTAREA
// is deliberately kept out of the TreeWalker's text-node traversal.
var fields = [];
if (root.nodeType === Node.ELEMENT_NODE) {
if (root.tagName === 'INPUT' || root.tagName === 'TEXTAREA') fields.push(root);
if (root.querySelectorAll) {
fields = fields.concat(Array.prototype.slice.call(root.querySelectorAll('input, textarea')));
}
}
fields.forEach(injectIntoInput);
var walker = document.createTreeWalker(
root,
NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT,
{
acceptNode: function (node) {
if (node.nodeType === Node.ELEMENT_NODE) {
if (SKIP_TAGS.has(node.tagName)) return NodeFilter.FILTER_REJECT;
if (node.getAttribute(BTN_ATTR)) return NodeFilter.FILTER_REJECT;
if (node.isContentEditable) return NodeFilter.FILTER_REJECT; // FIX v2.6
// Skip children of already-processed mailto links
if (node.tagName === 'A' && node.getAttribute('href') &&
node.getAttribute('href').indexOf('mailto:') === 0 &&
node.getAttribute(DONE_ATTR)) return NodeFilter.FILTER_REJECT;
}
return NodeFilter.FILTER_ACCEPT;
}
},
false
);
var textNodes = [];
var node;
while ((node = walker.nextNode())) {
if (node.nodeType === Node.TEXT_NODE) {
textNodes.push(node);
} else if (node.nodeType === Node.ELEMENT_NODE) {
var tag = node.tagName;
if (tag === 'A') injectIntoMailto(node);
injectIntoAria(node);
}
}
textNodes.forEach(injectIntoTextNode);
}
// -- Initial scan -----------------------------------------------------
walkNode(document.body);
// -- MutationObserver for SPA/dynamic pages ---------------------------
var observer = new MutationObserver(function (mutations) {
if (_scanning) return;
_scanning = true;
try {
var addedRoots = new Set();
mutations.forEach(function (m) {
m.addedNodes.forEach(function (n) {
if (n.nodeType === Node.ELEMENT_NODE || n.nodeType === Node.TEXT_NODE) {
var root = n.nodeType === Node.ELEMENT_NODE ? n : n.parentElement;
if (root && !root.getAttribute(BTN_ATTR)) addedRoots.add(root);
}
});
});
addedRoots.forEach(function (root) {
if (document.body && document.body.contains(root)) walkNode(root);
});
} finally {
_scanning = false;
}
});
observer.observe(document.body, { childList: true, subtree: true });
})();