-
-
Notifications
You must be signed in to change notification settings - Fork 4.9k
Description
Describe the bug.
After a system sleep/resume cycle, Chrome may perform an internal page navigation to recover corrupted IndexedDB storage. This navigation destroys the current execution context.
In inject(), the polling loops that wait for window.Debug?.VERSION and window.Store use page.evaluate() repeatedly. When the execution context is destroyed mid-poll, page.evaluate() throws "Execution context was destroyed", causing inject() to fail.
Additionally, the framenavigated listener (which calls inject() again after navigation) is registered after the initial inject() call in initialize(). This means if inject() fails due to navigation, the recovery mechanism is not yet in place.
Expected Behavior
inject() should survive internal page navigations (such as IndexedDB recovery) and continue polling in the new execution context without throwing.
Steps to Reproduce the Bug or Issue
- Connect a WhatsApp session using
LocalAuth - Put the system to sleep for a few minutes
- Resume the system
- Chrome performs an internal navigation to recover IndexedDB
inject()throws"Execution context was destroyed"or"auth timeout"- The client disconnects and may show a QR code on the next attempt
The root cause can also be reproduced programmatically:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({ headless: 'new' });
const page = await browser.newPage();
await page.setContent('<html><body></body></html>');
// Start a long-running evaluate (simulates polling mid-navigation)
const evalPromise = page.evaluate(async () => {
await new Promise(r => setTimeout(r, 5000));
return 'done';
}).catch(err => console.log('evaluate threw:', err.message));
// Navigate while evaluate is running (simulates IndexedDB recovery)
await new Promise(r => setTimeout(r, 200));
await page.goto('data:text/html,<html><body>New Page</body></html>');
await evalPromise;
// Output: evaluate threw: Execution context was destroyed.
// In contrast, waitForFunction survives navigation:
await page.setContent('<html><body></body></html>');
const waitPromise = page.waitForFunction(
'window.testReady === true',
{ timeout: 10000 }
);
await new Promise(r => setTimeout(r, 200));
await page.goto('data:text/html,<html><body><script>window.testReady = true;</script></body></html>');
await waitPromise;
console.log('waitForFunction survived navigation!');
await browser.close();
})();WhatsApp Account Type
Standard
Browser Type
Chromium (bundled with Puppeteer)
Operation System Type
Tested on Windows 11 and Linux (Ubuntu)
Phone OS Type
Android
WhatsApp-Web.js Version
1.26.1-alpha.3
WhatsApp Web Version
2.3000+
Node.js Version
v18+
Authentication Strategy
LocalAuth
Additional Context
Root cause analysis:
page.evaluate() is bound to a single execution context. When Chrome navigates internally (e.g., for IndexedDB recovery), that context is destroyed and evaluate() throws.
page.waitForFunction() is designed to survive navigation. Internally, Puppeteer's WaitTask treats "Execution context was destroyed" as a non-fatal error (returns undefined from getBadError()), and when a new context is created, IsolatedWorld calls taskManager.rerunAll() to re-evaluate all waiting tasks in the new context.
Fix (3 changes in src/Client.js):
- Replace the manual
page.evaluate()polling loop forDebug.VERSIONwithpage.waitForFunction() - Replace the manual
page.evaluate()polling loop forStorewithpage.waitForFunction() - Move the
framenavigatedlistener registration to beforeinject()ininitialize(), so the recovery mechanism is in place from the start
This fix uses no retry logic, no try/catch wrapping, and no error swallowing. It simply uses the correct Puppeteer API that natively handles execution context lifecycle.
Verified with Puppeteer 18.x (used by wwjs main) and 24.x - both versions have the same WaitTask resilience mechanism.
Included E2E tests that deterministically prove:
page.evaluate()throws during navigation (the bug)page.waitForFunction()survives navigation (the fix)- The full fix (both mechanisms together) works correctly