← Commit history

Position-independent, serialized birth-hwnd capture, the real fix: Chrome clamps our -32000 off-screen birth coordinate on-screen on some boxes, so identifying the new window by position fails (newOff was always 0). Now a global birth lock serializes window creation so the set-diff sees EXACTLY ONE new OS handle regardless of where Chrome placed it - that handle is the window, unambiguously, with no position or pid dependency. Combined with 2.0.37 routing the first window per profile through this same path, every pup window on Windows now gets a reliable unique handle, so AUMID and overlay paint and no window shows a raw Chrome button

John Lauer ·0af3e7c23e ·2mo ago ·parent 5518892
4 files changed +50−45
adom-bridge-puppeteer-manifest.json+4−4
@@ -1,10 +1,10 @@ {   "manifest_version": 1,   "name": "puppeteer",-  "version": "2.0.37",-  "url": "https://wiki.adom.inc/download/adom/pup-bridge/2.0.37/adom-bridge-puppeteer-v2.0.37.zip",-  "sha256": "5b1814b6f5be6a914e359afda9f9555f592f3f084fe17cc5e1133dc524745437",-  "size": 643519,+  "version": "2.0.38",+  "url": "https://wiki.adom.inc/download/adom/pup-bridge/2.0.38/adom-bridge-puppeteer-v2.0.38.zip",+  "sha256": "f134f98d89f5ede56ca8a777ca95d08c842589e9b3604bcefceca4ed03a61a1b",+  "size": 643374,   "verbPrefixes": [     "browser_",     "desktop_recorder_",
src/BRIDGE_VERSION+1−1
@@ -1 +1 @@-2.0.37+2.0.38
src/bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "puppeteer",   "displayName": "Puppeteer (Chrome for Testing + Native)",-  "version": "2.0.37",+  "version": "2.0.38",   "description": "Drives a browser ALREADY on the machine \u2014 installed Chrome, else Microsoft Edge (every Windows PC ships with Edge) \u2014 launched with a FRESH isolated profile, so the common case has NO ~150 MB Chrome-for-Testing download and it never touches the user's real tabs/logins. Binds its local server to LOOPBACK (127.0.0.1) only, so no Windows Firewall 'allow Node through the firewall' prompt ever pops. Chrome for Testing is fetched only as a LAST RESORT if the machine has no Chromium browser at all. SPAWN-VERIFIED fallback: pup tries each detected browser (Chrome \u2192 Edge \u2192 CfT) and actually launches it, falling through to the next if one won't start (and handling browsers like Edge that relaunch onto another PID), so a corrupt/partial install never hard-fails an open. The browser that launches is cached as the default (persisted across restarts) so it isn't re-probed each time. On spawn the bridge SELF-WARMS: detects browsers + disk, and only if the box has no Chrome/Edge and no cached CfT does it background-fetch CfT so the first open is instant. pup_use pins a specific browser (chrome | edge | cft | auto); {browser:'chrome',install:true} downloads & installs REAL Google Chrome (user-scoped, no admin) on an Edge-only box. pup_readiness (READ-ONLY) reports which browser will be used + the full candidate list + the default + free disk. Every failure returns a specific errorCode + _hint (chrome_install_no_disk, chrome_download_network, chrome_install_permission, chrome_stable_not_detected, \u2026). Launches with FULL browser permissions by default for full debug (strictPermissions:true opts out). Headful automation: open/close windows+tabs, navigate, screenshot, eval JS, record. pup is a GENERIC, non-profile browser only \u2014 every session is a fresh isolated Chrome/Edge process, never the user's real signed-in browser; profile defaults to sessionId. To drive the user's REAL Chrome/Edge (their profile, cookies, SSO), use the Adom browser EXTENSION's nbrowser_* verbs \u2014 a separate bridge, not pup's job. macOS (v1.9.74+): full parity with mac-native mechanics \u2014 background-by-default is the app-level hide (windows stay drivable/screenshottable while hidden), raise/lower map to unhide+activate/hide, pup_alert_window posts a native toast whose click focuses the window, pup_use {browser:'chrome',install:true} installs Chrome from the official dmg, and taskbar identity/badging remains Windows-only.",   "homepage": "https://wiki.adom.inc/adom/adom-bridge",   "author": "Adom Inc.",
src/server.js+44−39
@@ -326,7 +326,18 @@ const _launchInFlight = new Map(); // a fleet of AI threads opening default windows simultaneously is the 10,000-user case. const _profileOpenChain = new Map(); // profileName -> tail promise let _tabClaimSeq = 0; // monotonic per-call marker for in-window tab adoption (see addTabToSession)-let _birthSeq = 0;    // monotonic counter for unique off-screen birth positions (birth-time hwnd resolution)+let _birthSeq = 0;+// v2.0.38: SERIALIZE window creation so the set-diff birth capture always sees EXACTLY ONE new+// window handle (position is unreliable — Chrome clamps our -32000 birth coord on-screen, so we+// cannot identify the window by where it is; but "the one handle that appeared while I held the+// lock" is unambiguous regardless of position or concurrency across threads).+let _birthLock = Promise.resolve();+function withBirthLock(fn) {+  const prev = _birthLock;+  let release;+  _birthLock = new Promise(r => { release = r; });+  return prev.then(fn).finally(() => release());+}    // monotonic counter for unique off-screen birth positions (birth-time hwnd resolution) let _regenBusy = false;   // a dashboard "regenerate all overlays" sweep is in flight let _lastRegen = null;    // { at, done, swept, painted, changed, results[] } from the last sweep const _overlayRefreshTimers = new Map(); // per-session debounce for the centralized post-verb overlay re-sync@@ -7523,10 +7534,6 @@ async function createPageInNewWindow(browser, profileName) {   const birthLeft = -32000 - ((_birthSeq++ % 60) * 220);   // v2.0.35: 220px apart (was 40) so set-diff's nearest-match can't cross-wire adjacent births   try {     const before = new Set(await browser.pages());-    // v2.0.34: snapshot existing OS window handles BEFORE creating, so the SET-DIFF below identifies-    // the new window unambiguously — coordinate matching alone collided concurrent births.-    let _beforeHwnds = new Set();-    if (process.platform === 'win32') { try { _beforeHwnds = new Set((await enumChromeWidgetWindows()).map(r => Number(r.h))); } catch (e) {} }     cdp = await browser.target().createCDPSession();     // v1.9.144 (John: "you keep opening pup windows on top of the one i'm working in") — birth     // the window OFF-SCREEN so the moment between creation and the park is invisible. 1.9.135@@ -7541,43 +7548,41 @@ async function createPageInNewWindow(browser, profileName) {     // their screen. This is the at-the-source fix; parks and re-asserts then act on an unfocused     // window (osBottomUnlessForeground no longer mistakes the creation-grab for user intent).     const prevFg = await osGetForegroundWindow();-    await cdp.send('Target.createTarget', { url: 'about:blank', newWindow: true, left: birthLeft, top: -32000, width: 1280, height: 800 });-    let fresh = null;-    for (let i = 0; i < 50 && !fresh; i++) {-      fresh = (await browser.pages()).find(p => !before.has(p));-      if (!fresh) await new Promise(r => setTimeout(r, 100));-    }-    // Resolve the OS handle NOW, by the unique off-screen birth position, before park moves it on--    // screen. Parked siblings are at positive L; only newborns sit far off-screen, so "the off-screen-    // window of this profile at ~birthLeft" is unambiguous. Stash it on the page for the resolver.-    console.log(`[hwnd-dbg] birth block: fresh=${!!fresh} win32=${process.platform==='win32'} profile=${profileName} beforeHwnds=${_beforeHwnds.size} birthLeft=${birthLeft}`);-    if (fresh && process.platform === 'win32' && profileName) {-      try {-        // v2.0.34: SET-DIFF birth capture. The window that just APPEARED in the birth zone (off-screen,-        // and NOT present before createTarget, and not already owned by another session) is ours by-        // construction. If concurrency put several new windows there at once, disambiguate by MY unique-        // birthLeft with a TIGHT tolerance (births are 40px apart, so >18px would cross-wire — the-        // fusion+nb-shared-hwnd bug). No coordinate-only match, no pid filter.-        let picked = null;-        for (let i = 0; i < 15 && !picked; i++) {+    // v2.0.38: hold the birth lock across snapshot -> create -> capture so exactly one OS window+    // appears in the diff. `capturedHwnd` is set inside; the page-find stays outside is fine because+    // the page target is unique per createTarget regardless.+    let fresh = null, capturedHwnd = null;+    await withBirthLock(async () => {+      let beforeHwnds = new Set();+      if (process.platform === 'win32') { try { beforeHwnds = new Set((await enumChromeWidgetWindows()).map(r => Number(r.h))); } catch (e) {} }+      await cdp.send('Target.createTarget', { url: 'about:blank', newWindow: true, left: birthLeft, top: -32000, width: 1280, height: 800 });+      for (let i = 0; i < 50 && !fresh; i++) {+        fresh = (await browser.pages()).find(p => !before.has(p));+        if (!fresh) await new Promise(r => setTimeout(r, 100));+      }+      if (process.platform === 'win32') {+        for (let i = 0; i < 20 && !capturedHwnd; i++) {           const rows = await enumChromeWidgetWindows();-          const newOff = rows.filter(r => r.T < -10000 && !_beforeHwnds.has(Number(r.h)) && !hwndOwnedByOtherSession(r.h, null));-          if (i === 0 || i === 6) console.log(`[hwnd-dbg] iter ${i}: rows=${rows.length} offscreen=${rows.filter(r=>r.T<-10000).length} newOff=${newOff.length} sampleTop=${rows.slice(0,3).map(r=>r.T).join(',')}`);-          if (newOff.length === 1) picked = newOff[0].h;-          else if (newOff.length > 1) {-            // v2.0.35: pick the NEW off-screen window NEAREST my birthLeft (across DPI scales). Births-            // are 220px apart now, so nearest is unambiguous against Chrome's small placement jitter;-            // no hard tolerance to be too-tight (18px failed) or too-loose (60px cross-wired).-            let best = null, bestErr = 1e9;-            for (const r of newOff) for (const sc of [1, 1.25, 1.5, 1.75, 2]) { const e = Math.abs(r.L - Math.round(birthLeft * sc)); if (e < bestErr) { bestErr = e; best = r.h; } }-            if (best && bestErr <= 140) picked = best;+          const fresh2 = rows.filter(r => !beforeHwnds.has(Number(r.h)) && !hwndOwnedByOtherSession(r.h, null));+          if (fresh2.length === 1) { capturedHwnd = fresh2[0].h; break; }+          if (fresh2.length > 1) {+            // more than one appeared despite the lock (a sibling thread's stuck-off-screen leftover+            // enumerated late): prefer the off-screen one, else the one nearest my birthLeft.+            const off = fresh2.filter(r => r.T < -10000);+            const pool = off.length ? off : fresh2;+            let best = pool[0].h, bestErr = 1e9;+            for (const r of pool) for (const sc of [1, 1.25, 1.5, 1.75, 2]) { const e = Math.abs(r.L - Math.round(birthLeft * sc)); if (e < bestErr) { bestErr = e; best = r.h; } }+            capturedHwnd = best; break;           }-          if (!picked) await new Promise(r => setTimeout(r, 120));+          await new Promise(r => setTimeout(r, 120));         }-        if (picked) { fresh.__pupHwnd = picked; console.log(`[hwnd] birth-resolved new window on "${profileName}" -> ${picked} (off-screen L≈${birthLeft}, set-diff)`); }-        else console.log(`[hwnd] birth capture on "${profileName}" left AMBIGUOUS (concurrent births) — will resolve later`);-      } catch (e) { console.log(`[hwnd-dbg] birth block THREW: ${e.message}`); }-    }+      }+    });+    // Resolve the OS handle NOW, by the unique off-screen birth position, before park moves it on-+    // screen. Parked siblings are at positive L; only newborns sit far off-screen, so "the off-screen+    // window of this profile at ~birthLeft" is unambiguous. Stash it on the page for the resolver.+    if (fresh && capturedHwnd) { fresh.__pupHwnd = capturedHwnd; console.log(`[hwnd] birth-resolved new window on "${profileName}" -> ${capturedHwnd} (locked set-diff, birthLeft ${birthLeft})`); }+    else if (fresh) console.log(`[hwnd] birth capture on "${profileName}" produced no handle — will resolve later`);     try {       const nowFg = await osGetForegroundWindow();       // Diagnostic only. The playbook (dev-skills/pup-bridge-dev, SOLVED PLAYBOOK) says this can