← Commit history

v1.8.32: WS_EX_NOACTIVATE on background windows = deterministic no-focus-steal (never still-foreground); raise clears the style

John Lauer ·c8db488047 · ·parent 337b651
4 files changed +30−11
adom-bridge-puppeteer-manifest.json+5−5
@@ -1,10 +1,10 @@ {   "manifest_version": 1,   "name": "puppeteer",-  "version": "1.8.31",-  "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.8.31/adom-bridge-puppeteer-v1.8.31.zip",-  "sha256": "1fcc521481a903860af1dbf241057680e1ac60faa2da95147c8c105504c76d38",-  "size": 156260,+  "version": "1.8.32",+  "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.8.32/adom-bridge-puppeteer-v1.8.32.zip",+  "sha256": "671fc9d872f35fbb8975d3900c1d91ce7e2c0c1fd07c3b3ec9e307cc021697d7",+  "size": 156768,   "verbPrefixes": [     "browser_",     "desktop_recorder_",@@ -12,5 +12,5 @@   ],   "healthEndpoint": "/status",   "statusVerb": "browser_readiness",-  "released_at": "2026-07-14T19:40:23Z"+  "released_at": "2026-07-14T19:48:47Z" }\ No newline at end of file
src/BRIDGE_VERSION+1−1
@@ -1 +1 @@-1.8.31+1.8.32
src/bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "puppeteer",   "displayName": "Puppeteer (Chrome for Testing + Native)",-  "version": "1.8.31",+  "version": "1.8.32",   "description": "Drives a browser ALREADY on the machine — installed Chrome, else Microsoft Edge (every Windows PC ships with Edge) — 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 → Edge → 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. browser_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. browser_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, …). 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 — 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 — a separate bridge, not pup's job.",   "homepage": "https://wiki.adom.inc/adom/adom-desktop",   "author": "Adom Inc.",
src/server.js+23−4
@@ -1526,6 +1526,8 @@ function osBackgroundWindowByPid(pid, cdpPort) {       '  [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr h, int n);',       '  [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();',       '  [DllImport("user32.dll")] public static extern int GetSystemMetrics(int i);',+      '  [DllImport("user32.dll")] public static extern int GetWindowLong(IntPtr h, int i);',+      '  [DllImport("user32.dll")] public static extern int SetWindowLong(IntPtr h, int i, int v);',       '}',       '"@',       `$targetPid = ${Number(pid) || 0}`,@@ -1554,7 +1556,18 @@ function osBackgroundWindowByPid(pid, cdpPort) {       // time out, so it must stay shown), then MOVE it ON-SCREEN sized to the monitor at the       // BOTTOM of the z-order. Result: fully behind the user's windows (invisible, never       // covering) BUT reachable — its taskbar button brings it up on click, like any window.+      '$GWL_EXSTYLE = -20',+      '$WS_EX_NOACTIVATE = 0x08000000',       'foreach($h in $handles){',+      // v1.8.32: make the window NON-ACTIVATABLE (WS_EX_NOACTIVATE) so it can never take+      // foreground/keyboard focus, even when shown or clicked in the background. This is+      // what makes background-by-default DETERMINISTIC (no more flaky "still-foreground"):+      // the browser grabbed focus on launch, so we minimize ONCE to hand it back, then the+      // NOACTIVATE style guarantees the re-show + on-screen move can never re-grab it. pup+      // drives via CDP (isTrusted protocol input), which does not need OS activation, so the+      // window stays fully clickable-from-taskbar (browser_raise clears the style to show it).+      '  $ex = [WinBgP]::GetWindowLong($h, $GWL_EXSTYLE)',+      '  [WinBgP]::SetWindowLong($h, $GWL_EXSTYLE, ($ex -bor $WS_EX_NOACTIVATE)) | Out-Null',       '  [WinBgP]::ShowWindow($h, $SW_MINIMIZE) | Out-Null',       '  [WinBgP]::ShowWindow($h, $SW_SHOWNOACTIVATE) | Out-Null',       '  [WinBgP]::SetWindowPos($h, $BOTTOM, 0, 0, $scrW, $scrH, $SWP) | Out-Null',@@ -3374,20 +3387,26 @@ const server = http.createServer(async (req, res) => {                 '  [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr hWnd);',                 '  [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);',                 '  [DllImport("user32.dll")] public static extern bool IsIconic(IntPtr hWnd);',+                '  [DllImport("user32.dll")] public static extern int GetWindowLong(IntPtr h, int i);',+                '  [DllImport("user32.dll")] public static extern int SetWindowLong(IntPtr h, int i, int v);',+                // v1.8.32: background windows carry WS_EX_NOACTIVATE (they can never take focus).+                // To actually SHOW one, CLEAR that style first, then restore + foreground it.+                '  public static void Raise(IntPtr h){',+                '    int ex = GetWindowLong(h, -20); SetWindowLong(h, -20, ex & ~0x08000000);',+                '    if (IsIconic(h)) ShowWindow(h, 9); ShowWindow(h, 5); SetForegroundWindow(h);',+                '  }',                 '}',                 '"@',                 `$targetPid = ${pid}`,                 '$p = Get-Process -Id $targetPid -ErrorAction SilentlyContinue',                 'if ($p -and $p.MainWindowHandle -ne 0) {',-                '  if ([WinFocus]::IsIconic($p.MainWindowHandle)) { [WinFocus]::ShowWindow($p.MainWindowHandle, 9) }',-                '  [WinFocus]::SetForegroundWindow($p.MainWindowHandle)',+                '  [WinFocus]::Raise($p.MainWindowHandle)',                 "  Write-Output 'focused'",                 '} else {',                 '  Get-WmiObject Win32_Process | Where-Object { $_.ParentProcessId -eq $targetPid -and $_.Name -eq "chrome.exe" } | ForEach-Object {',                 '    $cp = Get-Process -Id $_.ProcessId -ErrorAction SilentlyContinue',                 '    if ($cp -and $cp.MainWindowHandle -ne 0) {',-                '      if ([WinFocus]::IsIconic($cp.MainWindowHandle)) { [WinFocus]::ShowWindow($cp.MainWindowHandle, 9) }',-                '      [WinFocus]::SetForegroundWindow($cp.MainWindowHandle)',+                '      [WinFocus]::Raise($cp.MainWindowHandle)',                 "      Write-Output 'focused'",                 '      return',                 '    }',