Impossible to login on our iPad

On an ipad, we are no longer able to log into one of our accounts on both Safari and Chrome. The app hangs on Loading Readlang… indefinitely. The annoying part is the only way to escape from this to try to address the issue is to clear all cookies and wait for some expiry to occur on the server. Only then can you attempt to login again. I tried resetting the password, and that did not help. On this device, login has become impossible. However login to this account still works on other devices.

Here is a more complete bug report that should help to resolve this issue.

Summary: Login hangs indefinitely on iOS 15 (all browsers) due to an unhandled TypeError when initializing speech synthesis, which appears to interrupt the post-login script execution.

Environment:

  • Device: iPad (older model, capped at iOS 15)
  • OS: iPadOS 15.8.8
  • Browsers tested: Safari and Chrome — both fail identically (both use WebKit under the hood on iOS, so this is engine-level, not browser-specific)
  • Other devices/browsers: login works normally

Symptom:
After entering credentials, the app hangs while “Loading Readlang…” is shown in the startSignedIn route. No error is shown to the user. The only way to recover is to close the browser and clear all site cookies/data for readlang.com. Reloading, navigating to another URL, or returning to root without clearing data all reproduce the same hang.

Diagnostic steps taken:

  1. Ruled out Intelligent Tracking Prevention / cross-site cookie blocking as the cause — reproduced with Private Browsing mode and again with “Prevent Cross-Site Tracking” fully disabled in Safari settings. No change.
  2. Connected the iPad to a Mac and used Safari’s remote Web Inspector (Develop menu → device → tab) to capture the Console and Network output while reproducing the hang.
  3. Console output showed an uncaught exception at the point of failure:
TypeError: window.speechSynthesis.addEventListener is not a function.
(In 'window.speechSynthesis.addEventListener("voiceschanged", kl)', 'window.speechSynthesis.addEventListener' is undefined)

Root cause:
On this WebKit version, window.speechSynthesis does not implement EventTarget, so it has no addEventListener/removeEventListener methods — only the legacy onvoiceschanged property assignment is supported. The app calls addEventListener("voiceschanged", ...) unconditionally, likely during startup/post-login initialization (probably for read-aloud/pronunciation support). The resulting uncaught TypeError appears to halt whatever script is running at that point, which manifests as the login flow hanging with no visible error to the user.

Suggested fix:
Feature-detect before calling addEventListener on speechSynthesis, falling back to the legacy property style:

js

if (window.speechSynthesis.addEventListener) {
  window.speechSynthesis.addEventListener("voiceschanged", kl);
} else {
  window.speechSynthesis.onvoiceschanged = kl;
}

This should resolve the hang for all users on this WebKit version, since the issue is engine-level and affects every iOS browser (Safari, Chrome, Firefox, Edge, etc. on iOS all use WebKit), not just Safari specifically.

Update: Confirmed the root cause and a working client-side workaround.

Using Safari’s remote Web Inspector against an affected iPad (iPadOS 15.8.8), I captured the exact uncaught exception occurring during login:

TypeError: window.speechSynthesis.addEventListener is not a function.
(In 'window.speechSynthesis.addEventListener("voiceschanged", kl)', 'window.speechSynthesis.addEventListener' is undefined)

On this WebKit version, speechSynthesis doesn’t implement EventTarget, so addEventListener/removeEventListener aren’t available — only the legacy onvoiceschanged property assignment is. The unhandled TypeError appears to halt script execution at a point that blocks the login flow from completing, which is why it presents as an indefinite hang with no visible error.

As a test, I injected the following polyfill via a userscript manager, and it fully resolved the hang — login now completes normally:

js

if (window.speechSynthesis && !window.speechSynthesis.addEventListener) {
  window.speechSynthesis.addEventListener = function(type, listener) {
    if (type === "voiceschanged") window.speechSynthesis.onvoiceschanged = listener;
  };
  window.speechSynthesis.removeEventListener = function(type) {
    if (type === "voiceschanged") window.speechSynthesis.onvoiceschanged = null;
  };
}

This confirms the fix suggested earlier (feature-detecting addEventListener support before calling it, with an onvoiceschanged fallback) should resolve this for all affected users. Since all iOS browsers share the WebKit engine, this affects every browser on this OS version, not just Safari specifically.