Skip to content

fix: defer reCAPTCHA load to renderReCaptcha with idle fallback for iframe compat - #7

Closed
rhoerr wants to merge 2 commits into
mainfrom
fix/recaptcha-idle-fallback
Closed

fix: defer reCAPTCHA load to renderReCaptcha with idle fallback for iframe compat#7
rhoerr wants to merge 2 commits into
mainfrom
fix/recaptcha-idle-fallback

Conversation

@rhoerr

@rhoerr rhoerr commented Mar 18, 2026

Copy link
Copy Markdown
Contributor

The reCAPTCHA deferred loading in #6 broke iframe-embedded payment forms (e.g. Braintree hosted fields) and had a DOM timing bug where the focus listener never actually attached.

This PR:

  • Moves deferred load from initialize() to renderReCaptcha() — the KO afterRender callback, where the form DOM actually exists
  • Focus on form fields triggers eager API load (preserves Load recaptcha related files on focus of the form fields. #6's performance goal)
  • requestIdleCallback with 3s timeout (or setTimeout(1s) fallback) ensures the API loads for iframes and no-focus scenarios
  • Falls back to immediate load when no parent <form> is found, instead of silently deadlocking
  • Uses $(window).one() for recaptchaapiready to prevent handler accumulation

Problems with the previous approach (#6)

  1. attachFocusEvent() ran in initialize() before KO rendered the template
  2. Iframe-embedded fields (Braintree, PayPal hosted fields) never fire focus on the parent form
  3. $(window).on('recaptchaapiready') accumulated handlers on repeated renderReCaptcha calls (like reCaptchaStorePickup)

Compatibility notes

  • Checkout with iframe payment methods: Should work — idle callback fires within 3s max
  • Simple forms (contact, newsletter, login): Focus-based eager load still works as intended
  • reCaptchaStorePickup: _apiLoaded guard prevents double _deferApiLoad; .one() prevents handler leak
  • reCaptchaCheckout clones: Inherit _apiLoaded state correctly; no behavior change
  • No parent form edge case: Loads API immediately instead of permanent deadlock

Ref: #6 (comment)

Test plan

  • Enable reCAPTCHA (any version) for a frontend form (contact, newsletter, login)
  • Verify reCAPTCHA script is NOT loaded on initial page load (check Network tab)
  • Click into a form field — verify reCAPTCHA loads and widget appears
  • Reload page, do NOT interact — verify reCAPTCHA loads within ~3 seconds via idle callback
  • Test checkout with Braintree (or other iframe-based payment) — verify reCAPTCHA loads and Place Order works
  • Test store pickup reCAPTCHA — verify widget renders on address change without console errors
  • Test with ad blocker blocking Google reCAPTCHA domain — verify form behavior is no worse than before

The focus-based deferral from #6 ran in initialize() before KnockoutJS
rendered the template, so the DOM query for the parent form always
returned empty — the focus listener never attached. Additionally,
iframe-embedded payment fields (e.g. Braintree hosted fields) never
fire focus events on the parent form, so reCAPTCHA never loaded for
those checkout flows.

Move the deferred load into renderReCaptcha() (called from KO
afterRender) where the DOM actually exists:

- Focus on any form field triggers an eager API load
- requestIdleCallback (capped at 3s) or setTimeout(1s) fallback
  ensures the API loads for iframes and no-focus scenarios
- When no parent form is found, fall back to immediate load instead
  of silently deadlocking
- Use $(window).one() for the recaptchaapiready listener to prevent
  handler accumulation on repeated renderReCaptcha calls

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@rhoerr
rhoerr requested a review from a team as a code owner March 18, 2026 00:58
@rhoerr

rhoerr commented Mar 18, 2026

Copy link
Copy Markdown
Contributor Author

Lucas van Staden says it tests well with this fix:

control test: mage-os as released:

10:22 $ NODE_TLS_REJECT_UNAUTHORIZED=0 APP_NAME=pps  TEST_BASE=checkout npx playwright test --project=chromium --retries=0 --workers=1
No Pi-hole configuration found. Skipping Pi-hole disable.

Running 3 tests using 1 worker

  ✘  1 [chromium] › ../checkout/tests/checkout_braintree.spec.ts:14:9 › Checkout braintree with one Item in cart › it can checkout braintree (1.2m)
  ✓  2 [chromium] › ../checkout/tests/checkout_existing_account_prompt.spec.ts:8:9 › Checkout - detects existing account for logon › Check existing email triggers logon prompt (26.8s)
  ✓  3 [chromium] › ../checkout/tests/checkout.spec.ts:13:9 › Checkout actions with one Item in cart › it can proceed to checkout from cart page (34.1s)
Running global teardown...

The patch applied:

11:23 $ NODE_TLS_REJECT_UNAUTHORIZED=0 APP_NAME=pps  TEST_BASE=checkout npx playwright test --project=chromium --retries=0 --workers=1
No Pi-hole configuration found. Skipping Pi-hole disable.

Running 3 tests using 1 worker

  ✓  1 [chromium] › ../checkout/tests/checkout_braintree.spec.ts:14:9 › Checkout braintree with one Item in cart › it can checkout braintree (1.2m)
  ✓  2 …kout_existing_account_prompt.spec.ts:8:9 › Checkout - detects existing account for logon › Check existing email triggers logon prompt (29.1s)
  ✓  3 …omium] › ../checkout/tests/checkout.spec.ts:13:9 › Checkout actions with one Item in cart › it can proceed to checkout from cart page (37.6s)
Running global teardown...
No Pi-hole configuration found. Skipping Pi-hole enable.

  3 passed (2.4m)

Markdown report: /var/www/html/tests/m2-hyva-playwright/test-results/pps/pps-checkout-reports/test-report.md

To open last HTML report run:

  npx playwright show-report ../../../test-results/pps/pps-checkout-reports/playwright-report --host pvcpeipesupplies.ddev.site

so looks good to me. manual test also passed.

@rhoerr

rhoerr commented Mar 18, 2026

Copy link
Copy Markdown
Contributor Author

In further discussion, Lucas can't reproduce issues with these changes but is still seeing anomalous behavior / logged errors / possibly lower order volume. I'm going to defer this at least for now and roll back #6.

@mage-os-ci
mage-os-ci marked this pull request as draft March 26, 2026 01:25
@rhoerr rhoerr closed this Apr 19, 2026
@rhoerr
rhoerr deleted the fix/recaptcha-idle-fallback branch April 19, 2026 20:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant