现象
网站上启用了 WP Armour Honeypot 防垃圾插件,普通页面上的表单工作正常。但所有通过 Elementor Popup 提交的表单,无论填写什么内容,提交后都返回 “Spamming or your Javascript is disabled !!” 错误。
排查过程
第一步:确认插件正常工作
首先确认 WP Armour 在普通页面上是否正常。网站运行了 WP Armour Honeypot 一段时间,非 Popup 页面上的各类表单从未出现过误判,说明插件核心功能在常规场景下工作正常。
第二步:定位到 Popup
问题只出现在 Elementor Popup 中加载的表单上。常见的 Popup 有两种加载方式:
- 页面内嵌 — Popup 内容直接在页面 HTML 中,用 CSS 隐藏,通过 JS 控制显示
- 动态加载 (AJAX) — Popup 内容通过 AJAX 请求获取,渲染到 DOM 中
我们的网站使用的是 AJAX 动态加载模式。这意味着 Popup 内的表单在页面加载时并不存在于 DOM 中。
第三步:分析 WP Armour 的注入机制
查看 WP Armour 的 JavaScript 代码(wpa.js 和 wpa_vanilla.js),发现它的工作流程是这样的:
页面加载 → 监听用户交互事件 (mousemove, keydown, scroll 等)
→ 用户交互触发 → 运行 wpa_add_honeypot_field()
→ 2 秒后备超时 → 同样运行 wpa_add_honeypot_field()
→ wpa_injected = true (锁定,不再重复注入)
wpa_add_honeypot_field() 的工作方式是在 DOM 中查找所有匹配选择器的表单,然后追加蜜罐字段:
function wpa_add_honeypot_field() {
var allFormSelectors = [
'form.wpcf7-form, .wpcf7 form',
'form.elementor-form',
'form.frm-fluent-form',
// ... 更多选择器
];
// 一次性查询所有现有表单并注入
document.querySelectorAll(allFormSelectors.join(', ')).forEach(...)
}
关键问题就在这里:querySelectorAll 只查询当前 DOM 中已存在的表单。AJAX 加载 Popup 时,表单被添加到 DOM 中,但 wpa_add_honeypot_field() 已经运行完毕,且 wpa_injected 已经锁定,不会再运行第二次。
第四步:确认蜜罐字段缺失
在浏览器开发者工具中检查 Elementor Popup 中的表单,对比普通页面的表单:
普通页面表单(正常):
<form class="elementor-form">
<!-- 正常表单字段 -->
<input type="text" name="name">
<input type="email" name="email">
<!-- 蜜罐字段 -->
<div id="altEmail_container" class="altEmail_container">
<label for="alt_s">Alternative:</label>
<input type="text" id="alt_s" name="alt_s">
</div>
<span class="wpa_hidden_field" style="display:none;height:0;width:0;">
<label>WPA <input type="text" name="wpa_field_xyz" value="12345"></label>
</span>
</form>
Popup 中的表单(异常):
<form class="elementor-form">
<!-- 只有正常表单字段,没有蜜罐 -->
<input type="text" name="name">
<input type="email" name="email">
<!-- ❌ wpa_hidden_field 和 altEmail_container 完全不存在 -->
</form>
第五步:验证 PHP 端的检测逻辑
然后看了 PHP 端如何判断是否为垃圾信息:
function wpa_check_is_spam($form_data) {
if (
(isset($form_data[$GLOBALS['wpa_field_name']])) && // ① 蜜罐隐藏字段必须在提交数据中
(isset($form_data['alt_s'])) && // ② alt_s 字段必须在提交数据中
(empty($form_data['alt_s'])) // ③ alt_s 必须为空
) {
return false; // ✅ 非垃圾
} else {
return true; // ❌ 判定为垃圾
}
}
三条检查全通过才算正常提交。因为 Popup 表单没有注入蜜罐字段,提交的数据中自然没有 wpa_field_name 和 alt_s,wpa_check_is_spam() 返回 true,”Spamming or your Javascript is disabled !!” 的错误就被抛出来了。
根因
WP Armour Honeypot 的 JS 注入逻辑设计为”一次性”的:
- 只在页面加载时(或用户首次交互时)运行一次
- 有
wpa_injected开关防止重复注入 - 没有监听 DOM 变化的机制
Elementor Popup 的 AJAX 动态加载模式,恰好撞上了这个设计盲区。不是 WP Armour 有问题,也不是 Elementor 有问题,而是两者没有意识到对方的存在。
修复方法
修复思路很简单:监听 Elementor Popup 打开事件,在 Popup 打开时对弹窗内的表单重新注入蜜罐字段。
Elementor 提供了 elementor/popup/show 事件,通过 elementorFrontend.hooks.addAction 可以注册回调:
elementorFrontend.hooks.addAction('elementor/popup/show', function(popupId, popupData, popupElement) {
// 获取弹窗容器
var popupEl = popupElement || document.querySelector('.elementor-' + popupId);
if (!popupEl) return;
// 在弹窗内查找所有表单
var forms = popupEl.querySelectorAll([
'form.elementor-form',
'form.wpcf7-form',
'form.frm-fluent-form',
// ...
].join(', '));
forms.forEach(function(form) {
// 如果已经存在蜜罐字段则跳过,防止重复注入
if (form.querySelector('.wpa_hidden_field')) return;
form.insertAdjacentHTML('beforeend', wpa_hidden_field);
});
});
完整的修复方案包括三个部分:
- Elementor 事件钩子 — 监听
elementor/popup/show,在弹窗打开时注入 - 重复注入防护 — 检查
.wpa_hidden_field是否已存在,支持弹窗多次打开而不重复注入 - MutationObserver 后备方案 — 对于非 Elementor 的动态加载场景(如 Beaver Builder、自定义 AJAX 表单),也能捕获
完整的可部署代码:
(function() {
function init() {
if (typeof wpa_add_honeypot_field !== 'function' || typeof wpa_hidden_field === 'undefined') {
setTimeout(init, 500);
return;
}
if (typeof elementorFrontend !== 'undefined' && elementorFrontend.hooks) {
elementorFrontend.hooks.addAction('elementor/popup/show', function(popupId, popupData, popupElement) {
var popupEl = popupElement || document.querySelector('.elementor-' + popupId);
if (!popupEl) return;
var formSelectors = [
'form.elementor-form',
'form.wpcf7-form', '.wpcf7 form',
'form.wpforms-form',
'.gform_wrapper form',
'.frm_forms form',
'form.frm-fluent-form',
'.ff_conv_app',
'form.wpa_form', '.wpa_form form',
'form#commentform', 'form.comment-form',
'form#loginform',
'form.register',
'form.cart'
];
var forms = popupEl.querySelectorAll(formSelectors.join(', '));
forms.forEach(function(form) {
if (form.querySelector('.wpa_hidden_field')) return;
form.insertAdjacentHTML('beforeend', wpa_hidden_field);
});
var initiators = popupEl.querySelectorAll('input.wpa_initiator');
initiators.forEach(function(initiator) {
var form = initiator.closest('form');
if (form && !form.querySelector('.wpa_hidden_field')) {
initiator.insertAdjacentHTML('afterend', wpa_hidden_field);
}
});
});
}
if (!window._wpaObserver) {
window._wpaObserver = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
mutation.addedNodes.forEach(function(node) {
if (node.nodeType !== 1) return;
var forms = node.matches && node.matches('form') ? [node] : node.querySelectorAll ? node.querySelectorAll('form') : [];
forms.forEach(function(form) {
if (!form.querySelector('.wpa_hidden_field')) {
form.insertAdjacentHTML('beforeend', wpa_hidden_field);
}
});
});
});
});
window._wpaObserver.observe(document.body, { childList: true, subtree: true });
}
}
init();
})();
安装方式:将代码添加到主题的 functions.php 中,或者使用自定义 JS 插件(如 WPCode):
add_action('wp_footer', function() { ?>
<script>
/* 粘贴上面的 JS 代码 */
</script>
<?php }, 999);
总结
这个问题的本质是一个典型的时序问题:WP Armour 假设所有表单都在页面加载时存在,而 Elementor Popup 打破了这一假设。
从这次的排查中学到几点:
- 安全插件的 JS 注入逻辑需要考虑动态内容。静态 DOM 查询在单页应用(SPA)和动态加载场景下会有盲区。MutationObserver 是处理这类问题的基础设施级方案。
- Elementor Popup 的事件系统很完善。
elementor/popup/show事件是处理 Popup 相关逻辑的入口点,比监听 DOM 变化更精确、更高性能。 - 给插件提建议时,带上代码。与其让作者自己理解问题再写代码,不如直接给他一个可合并的修复方案。WP Armour 的开发成本不高——让
wpa_add_honeypot_field()支持幂等调用(已注入的表单自动跳过),再加上 Elementor 事件监听,就能解决这个问题。