Browser Page Lock
Purpose
The WhatsApp backend shares a single Puppeteer page (headless browser tab) across multiple services. Without coordination, concurrent operations can read wrong DOM state or interfere with navigation. The page lock provides mutual exclusion.
Problem
Multiple services access the same browser page:
| Service | Operation | DOM Impact |
|---|---|---|
| Deep sync | Types in search box, navigates to chat, scrolls | Changes visible page completely |
| Archived chats | Clicks archive button, reads list, clicks back | Changes visible page completely |
| Avatar retry | Reads chat list for avatar URLs | Read-only, but gets garbage if page is navigated |
| Backfill | Reads contacts and messages from DOM | Read-only, same garbage risk |
| getChats() | Reads current chat list grid | Read-only, same garbage risk |
If deep sync is running and avatar retry fires simultaneously, the retry reads the search-filtered DOM and caches wrong data.
Solution
A mutex-style lock on DOMReader:
private _pageBusy = false;
private _pageBusyReason = '';
acquirePageLock(reason: string): boolean {
if (this._pageBusy) return false;
this._pageBusy = true;
this._pageBusyReason = reason;
return true;
}
releasePageLock(): void {
this._pageBusy = false;
this._pageBusyReason = '';
}
Lock Usage
| Operation | Behavior |
|---|---|
deepLoadChat() |
Acquires lock at start, releases in finally block |
getArchivedChats() |
Acquires lock at start, releases in finally block |
getChats() |
Checks lock — returns [] if busy |
AvatarRetryService.tick() |
Checks pageBusy — skips entire cycle if busy |
Rule: Navigation operations (that change visible page) acquire exclusive access. Read-only operations (that scan current state) bail immediately if locked.
Search Clear Safety
After deep sync navigates away, clearSearch() restores the normal chat list:
- Detects if search is active (back arrow or text in search box)
- Clicks back arrow if visible (most reliable)
- Falls back to Escape key press
- Retries up to 5 times with 500ms delays
- Final fallback: directly clears search box content
- Verifies search is actually cleared before returning
Used in both success and error paths of deepLoadChat().
Key Files
| File | Role |
|---|---|
backend/src/services/DOMReader.ts |
Page lock, clearSearch(), deepLoadChat(), getArchivedChats() |
backend/src/services/AvatarRetryService.ts |
Checks pageBusy before scanning |
backend/src/controllers/mediaController.ts |
Deep sync integration |
Status
Complete and deployed. All concurrent browser operations are safely coordinated.