DocHub
Mutex-style lock on DOMReader to coordinate concurrent operations on the shared Puppeteer page

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:

  1. Detects if search is active (back arrow or text in search box)
  2. Clicks back arrow if visible (most reliable)
  3. Falls back to Escape key press
  4. Retries up to 5 times with 500ms delays
  5. Final fallback: directly clears search box content
  6. 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.