← Commit history

v2.0.718 (insiders): The owner chip is off on desktops that had the old default saved (a stored "on" from before 2.0.713 is turned off once; a later choice is kept). Restored windows keep their videos paused however they come back and never wait on it (2.0.717). Input reaches isolated and named-profile windows (2.0.714). The foreground and jump-list gates run beside other threads' windows without touching them. Gates: smoke all pass on arav-rog and ConfRoomROG; foreground pass, parity 55/56 and jump list 11/14 (baseline, Close ALL skipped in shared mode) on arav-rog; other threads' windows unchanged before and after.

John Lauer ·7f851e39b3 ·29d ago ·parent 6ab4424
7 files changed +55−20
rust/Cargo.lock+12−12
@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"  [[package]] name = "adom-pup-bridge"-version = "2.0.713"+version = "2.0.718" dependencies = [  "pup-browser",  "pup-cdp",@@ -655,7 +655,7 @@ dependencies = [  [[package]] name = "pup-browser"-version = "2.0.713"+version = "2.0.718" dependencies = [  "pup-core",  "pup-host",@@ -667,7 +667,7 @@ dependencies = [  [[package]] name = "pup-cdp"-version = "2.0.713"+version = "2.0.718" dependencies = [  "serde_json",  "thiserror",@@ -677,7 +677,7 @@ dependencies = [  [[package]] name = "pup-core"-version = "2.0.713"+version = "2.0.718" dependencies = [  "serde",  "serde_json",@@ -687,7 +687,7 @@ dependencies = [  [[package]] name = "pup-dashboard"-version = "2.0.713"+version = "2.0.718" dependencies = [  "pup-cdp",  "pup-core",@@ -700,7 +700,7 @@ dependencies = [  [[package]] name = "pup-host"-version = "2.0.713"+version = "2.0.718" dependencies = [  "serde",  "serde_json",@@ -709,14 +709,14 @@ dependencies = [  [[package]] name = "pup-host-mac"-version = "2.0.713"+version = "2.0.718" dependencies = [  "pup-host", ]  [[package]] name = "pup-host-win"-version = "2.0.713"+version = "2.0.718" dependencies = [  "pup-core",  "pup-host",@@ -726,7 +726,7 @@ dependencies = [  [[package]] name = "pup-identity"-version = "2.0.713"+version = "2.0.718" dependencies = [  "fontdue",  "image",@@ -736,7 +736,7 @@ dependencies = [  [[package]] name = "pup-session"-version = "2.0.713"+version = "2.0.718" dependencies = [  "pup-core",  "serde",@@ -746,7 +746,7 @@ dependencies = [  [[package]] name = "pup-vault"-version = "2.0.713"+version = "2.0.718" dependencies = [  "pup-core",  "pup-host",@@ -757,7 +757,7 @@ dependencies = [  [[package]] name = "pup-verbs"-version = "2.0.713"+version = "2.0.718" dependencies = [  "base64",  "png 0.17.16",
rust/Cargo.toml+1−1
@@ -4,7 +4,7 @@ members = ["core", "host", "host-win", "host-mac", "cdp", "browser", "session",  # One version for the whole bridge, in lockstep with src/BRIDGE_VERSION (tools/ship.sh bumps both). [workspace.package]-version = "2.0.713"+version = "2.0.718" edition = "2021" license = "MIT" 
rust/verbs/src/extdrive.rs+1
@@ -196,6 +196,7 @@ pub fn reload_stale_worker(ctx: &Ctx) {         toast(format!("pup is updating to {to}"), format!("Your {} pup window{} will close and come back in about a minute, on the same pages. Nothing has crashed.", ids.len(), if ids.len() == 1 { "" } else { "s" }));     }     for id in &ids { ctx.sessions.remove(id); }+    crate::recovery::mark_browser_restarted(&cur_profile());   // 2.0.717: re-adopted windows get the media guard too     let n = crate::kill_extdrive(ctx);     if n > 0 { *ASKED.lock().unwrap() = Some(want.clone()); }   // 2.0.690: done only once the old browser really ended     // 2.0.650: WAIT for the windows to be gone before relaunching. The kill is asynchronous; relaunch_from_file saw
rust/verbs/src/extras.rs+1
@@ -282,6 +282,7 @@ pub fn move_to_workspace(ctx: &Ctx, caller: &Caller, args: &Value) -> Value { /// process by hand and retyping 20 tab URLs out of a log. pub fn restart_browser(ctx: &Ctx, caller: &Caller, args: &Value) -> Value {     let profile = args.get("profile").and_then(|p| p.as_str()).unwrap_or("adom-you").to_string();+    crate::recovery::mark_browser_restarted(&profile);   // 2.0.717: windows Chrome brings back itself get the media guard too     let sessions = ctx.sessions.for_profile(&profile);     if sessions.is_empty() && crate::browser_for(ctx, &profile).is_none() {         return err("no_browser", format!("no browser is running for profile \"{profile}\""), Some("pup_status lists the live profiles."));
rust/verbs/src/input.rs+5
@@ -27,6 +27,11 @@ pub fn resolve_tab(ctx: &Ctx, caller: &Caller, args: &Value) -> Result<(Session,     let ext = crate::transport::is_ext(&s);     let by_url = args.get("_tabUrl").and_then(|u| u.as_str()).and_then(|u| crate::session_for_tab_url(ctx, u)).filter(|(w, _)| w.id == s.id).map(|(_, t)| t);     let t = args.get("tabId").and_then(|t| t.as_str()).map(|id| s.tab_by_id(id).cloned()).unwrap_or_else(|| by_url.or_else(|| s.active().cloned())).ok_or_else(|| err("no_tab", "the window has no tabs", None))?;+    // 2.0.714: talk to the window's OWN browser. Without this every input verb on an isolated or named-profile window+    // (click, type, keys, mouse) went down the MAIN browser's channel: typing "capacitor" into an isolated window came+    // back "selector not found" because it searched the other browser's page (found by the shared-box jump-list gate,+    // 2026-10-12). lane_for has done this since 2.0.659; resolve_tab never did.+    if ext { crate::extdrive::enter_profile(&s.profile); }     let b = if ext { crate::extdrive::ext_handle() } else { browser_for(ctx, &s.profile).ok_or_else(|| err("browser_gone", "the browser behind this window is not connected", Some("pup_close_window then pup_open_window; recovery reconnects live Chromes at boot.")))? };     crate::focus_emulate(ctx, &s.id, Some(&t.tab_id));   // 2.0.458: this tab is the one pup is driving     Ok((s, t, b))
rust/verbs/src/recovery.rs+29−5
@@ -87,6 +87,25 @@ struct ProfileScope; impl ProfileScope { fn enter(profile: &str) -> ProfileScope { crate::extdrive::enter_profile(profile); ProfileScope } } impl Drop for ProfileScope { fn drop(&mut self) { crate::extdrive::leave_profile(); } } +/// 2.0.717: profiles whose browser pup restarted on purpose in the last 2 min. Chrome can bring such windows back+/// itself (its own session restore); pup then RE-ADOPTS them instead of relaunching, and the relaunch path was the only+/// one that paused their autoplaying media. A plain pup restart (the browser kept running) is not in this set, so a+/// video someone is watching is never paused by it.+static RESTARTED: std::sync::Mutex<Vec<(String, u64)>> = std::sync::Mutex::new(Vec::new());+pub fn mark_browser_restarted(profile: &str) { let now = pup_core::now_ms(); let mut m = RESTARTED.lock().unwrap(); m.retain(|(p, t)| p != profile && now.saturating_sub(*t) < 120_000); m.push((profile.to_string(), now)); }+fn browser_restarted_recently(profile: &str) -> bool { let now = pup_core::now_ms(); RESTARTED.lock().unwrap().iter().any(|(p, t)| p == profile && now.saturating_sub(*t) < 120_000) }+/// Pause autoplaying media in a restored window's tabs, in the background (see quietMedia in worker.js).+fn quiet_restored(id: &str, profile: &str, wid: i64) {+    let (id2, prof2) = (id.to_string(), profile.to_string());+    std::thread::spawn(move || {+        crate::extdrive::enter_profile(&prof2);+        match crate::extdrive::run(json!({ "action": "quietMedia", "windowId": wid, "ms": 30000, "timeoutMs": 20000 }), Duration::from_secs(24)) {+            Ok(q) => pup_core::log(format!("[recovery] \"{id2}\": restored tabs keep their media paused for 30 s ({} tab(s) guarded, {} paused at once)", q["guarded"], q["paused"])),+            Err(e) => pup_core::log(format!("[recovery] \"{id2}\": could not quiet the restored tabs: {}", e["error"].as_str().unwrap_or("?"))),+        }+    });+}+ fn readopt_ext(ctx: &Ctx, f: &Value) -> bool {     if f["transport"].as_str() != Some("ext") { return false; }     let _scope = ProfileScope::enter(f["profile"].as_str().unwrap_or("adom-you"));@@ -131,6 +150,7 @@ fn readopt_ext(ctx: &Ctx, f: &Value) -> bool {         if let Some(a) = ctx.arc() { crate::identity::schedule(a, s.id.clone(), crate::identity::internal_caller(), true); pup_core::log(format!("[recovery] \"{id}\": its tile is from an older drawing; re-stamping it")); }     }     pup_core::log(format!("[recovery] \"{id}\": re-adopted its extension-lane window (Chrome window {wid}, {} tab(s), hwnd {:?}); nothing relaunched", s.tabs.len(), s.hwnd));+    if browser_restarted_recently(&s.profile) { quiet_restored(&id, &s.profile, wid); }     crate::extdrive::rebind_unbound(ctx);   // stamp the re-adopted window NOW; the health tick is only a backstop     true }@@ -248,11 +268,7 @@ pub fn relaunch_from_file(ctx: &Ctx, f: &Value) {     // tabs for 30 s; anything the user starts by clicking plays normally.     if r["ok"] == json!(true) {         if let Some(wid) = ctx.sessions.get(&id).filter(|s| crate::transport::is_ext(s)).and_then(|s| s.window_id) {-            let _scope = ProfileScope::enter(&profile);-            match crate::extdrive::run(json!({ "action": "quietMedia", "windowId": wid, "ms": 30000, "timeoutMs": 40000 }), Duration::from_secs(45)) {-                Ok(q) => pup_core::log(format!("[recovery] \"{id}\": restored tabs keep their media paused for 30 s ({} tab(s) guarded, {} paused at once)", q["guarded"], q["paused"])),-                Err(e) => pup_core::log(format!("[recovery] \"{id}\": could not quiet the restored tabs: {}", e["error"].as_str().unwrap_or("?"))),-            }+            quiet_restored(&id, &profile, wid);   // 2.0.715: in the background; the next window's restore never waits on it         }     }     // 2.0.471: a relaunch that WORKED resets the counter. It counted every relaunch in the bridge's life, so a window@@ -333,6 +349,14 @@ pub fn adopt_window_tabs(ctx: &Ctx, h: &Arc<BrowserHandle>) -> usize { }  pub fn recover_at_boot(ctx: &Ctx) {+    // 2.0.718: the owner chip went default-OFF in 2.0.713 (John: it covers the page's own controls), but a desktop where+    // the Settings dialog had ever saved its values kept the old default stored as "on" (ConfRoomROG, measured). Before+    // 2.0.713 "on" was the default, so a stored "on" is almost never a choice: turn it off once. Anyone who turns the+    // chip on after this keeps it (the marker means the migration never runs again).+    if ctx.settings.get("ownerChipMigrated", "") != "713" {+        if ctx.settings.get("ownerChip", "off") == "on" { let _ = ctx.settings.set("ownerChip", json!("off")); pup_core::log("[settings] ownerChip was stored as the old default \"on\"; turned off once (2.0.713 made it default-off)"); }+        let _ = ctx.settings.set("ownerChipMigrated", json!("713"));+    }     // 2.0.659 (pup-bridge#113): throwaway lanes do not survive a pup restart. End any browser still on an     // iso-* profile and remove the profile; their windows were fleeting by definition.     if let Ok(rd) = std::fs::read_dir(pup_browser::profiles_dir(&*ctx.host)) {
src/extension/worker.js+6−2
@@ -445,10 +445,14 @@ async function pupExtPollOnce() {         const iv = setInterval(() => { if (Date.now() > stopAt || user()) { clearInterval(iv); return; } pauseAll(); }, 500);         return pauseAll();       };+      // 2.0.715: every tab is capped at ~15 s (12 s for the page, 3 s to inject). A tab whose page or injection never settles used to hold this command for+      // its full 40 s, and restores run one window after another, so one stuck tab delayed every window behind it.+      const capped = (p, ms2) => Promise.race([p, new Promise((res) => setTimeout(() => res('timeout'), ms2))]);       const results = await Promise.all(tabs.map(async (t) => {         try {-          if (t.status !== 'complete') await pupExtWaitComplete(t.id, 15000);-          const r = await chrome.scripting.executeScript({ target: { tabId: t.id, allFrames: true }, func: guard, args: [ms] });+          if (t.status !== 'complete') await capped(pupExtWaitComplete(t.id, 12000), 12200);   // a guard injected before the page commits is thrown away with it+          const r = await capped(chrome.scripting.executeScript({ target: { tabId: t.id, allFrames: true }, func: guard, args: [ms] }), 3000);+          if (r === 'timeout') return -1;           return (r || []).reduce((a, x) => a + (Number(x && x.result) || 0), 0);         } catch (e) { return -1; }       }));