master
John Lauer v2.0.735 (insiders): x.com and other sites that refuse a fresh sign-in from pup's browser (John, 2026-10-27, pup-bridge… 55f65e3 14d ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993
//! pup-bridge#109: EXTENSION-DRIVEN windows — pup drives a Chrome that carries NO CDP debug port.
//!
//! Measured on AdomLapper 2026-09-12, same box and minute:
//!   pup over CDP        st.com + mouser ERR_HTTP2_PROTOCOL_ERROR, arrow Access Denied
//!   extension-driven    st.com + mouser + arrow ALL LOAD
//! and the extension-driven browser was the SAME Chrome for Testing binary reporting raw
//! Chromium-148 brands with no "Google Chrome" brand at all — the exact identity that was being
//! walled over CDP. So the discriminator is the DevTools debug port, not the browser identity.
//! (analog.com stays denied either way: it denies real Chrome driven by the real extension on a
//! profile without established trust, so that one is profile trust, not a driver problem.)
//!
//! The channel: pup queues ONE command; the extension's service worker polls `/extcmd`, runs it
//! with chrome.tabs / chrome.scripting, and POSTs the result to `/extresult`.

use pup_core::{err, ok, Caller};
use serde_json::{json, Value};
use std::sync::Mutex;
use std::time::{Duration, Instant};

use crate::Ctx;

/// The command channel, 2.0.570: a QUEUE. It was one slot (one pending command, one result), so two
/// callers at once (the annotate watcher's read every 500 ms and a pup_eval, or the health tick's overlay
/// and a verb) overwrote each other, one of them timed out and the annotate watcher died on its first
/// read. Commands queue by id, results are keyed by id, the worker drains the queue on every poll, and
/// everything the worker had taken is re-queued when a fresh hello says it restarted.
/// 2.0.659 (pup-bridge#113): the lane is PER PROFILE. The default lane (adom-you, the one every user runs)
/// keeps exactly the state it always had; every other profile (a throwaway iso-<id> for isolated:true, or a
/// named profile) gets its own browser, its own staged extension with its own lane token, its own queue.
/// Which lane a call means comes from a thread-local set by `enter_profile` (the dispatcher resets it per
/// request; `transport::lane_for` sets it from the session's profile), so every existing call site is unchanged.
pub struct LaneState {
    pub profile: String,
    pub token: Mutex<String>,
    pub pids: Mutex<Vec<u32>>,
    pub pending: Mutex<std::collections::VecDeque<Value>>,
    pub results: Mutex<Option<std::collections::HashMap<u64, Value>>>,
    pub taken: Mutex<Option<std::collections::HashMap<u64, (Value, u64)>>>,
    pub last_poll: Mutex<u64>,
    pub removed: Mutex<Vec<i64>>,
    pub worker: Mutex<String>,
    pub cur: Mutex<(i64, i64)>,
    pub handle: std::sync::Mutex<Option<std::sync::Arc<crate::BrowserHandle>>>,
}
impl LaneState {
    pub fn new(profile: &str) -> LaneState { LaneState { profile: profile.to_string(), token: Mutex::new(String::new()), pids: Mutex::new(Vec::new()), pending: Mutex::new(std::collections::VecDeque::new()), results: Mutex::new(None), taken: Mutex::new(None), last_poll: Mutex::new(0), removed: Mutex::new(Vec::new()), worker: Mutex::new(String::new()), cur: Mutex::new((0, 0)), handle: std::sync::Mutex::new(None) } }
}
static DEFAULT_LANE: std::sync::OnceLock<std::sync::Arc<LaneState>> = std::sync::OnceLock::new();
static LANES: Mutex<Vec<std::sync::Arc<LaneState>>> = Mutex::new(Vec::new());
/// Set once restore_extdrive has read every lane file at boot; adoption of unknown lanes waits for it.
pub static RESTORED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
thread_local! { static CUR_PROFILE: std::cell::RefCell<String> = std::cell::RefCell::new(String::new()); }
pub fn default_lane() -> std::sync::Arc<LaneState> { DEFAULT_LANE.get_or_init(|| std::sync::Arc::new(LaneState::new("adom-you"))).clone() }
pub fn st() -> std::sync::Arc<LaneState> {
    let p = CUR_PROFILE.with(|c| c.borrow().clone());
    if p.is_empty() || p == "adom-you" { return default_lane(); }
    // 2.0.723: never fall back to the adom-you lane for another profile. A lane dropped mid-call (the idle sweep of
    // an isolated profile) made a close clear the adom-you lane and end the user's main browser.
    ensure_lane(&p)
}
pub fn cur_profile() -> String { let p = CUR_PROFILE.with(|c| c.borrow().clone()); if p.is_empty() { "adom-you".into() } else { p } }
pub fn enter_profile(profile: &str) {
    if profile.is_empty() || profile == "adom-you" { CUR_PROFILE.with(|c| c.borrow_mut().clear()); return; }
    if lane_for_profile(profile).is_none() { LANES.lock().unwrap().push(std::sync::Arc::new(LaneState::new(profile))); }
    CUR_PROFILE.with(|c| *c.borrow_mut() = profile.to_string());
}
pub fn leave_profile() { CUR_PROFILE.with(|c| c.borrow_mut().clear()); }
/// The lane for `profile`, created if pup has none yet (the default lane for adom-you).
pub fn ensure_lane(profile: &str) -> std::sync::Arc<LaneState> {
    if let Some(l) = lane_for_profile(profile) { return l; }
    let l = std::sync::Arc::new(LaneState::new(profile)); LANES.lock().unwrap().push(l.clone()); l
}
pub fn lane_for_profile(profile: &str) -> Option<std::sync::Arc<LaneState>> {
    if profile.is_empty() || profile == "adom-you" { return Some(default_lane()); }
    LANES.lock().unwrap().iter().find(|l| l.profile == profile).cloned()
}
pub fn lane_for_token(token: &str) -> Option<std::sync::Arc<LaneState>> {
    if token.is_empty() { return None; }
    if *default_lane().token.lock().unwrap() == token { return Some(default_lane()); }
    LANES.lock().unwrap().iter().find(|l| *l.token.lock().unwrap() == token).cloned()
}
pub fn drop_lane_for_profile(profile: &str) { LANES.lock().unwrap().retain(|l| l.profile != profile); }
pub fn secondary_lanes() -> Vec<std::sync::Arc<LaneState>> { LANES.lock().unwrap().clone() }
static SEQ: Mutex<u64> = Mutex::new(0);
pub fn take_removed() -> Vec<i64> { std::mem::take(&mut *st().removed.lock().unwrap()) }

/// GET /extcmd — the extension asks for work. Returns `{}` when there is nothing to do.
pub fn take_command_for(lane: &str, removed: &[i64], wait_ms: u64) -> Value {
    // 2.0.502: only a browser carrying a lane token pup issued may take work. 2.0.659: the token names WHICH
    // lane (default adom-you, or a per-profile lane), so several browsers can be driven at once.
    let Some(l) = lane_for_token(lane) else { return json!({}) };
    if !removed.is_empty() { l.removed.lock().unwrap().extend_from_slice(removed); }
    let cmd = take_command_on(&l);
    if cmd["id"].is_u64() || wait_ms == 0 { return cmd; }
    // 2.0.693: a LONG poll. The worker used to ask every 1.2 s, so every command waited up to 1.2 s just to
    // be picked up, and a two-step verb like Annotate (inject the overlay, then start it) took 1.5 to 2.2 s
    // before the user could draw (John, 2026-10-04). Now the request waits here and run() wakes it the moment
    // a command is queued. Capped well under the MV3 worker's 30 s idle limit and alive()'s 20 s heartbeat.
    let deadline = Instant::now() + Duration::from_millis(wait_ms.min(10_000));
    let mut g = QUEUED.0.lock().unwrap();
    while l.pending.lock().unwrap().is_empty() {
        let now = Instant::now();
        if now >= deadline { break; }
        g = QUEUED.1.wait_timeout(g, deadline - now).unwrap().0;
    }
    drop(g);
    take_command_on(&l)
}
/// Rung by run() whenever a command is queued, so a waiting /extcmd long poll returns at once.
static QUEUED: (Mutex<()>, std::sync::Condvar) = (Mutex::new(()), std::sync::Condvar::new());

pub fn take_command() -> Value { take_command_on(&st()) }
fn take_command_on(l: &LaneState) -> Value {
    let first = *l.last_poll.lock().unwrap() == 0;
    *l.last_poll.lock().unwrap() = pup_core::now_ms();
    if first { pup_core::log(format!("[extdrive] the pup extension reached /extcmd (lane profile {}) — the control channel is live", l.profile)); }
    let cmd = l.pending.lock().unwrap().pop_front().unwrap_or(json!({}));
    if let Some(id) = cmd["id"].as_u64() { l.taken.lock().unwrap().get_or_insert_with(Default::default).insert(id, (cmd.clone(), pup_core::now_ms())); }
    cmd
}

/// Called on a matched hello: if the previous worker instance took a command and never answered, hand it
/// to this one. Returns how many were re-queued.
fn requeue_taken_on(l: &LaneState, reason: &str) -> usize {
    let now = pup_core::now_ms();
    let mut taken = l.taken.lock().unwrap();
    let Some(m) = taken.as_mut() else { return 0 };
    let mut ids: Vec<u64> = m.iter().filter(|(_, (_, at))| now.saturating_sub(*at) >= 1500).map(|(id, _)| *id).collect();
    ids.sort();
    let mut n = 0;
    let mut pending = l.pending.lock().unwrap();
    let results = l.results.lock().unwrap();
    for id in ids.into_iter().rev() {   // oldest ends up at the front
        if results.as_ref().map(|r| r.contains_key(&id)).unwrap_or(false) { m.remove(&id); continue; }
        if let Some((cmd, _)) = m.remove(&id) {
            pup_core::log(format!("[extdrive] re-issued command {id} ({}) after {reason}", cmd["action"].as_str().unwrap_or("?")));
            pending.push_front(cmd); n += 1;
        }
    }
    n
}
#[allow(dead_code)]
fn requeue_taken(reason: &str) -> usize { requeue_taken_on(&st(), reason) }

/// POST /exthello — an extension announces itself, whatever lane it holds. Throttled to one line per
/// distinct (lane, match) per 30s so a 1.2s poll loop cannot flood the log.
/// The worker version the running lane browser reported in its last hello ("2-0-541" from /worker.2-0-541.js).
// LANE_WORKER lives in LaneState.worker since 2.0.659
/// 2.0.569: after a bridge_install the running extension browser keeps the OLD worker until it relaunches,
/// which only happens when no thread has an ext window open (measured: a 2.0.565 bridge driving a 2.0.562
/// worker for an hour). On each health tick, if the lane's worker is behind, re-stage the extension folder
/// in place and ask the worker to chrome.runtime.reload(): it comes back on the new files with the same lane
/// token, and the bridge re-queues any command it had in flight.
pub fn reload_stale_worker(ctx: &Ctx) {
    static ASKED: Mutex<Option<String>> = Mutex::new(None);
    if !alive(ctx) { return; }
    let Some((have, want)) = lane_worker_stale() else { return };
    if ASKED.lock().unwrap().as_deref() == Some(want.as_str()) { return; }
    let lane = st().token.lock().unwrap().clone();
    if lane.is_empty() { return; }
    // 2.0.690: never cycle a lane pup cannot end yet. Right after a restart the lane's processes and windows are
    // re-attached a few seconds AFTER the worker's first hello; cycling in that gap killed 0 processes, marked the
    // version done, and left John's laptop on the 2.0.686 extension under pup 2.0.689 with no retry (2026-10-03).
    if st().pids.lock().unwrap().is_empty() {
        // a lane re-attached by its hello (not from lane.json) has a token but no process list: find the processes
        // running its profile folder, as the orphan sweep does. Still none: nothing pup can end yet.
        let found = crate::pup_browser_pids_by_profile(ctx).get(&cur_profile()).cloned().unwrap_or_default();
        if found.is_empty() { return; }
        pup_core::log(format!("[extdrive] the lane's process list was empty; found {} process(es) running profile \"{}\"", found.len(), cur_profile()));
        *st().pids.lock().unwrap() = found;
    }
    let pending = ctx.sessions.read_files().iter().filter(|f| f["transport"].as_str() == Some("ext")).filter_map(|f| f["sessionId"].as_str().map(|x| x.to_string())).any(|id| !ctx.sessions.exists(&id));
    if pending { return; }
    // 2.0.695: and never while a window the browser holds has no session yet. adopt_stray_windows gives it one
    // (and a file) within 5 s; cycling first closed such windows for good.
    if STRAYS_WAITING.load(std::sync::atomic::Ordering::Relaxed) != 0 { return; }
    let shim = crate::lane_shim(ctx);
    let staged = pup_browser::stage_extensions_lane(&*ctx.host, ctx.port, "adom-you", true, Some(&lane), shim);
    if staged.is_empty() { pup_core::log("[extdrive] worker reload skipped: re-staging the extension produced nothing".to_string()); return; }
    // 2.0.646/647 (pup-bridge#121): NEVER reload in place (chrome.runtime.reload() on the running worker broke the
    // channel: every verb answered browser_gone while the windows were alive). And never a bare kill either: 2.0.646
    // killed the lane and recovery FORGOT every window, because the kill had already cleared the pid list that
    // recovery uses to tell pup's own kill from the user closing the last window (AdomLapper, 2026-09-19). This is
    // the crash-restore path, driven on purpose: reserve every extension window, end the lane, relaunch each
    // window from its file on the re-staged extension, in the background, at its last page.
    let hwnds: Vec<i64> = ctx.sessions.all().into_iter().filter(|s| s.transport == "ext").filter_map(|s| s.hwnd).collect();   // 2.0.651: from the live sessions (the file has no hwnd)
    let files: Vec<Value> = ctx.sessions.all().into_iter().filter(|s| s.transport == "ext").map(|s| s.file_json()).collect();
    let ids: Vec<String> = files.iter().filter_map(|f| f["sessionId"].as_str().map(|s| s.to_string())).collect();
    crate::recovery::reserve(&ids); crate::recovery::mark_lost(&ids);   // 2.0.682: pup ends this browser itself
    // 2.0.696: say so. The windows vanish for about a minute and come back one by one, and with no word from pup
    // that read as a crash (John, 2026-10-04: "i just thought you had crashed").
    let to = want.replace('-', ".");
    let toast = |title: String, body: String| {
        let r = ctx.ab.call("notify_user", json!({ "title": title.clone(), "body": body, "focus": false }), &crate::identity::internal_caller(), Duration::from_secs(8));
        pup_core::log(format!("[extdrive] update notice \"{title}\": {}", match r { Ok(_) => "shown".to_string(), Err(e) => format!("NOT shown ({e})") }));
    };
    if !ids.is_empty() {
        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
    // every window still "alive on glass", declined ("will re-adopt"), and the sessions were lost when the browser
    // finished dying (AdomLapper, 2.0.649 upgrade: 5 windows, 4 lost).
    let t0 = std::time::Instant::now();
    while t0.elapsed() < Duration::from_secs(8) && hwnds.iter().any(|h| ctx.host.is_alive(*h)) { std::thread::sleep(Duration::from_millis(200)); }
    pup_core::log(format!("[extdrive] lane worker {} is behind pup {}: re-staged the extension and cycled the lane browser ({n} process(es), windows gone after {} ms); relaunching its {} window(s) from their files on the new extension", have.replace('-', "."), want.replace('-', "."), t0.elapsed().as_millis(), ids.len()));
    for f in files { crate::recovery::relaunch_from_file(ctx, &f); }
    if !ids.is_empty() {
        let back: Vec<&String> = ids.iter().filter(|id| ctx.sessions.exists(id)).collect();
        // 2.0.698: a window the user closed while it was being restored is final, not "still coming" (John closed two)
        let closed: Vec<&str> = ids.iter().filter(|id| !ctx.sessions.exists(id) && crate::recovery::restore_cancelled(id)).map(|s| s.as_str()).collect();
        let missing: Vec<&str> = ids.iter().filter(|id| !ctx.sessions.exists(id) && !crate::recovery::restore_cancelled(id)).map(|s| s.as_str()).collect();
        let secs = t0.elapsed().as_secs();
        let you_closed = if closed.is_empty() { String::new() } else { format!(" You closed {}.", closed.join(", ")) };
        if missing.is_empty() { toast(format!("pup updated to {to}"), format!("{} window{} back ({secs} s).{you_closed}", back.len(), if back.len() == 1 { " is" } else { "s are" })); }
        else { toast(format!("pup updated to {to}"), format!("{} of {} windows are back.{you_closed} Not back yet: {}; pup keeps trying in the background.", back.len(), ids.len(), missing.join(", "))); }
        pup_core::log(format!("[extdrive] update cycle finished in {secs} s: {} of {} window(s) back{}", back.len(), ids.len(), if missing.is_empty() { String::new() } else { format!("; not yet: {}", missing.join(", ")) }));
    }
}

/// 2.0.647: a lane that ends takes its worker version with it; a fresh browser reports its own at hello.
pub fn reset_worker_version() { st().worker.lock().unwrap().clear(); }

pub fn lane_worker_stale() -> Option<(String, String)> {
    let w = st().worker.lock().unwrap().clone();
    if w.is_empty() { return None; }
    let want = pup_core::version().replace('.', "-");
    if w == want { None } else { Some((w, want)) }
}

pub fn hello(v: Value) {
    static LAST: Mutex<Option<std::collections::HashMap<String, u64>>> = Mutex::new(None);
    let lane = v["lane"].as_str().unwrap_or("").to_string();
    let target = lane_for_token(&lane);
    if let (Some(l), Some(w)) = (target.as_ref(), v["worker"].as_str().and_then(|p| p.rsplit('/').next()).and_then(|f| f.strip_prefix("worker.")).and_then(|f| f.strip_suffix(".js"))) {
        *l.worker.lock().unwrap() = w.to_string();
    }
    let want = default_lane().token.lock().unwrap().clone();
    let matched = target.is_some();
    if let Some(l) = target.as_ref() { requeue_taken_on(l, "the lane worker restarted (fresh hello)"); }
    let key = format!("{lane}|{matched}");
    {
        let mut g = LAST.lock().unwrap();
        let m = g.get_or_insert_with(std::collections::HashMap::new);
        let now = pup_core::now_ms();
        if let Some(t) = m.get(&key) { if now.saturating_sub(*t) < 30_000 { return; } }
        m.insert(key, now);
    }
    let dl = default_lane();
    // 2.0.670: adopt an unknown lane browser ONLY into the lane of the profile it says it runs. It used to become the
    // adom-you lane whatever its jar: on AdomLapper (2026-09-21) the "services" browser was adopted after an upgrade
    // and adom-you windows opened inside it with the wrong cookies. A worker too old to report its profile is adopted
    // as adom-you only while pup knows no other profile's lane.
    let hp = v["profile"].as_str().unwrap_or("").to_string();
    // 2.0.670: the HTTP server answers before the boot restore has read the lane files; an adoption in that gap
    // took the svc-test browser as adom-you (CRR, 2026-09-21 13:34:43). Wait for the restore; the hello repeats.
    if !matched && !lane.is_empty() && !RESTORED.load(std::sync::atomic::Ordering::SeqCst) { return; }
    if !matched && !lane.is_empty() {
        let dest = if !hp.is_empty() { Some(ensure_lane(&hp)) } else if LANES.lock().unwrap().is_empty() { Some(dl.clone()) } else { None };
        match dest {
            Some(d) if *d.last_poll.lock().unwrap() == 0 => {
                *d.token.lock().unwrap() = lane.clone();
                pup_core::log(format!("[extdrive] adopted the running extension browser (lane {lane}) as the {} lane{}; pup had lost track of it", d.profile, if hp.is_empty() { " (its worker does not report a profile)" } else { "" }));
                return;
            }
            None => { pup_core::log(format!("[extdrive] a pup extension with an unknown lane {lane:?} and no reported profile said hello; NOT adopted (pup runs more than one profile's lane and cannot tell which jar it is)")); return; }
            _ => {}
        }
    }
    if !matched && lane.is_empty() && *dl.last_poll.lock().unwrap() == 0 {
        *TOKENLESS_HELLO_AT.lock().unwrap() = pup_core::now_ms();
        pup_core::log(format!("[extdrive] a pup extension is polling with no lane token{}: the next health tick adopts it if pup's own browser is gone", if want.is_empty() { String::new() } else { format!(" while pup wants {want}") }));
        return;
    }
    if let Some(l) = target { pup_core::log(format!("[extdrive] hello from the lane browser (lane {lane}, profile {}) — this one may take work", l.profile)); }
    else { pup_core::log(format!("[extdrive] hello from a pup extension carrying lane {:?} (pup wants {:?}), port.json read={} pkg={} — NOT served", lane, want, v["read"], v["pkg"])); }
}

pub fn put_result(v: Value) {
    let Some(id) = v["id"].as_u64() else { return };
    let mut lanes = vec![default_lane()]; lanes.extend(secondary_lanes());
    let owner = lanes.iter().find(|l| l.taken.lock().unwrap().as_ref().map(|m| m.contains_key(&id)).unwrap_or(false)).cloned().unwrap_or_else(|| st());
    if let Some(m) = owner.taken.lock().unwrap().as_mut() { m.remove(&id); }
    owner.results.lock().unwrap().get_or_insert_with(Default::default).insert(id, v);
}

/// A killed lane must read as dead at once. `alive()` is heartbeat-based, and a browser killed 3 s after its
/// last poll still counted as alive for 7 more seconds: the next open took the WARM path and waited 60 s on a
/// dead channel until ab timed out (2026-09-13, the first cold open of the 20-site proof).
pub(crate) fn mark_dead() { *st().last_poll.lock().unwrap() = 0; }

/// The lane as pup_status reports it. There are no lane verbs: a lane window is a pup window.
pub fn lane_health(ctx: &Ctx) -> Value {
    let ago = connected_ms_ago();
    let stale = lane_worker_stale();
    // 2.0.583: LIVE lane windows on glass, not the session count (a dead lane reported "1 window" for 14 h);
    // and an EMPTY pid list means zero, because to the enumerator it means "no filter" (it counted the user's own Chrome and Edge: 7)
    let live_windows = { let p = crate::extdrive_pids(); if p.is_empty() { 0 } else { ctx.host.windows_for_pids(&p, Some("Chrome_WidgetWin_1")).into_iter().filter(|w| w.visible && w.rect.2 - w.rect.0 > 200).count() } };
    json!({ "connected": alive(ctx), "lastPollMsAgo": if ago > 86_400_000 { Value::Null } else { json!(ago) },
            "processes": crate::extdrive_pids().len(), "profile": "adom-you", "debugPort": false,
            "workerVersion": st().worker.lock().unwrap().replace('-', "."), "stale": stale.is_some(),
            "_staleHint": stale.map(|(w, want)| format!("the lane browser still runs extension {} while pup is {}; it cycles by itself the moment no extension-lane window is open, or pup_close_window {{all:true}} from every thread cycles it now", w.replace('-', "."), want.replace('-', "."))),
            "sessions": ctx.sessions.all().iter().filter(|s| crate::transport::is_ext(s)).count(),
            "windows": live_windows })
}

pub fn connected_ms_ago() -> u64 { pup_core::now_ms().saturating_sub(*st().last_poll.lock().unwrap()) }
pub fn last_poll_ms() -> u64 { *st().last_poll.lock().unwrap() }
/// 2.0.580: a pup extension polled with NO lane token while pup holds none either (both sides empty after a
/// restart whose lane.json did not survive). Refusing it forever left an orphan browser (2026-09-14 20:57).
/// The health tick adopts it: mint a token, stamp it into port.json (the worker re-reads it every poll),
/// and learn the lane's pids from Chrome for Testing frames only, never the user's own Chrome.
static TOKENLESS_HELLO_AT: Mutex<u64> = Mutex::new(0);
pub fn adopt_tokenless(ctx: &Ctx) {
    let at = *TOKENLESS_HELLO_AT.lock().unwrap();
    if at == 0 || pup_core::now_ms().saturating_sub(at) > 90_000 { return; }
    // 2.0.599: pup holding a token is NOT a reason to refuse forever. On the conference room PC pup kept
    // wanting "ext-1789600049390" while the live extension said lane "" on every hello, so every poll was
    // refused and only a pup restart fixed it (measured 2026-09-16). The token belongs to a browser that is
    // GONE: adopt the live one. Still never while the browser pup minted that token for is alive (a cold
    // launch sets the token, then waits for its first poll: an old browser must not be able to hijack that).
    if last_poll_ms() != 0 { return; }
    let mine = crate::extdrive_pids();
    if !mine.is_empty() && mine.iter().any(|p| ctx.host.alive(*p)) { return; }
    let had = st().token.lock().unwrap().clone();
    let lane = format!("ext-{}", pup_core::now_ms());
    let staged = pup_browser::stage_extensions_lane(&*ctx.host, ctx.port, "adom-you", true, Some(&lane), crate::lane_shim(ctx));
    if staged.is_empty() { return; }
    // 2.0.600: never the pids of a CDP browser pup already owns on another profile. Learning them here made
    // the next kill_extdrive take the ab-smoke window's browser down with the lane (ConfRoomROG, 2026-09-16).
    let cdp: Vec<u32> = ctx.browsers.lock().unwrap().values().map(|b| b.launched.pid).filter(|p| *p > 0).collect();
    let pids: Vec<u32> = { let mut v: Vec<u32> = ctx.host.frame_windows_all(Some("Chrome_WidgetWin_1")).into_iter().filter(|w| w.title.ends_with("Google Chrome for Testing") && !cdp.contains(&w.pid)).map(|w| w.pid).collect(); v.sort(); v.dedup(); v };
    if pids.is_empty() { return; }
    *st().token.lock().unwrap() = lane.clone();
    crate::set_extdrive_pids(pids.clone());
    crate::persist_lane(ctx);
    *TOKENLESS_HELLO_AT.lock().unwrap() = 0;
    pup_core::log(format!("[extdrive] adopted a token-less extension browser: minted lane {lane}, stamped it into port.json, {} Chrome for Testing process(es) learned from its windows{}", pids.len(), if had.is_empty() { String::new() } else { format!(" (dropping the stale token {had}, whose browser is gone)") }));
}

/// Queue one command and block until the extension answers it (or the deadline passes).
pub(crate) fn run(cmd: Value, wait: Duration) -> Result<Value, Value> {
    IN_FLIGHT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
    let r = run_inner(cmd, wait);
    IN_FLIGHT.fetch_sub(1, std::sync::atomic::Ordering::Relaxed);
    r
}
fn run_inner(cmd: Value, wait: Duration) -> Result<Value, Value> {
    let id = { let mut s = SEQ.lock().unwrap(); *s += 1; *s };
    let mut c = cmd;
    c["id"] = json!(id);
    st().pending.lock().unwrap().push_back(c);
    { let _g = QUEUED.0.lock().unwrap(); QUEUED.1.notify_all(); }
    let t0 = Instant::now();
    while t0.elapsed() < wait {
        let hit = st().results.lock().unwrap().as_mut().and_then(|m| m.remove(&id));
        if let Some(r) = hit {
            if r["ok"] == json!(true) { return Ok(r); }
            return Err(err("ext_command_failed", r["error"].as_str().unwrap_or("the extension could not run it").to_string(), None));
        }
        std::thread::sleep(Duration::from_millis(60));
    }
    st().pending.lock().unwrap().retain(|c| c["id"].as_u64() != Some(id));
    if let Some(m) = st().taken.lock().unwrap().as_mut() { m.remove(&id); }
    Err(pup_core::merge(
        err("ext_no_answer", format!("the pup extension did not answer within {}s", wait.as_secs()),
            Some("Is an extension-driven window open? pup_open_window starts one. The extension only answers while its browser is running; a normal CDP window does not serve this channel.")),
        json!({ "lastPollMsAgo": connected_ms_ago() }),
    ))
}

/// Liveness is the extension's own heartbeat, NOT the spawned pid: Chrome's launcher process exits
/// immediately and hands off to a new pid, so the pid we spawned is dead within a second while the
/// browser runs fine (that is why pup's normal launch reads the pid back over CDP instead).
/// 2.0.532: a command in flight counts as alive. The worker does not poll while it executes a command (a
/// slow site's tab open holds it for up to 30 s), so the heartbeat stopped, alive() read the lane as dead
/// and the next three tab opens were refused with "browser not running" while the browser was fine
/// (the 20-site proof, 2026-09-13: nordicsemi held the worker; newark, rs-online and infineon were refused).
static IN_FLIGHT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
pub(crate) fn alive(_ctx: &Ctx) -> bool { connected_ms_ago() < 20_000 || (IN_FLIGHT.load(std::sync::atomic::Ordering::Relaxed) > 0) }

/// The window/tab pup is driving, so navigate/eval do not wander to another window.
// CUR lives in LaneState.cur since 2.0.659
fn pin(r: &Value) {
    let w = r["windowId"].as_i64().unwrap_or(0);
    let t = r["tabId"].as_i64().unwrap_or(0);
    if w > 0 || t > 0 { *st().cur.lock().unwrap() = (w, t); }
}
fn aim(c: &mut Value) {
    let (w, t) = *st().cur.lock().unwrap();
    if t > 0 { c["tabId"] = json!(t); }
    if w > 0 { c["windowId"] = json!(w); }
}

/// pup_open_window {url} — launch (or reuse) the extension-driven browser and open `url` in it.
pub fn open(ctx: &Ctx, _caller: &Caller, args: &Value) -> Value {
    let r = with_lane_progress(ctx, _caller, args, || open_inner(ctx, _caller, args));
    if r["ok"] != json!(false) { dress_after(ctx, _caller); }
    r
}
fn open_inner(ctx: &Ctx, _caller: &Caller, args: &Value) -> Value {
    let url = args.get("url").and_then(|u| u.as_str()).unwrap_or("about:blank").to_string();
    if !alive(ctx) {
        if let Err(e) = crate::launch_extdrive(ctx, &url) { return e; }
        // 2.0.493: return WELL INSIDE the caller's request window. Waiting 45 s here for the extension to call
        // home blew past ab's ~60 s relay on a slower box, so every pup_open_window on ConfRoomROG came back
        // "Request timed out" while the browser was in fact starting fine. Wait briefly; if the channel is not
        // up yet, answer `starting` (NOT an error) and let the caller poll pup_status, the same contract
        // pup_restart_browser uses.
        let t0 = Instant::now();
        while t0.elapsed() < Duration::from_secs(20) {
            if connected_ms_ago() < 4000 { break; }
            std::thread::sleep(Duration::from_millis(300));
        }
        if connected_ms_ago() > 8000 {
            dress_window(ctx, _caller);
            return pup_core::merge(ok(json!({ "starting": true, "opened": false, "url": url })), json!({
                "statusVerb": "pup_status", "_hint": "The extension-driven browser is starting; its extension has not called home yet. Poll pup_status until extensionConnected is true (usually a few seconds, longer on a cold Chrome for Testing), then pup_navigate / pup_eval. This is NOT a failure.",
            }));
        }
        dress_window(ctx, _caller);
        fit_now();
        return match run(json!({ "action": "tabs" }), Duration::from_secs(20)) {
            Ok(r) => {
                if let Some(t) = r["tabs"].as_array().and_then(|a| a.iter().find(|t| t["url"].as_str().map(|u| u.starts_with("http")).unwrap_or(false))) { pin(t); }
                pup_core::merge(ok(json!({ "opened": true, "url": url, "tabs": r["tabs"] })),
                json!({ "_hint": "Extension-driven window: NO CDP debug port, so vendor edges that wall automation (st.com, mouser, arrow) load here. Drive it with pup_navigate / pup_eval. Trusted input (pup_click, pup_type, pup_input_dispatch), element and full-page screenshots, and annotate all work here, and pup_devtools {method, params} sends any DevTools command to this window." }))
            }
            Err(e) => e,
        };
    }
    dress_window(ctx, _caller);
    fit_now();
    match run(json!({ "action": "open", "url": url }), Duration::from_secs(60)) {
        Ok(r) => pup_core::merge({ pin(&r); ok(json!({ "opened": true, "url": url, "tabId": r["tabId"], "windowId": r["windowId"] })) },
            json!({ "_hint": "Opened in the existing extension-driven browser. pup_eval reads it; pup_navigate moves it." })),
        Err(e) => e,
    }
}

/// pup_navigate {url, tabId?}
pub fn navigate(ctx: &Ctx, _caller: &Caller, args: &Value) -> Value {
    let r = with_lane_progress(ctx, _caller, args, || navigate_inner(ctx, _caller, args));
    if r["ok"] != json!(false) { dress_after(ctx, _caller); }
    r
}
fn navigate_inner(ctx: &Ctx, _caller: &Caller, args: &Value) -> Value {
    dress_window(ctx, _caller);
    if !alive(ctx) { return not_running(); }
    let mut c = json!({ "action": "navigate", "url": args.get("url").and_then(|u| u.as_str()).unwrap_or("about:blank") });
    aim(&mut c);
    if let Some(t) = args.get("tabId") { c["tabId"] = t.clone(); }
    match crate::transport::with_frames(|| run(c, Duration::from_secs(60))) {
        Ok(r) => { pin(&r); ok(json!({ "url": r["url"], "title": r["title"], "tabId": r["tabId"], "_hint": "Navigated through the extension (no CDP). Read it with pup_eval." })) }
        Err(e) => e,
    }
}

/// pup_eval {expr, tabId?} — runs in the page's MAIN world; non-strings come back JSON-encoded.
pub fn eval(ctx: &Ctx, _caller: &Caller, args: &Value) -> Value {
    if !alive(ctx) { return not_running(); }
    let mut c = json!({ "action": "eval", "expr": args.get("expr").and_then(|e| e.as_str()).unwrap_or("") });
    aim(&mut c);
    if let Some(t) = args.get("tabId") { c["tabId"] = t.clone(); }
    match run(c, Duration::from_secs(45)) {
        Ok(r) => { pin(&r); ok(json!({ "result": r["result"], "tabId": r["tabId"], "url": r["url"] })) }
        Err(e) => e,
    }
}

/// pup_list_tabs {}
pub fn tabs(ctx: &Ctx, _caller: &Caller, _args: &Value) -> Value {
    if !alive(ctx) { return not_running(); }
    match run(json!({ "action": "tabs" }), Duration::from_secs(25)) { Ok(r) => ok(json!({ "tabs": r["tabs"] })), Err(e) => e }
}

/// pup_close_window {tabId?} — a tab, or the whole extension-driven browser when nothing is named.
pub fn close(ctx: &Ctx, _caller: &Caller, args: &Value) -> Value {
    if !alive(ctx) { return ok(json!({ "closed": false, "_hint": "No extension-driven browser was running." })); }
    if let Some(t) = args.get("tabId") {
        return match run(json!({ "action": "close", "tabId": t }), Duration::from_secs(20)) { Ok(_) => ok(json!({ "closed": true })), Err(e) => e };
    }
    let via_ext = run(json!({ "action": "closeAll" }), Duration::from_secs(20)).is_ok();
    *st().cur.lock().unwrap() = (0, 0);
    let n = crate::kill_extdrive(ctx);
    ok(json!({ "closed": true, "viaExtension": via_ext, "processesEnded": n, "_hint": "The extension-driven browser is closed. pup_open_window starts a fresh one." }))
}

/// pup_status {}
pub fn status(ctx: &Ctx, _caller: &Caller, _args: &Value) -> Value {
    let a = alive(ctx);
    let ago = connected_ms_ago();
    ok(json!({
        "browserRunning": a,
        "extensionConnected": a && ago < 8000,
        "lastPollMsAgo": if *st().last_poll.lock().unwrap() == 0 { Value::Null } else { json!(ago) },
        "profile": "adom-you",
        "_hint": "Extension-driven mode carries NO CDP debug port. Measured 2026-09-12: st.com, mouser and arrow load here and fail over CDP on the same box. analog.com stays denied (that one is profile trust, and denies the real extension on a cold profile too).",
        "_next": ["pup_close_window"],
    }))
}

fn not_running() -> Value {
    err("ext_not_running", "no extension-driven browser is open", Some("pup_open_window {url} starts one (Chrome for Testing, pup-owned profile, no debug port)."))
}

/// pup_status {} — read the pup extension's SERVICE WORKER directly over CDP in the normal (CDP) browser:
/// is the worker even running, what port did it read from port.json, and what does its own findPort() return.
/// This answers "the extension never called home" from inside the extension instead of by guessing.
pub fn diag(ctx: &Ctx, _caller: &Caller, args: &Value) -> Value {
    let profile = args.get("profile").and_then(|p| p.as_str()).unwrap_or("adom-you");
    let Some(b) = crate::browser_for(ctx, profile) else { return err("no_cdp_browser", format!("no CDP browser on profile \"{profile}\""), Some("Open a normal pup window first (pup_open_window); the diagnostic attaches to the extension worker inside that browser.")) };
    let targets = b.cdp.targets().unwrap_or_default();
    let ext_id = pup_browser::EXT_IDS.iter().find(|(n, _)| *n == "pup-extension").map(|(_, id)| *id).unwrap_or("");
    let workers: Vec<Value> = targets.iter().filter(|t| t["type"] == json!("service_worker") && t["url"].as_str().map(|u| u.contains(ext_id)).unwrap_or(false)).cloned().collect();
    let all_ext: Vec<Value> = targets.iter().filter(|t| t["url"].as_str().map(|u| u.starts_with("chrome-extension://")).unwrap_or(false)).map(|t| json!({ "type": t["type"], "url": t["url"] })).collect();
    if workers.is_empty() {
        return pup_core::merge(ok(json!({ "workerRunning": false, "extensionTargets": all_ext })), json!({ "_hint": "The pup extension's service worker is NOT running in this browser: nothing to poll pup with. Either the extension did not load (--load-extension path, a manifest error) or the worker was never started." }));
    }
    let tid = workers[0]["targetId"].as_str().unwrap_or("").to_string();
    let sid = match b.cdp.attach(&tid) { Ok(s) => s, Err(e) => return err("attach_failed", e.to_string(), None) };
    let expr = "(async () => { const o = { hasPackagedPort: typeof packagedPort, hasFindPort: typeof findPort, hasPoll: typeof pupExtPoll }; try { o.pkg = await packagedPort(); } catch (e) { o.pkgErr = String(e); } try { const f = await findPort(); o.found = f ? f.port : null; } catch (e) { o.findErr = String(e); } try { const r = await fetch(chrome.runtime.getURL('port.json')); o.portJsonStatus = r.status; o.portJsonText = (await r.text()).slice(0, 120); } catch (e) { o.portJsonErr = String(e); } try { o.storagePort = (await chrome.storage.local.get('pupPort')).pupPort; } catch (e) {} return JSON.stringify(o); })()";
    let r = b.cdp.send(Some(&sid), "Runtime.evaluate", json!({ "expression": expr, "awaitPromise": true, "returnByValue": true }), Duration::from_secs(20));
    let _ = b.cdp.send(None, "Target.detachFromTarget", json!({ "sessionId": sid }), Duration::from_secs(5));
    match r {
        Ok(v) => {
            let raw = v["result"]["value"].as_str().unwrap_or("").to_string();
            let parsed: Value = serde_json::from_str(&raw).unwrap_or(json!({ "raw": raw, "exception": v["exceptionDetails"] }));
            ok(json!({ "workerRunning": true, "workerUrl": workers[0]["url"], "insideWorker": parsed, "pupPort": ctx.port, "_hint": "insideWorker is the extension's OWN view: pkg = the port it read from port.json, found = the port its findPort() settled on (null = it cannot reach pup), portJsonStatus/Err = whether it can even read its packaged port.json." }))
        }
        Err(e) => err("worker_eval_failed", e.to_string(), None),
    }
}
/// pup_open_tab {url} — open a TAB in the extension-driven window (the lane could only make new windows).
pub fn tab(ctx: &Ctx, _caller: &Caller, args: &Value) -> Value {
    let r = with_lane_progress(ctx, _caller, args, || tab_inner(ctx, _caller, args));
    if r["ok"] != json!(false) { dress_after(ctx, _caller); }
    r
}
fn tab_inner(ctx: &Ctx, _caller: &Caller, args: &Value) -> Value {
    dress_window(ctx, _caller);
    let url = args.get("url").and_then(|u| u.as_str()).unwrap_or("about:blank").to_string();
    if !alive(ctx) { return open(ctx, _caller, args); }
    let mut c = json!({ "action": "newtab", "url": url });
    aim(&mut c);
    match run(c, Duration::from_secs(70)) {
        Ok(r) => { let w = r["windowId"].as_i64().unwrap_or(0); if w > 0 { st().cur.lock().unwrap().0 = w; }
            ok(json!({ "opened": true, "url": r["url"], "title": r["title"], "tabId": r["tabId"], "windowId": r["windowId"],
                       "_hint": "Tab opened in the extension-driven window (no CDP debug port). pup_eval {tabId} reads it." })) }
        Err(e) => e,
    }
}

/// pup_screenshot {tabId?} — PNG of a tab via chrome.tabs.captureVisibleTab (no CDP).
pub fn screenshot(ctx: &Ctx, _caller: &Caller, args: &Value) -> Value {
    if !alive(ctx) { return not_running(); }
    let mut c = json!({ "action": "screenshot" });
    aim(&mut c);
    if let Some(t) = args.get("tabId") { c["tabId"] = t.clone(); }
    match run(c, Duration::from_secs(60)) {
        Ok(r) => {
            let data = r["dataUrl"].as_str().unwrap_or("");
            let b64 = data.split(',').nth(1).unwrap_or("");
            match pup_cdp::base64_decode(b64) {
                Ok(bytes) => {
                    let path = ctx.shots_dir().join(format!("ext-{}.png", pup_core::now_ms()));
                    let _ = std::fs::write(&path, &bytes);
                    ok(json!({ "filePath": path.to_string_lossy().replace('\\', "/"), "sizeKB": bytes.len() / 1024,
                        "tabId": r["tabId"], "_hint": "PNG of the extension-driven tab (captureVisibleTab: the VISIBLE area of the active tab, not a full-page CDP capture). READ the file before asserting what it shows." }))
                }
                Err(e) => err("screenshot_decode_failed", e.to_string(), None),
            }
        }
        Err(e) => e,
    }
}

/// pup_switch_tab {tabId} — bring a tab to the front of the extension-driven window.
pub fn activate(ctx: &Ctx, _caller: &Caller, args: &Value) -> Value {
    dress_window(ctx, _caller);
    if !alive(ctx) { return not_running(); }
    let mut c = json!({ "action": "activate" });
    aim(&mut c);
    if let Some(t) = args.get("tabId") { c["tabId"] = t.clone(); }
    match run(c, Duration::from_secs(30)) { Ok(r) => ok(json!({ "tabId": r["tabId"] })), Err(e) => e }
}
/// 2.0.503: give the extension-driven window the SAME taskbar identity and placement a normal pup window
/// gets. Both live in the CDP birth path, so shipping this lane without them regressed the user experience:
/// John saw a raw "Chrome for Testing" taskbar icon and a window about 60% of his screen width. Neither
/// needs CDP — pup stamps identity through ab on a plain HWND, and ab can place the window.
/// Give every extension-driven window the same look a CDP pup window has: MAXIMIZED, wearing the
/// owning thread's taskbar tile. Idempotent (a window is dressed once), so it is cheap to call from
/// every verb rather than only from the cold-launch path.
///
/// 2.0.504, after John: "why is your window sizing so screwed up ... your pup window is like 60% the
/// width of my screen?" and "why is your windows taskbar icon still wrong?". Three real bugs, all here:
///   1. `launch_extdrive` never passed `--start-maximized` (the CDP lane's `chrome_switches` does), so
///      Chrome opened at whatever size its profile remembered: measured 1575px of a 2582px work area.
///   2. this ran ONLY on the cold-launch path of `open`, and that path returns early with
///      `starting:true`, so on a warm browser it never ran at all: pup.log had ZERO stamp lines.
///   3. the placement call named its args `width`/`height` while ab takes `w`/`h`, and the result was
///      swallowed with `let _ =`, so the rejection was invisible. Maximizing needs no bounds math and
///      no DPI correction (this exe is DPI-unaware, so SPI_GETWORKAREA lies about the work area).
/// Put the lane's window at the work area in the NORMAL state. Run on every open: dress_window fits a
/// window only the first time it dresses it, so a window that later ends up minimized (John has now hit
/// this three times: pup said the lane was up and there was nothing on his screen) never got re-asserted.
/// chrome.windows.update with state normal also restores a minimized window, which ab geometry must not
/// do here because it would raise the window.

/// Late rebind. An ext session whose birth outran open_window_ext's hwnd wait keeps hwnd None for
/// ever, so identity::stamp has nothing to stamp and the button wears Chrome's STOCK icon (John,
/// 2026-09-14: kicad's aiflow-overlay window, "did you regress on the windows taskbar icons?"). The
/// 2.0.545 birth (about:blank, identity shim, then the real URL) can take longer than that wait on a
/// cold browser. Runs every health tick and on pup_rescan: bind every unbound ext session to the lane
/// window and stamp it, exactly as a timely birth would have.
pub fn rebind_unbound(ctx: &Ctx) -> usize {
    // Three ways an ext window ends up wearing Chrome's stock icon, all seen 2026-09-14 in pup.log:
    // born bound to a TRANSIENT hwnd (the about:blank launch window Chrome replaces: "HWND 13510448 is not
    // a valid window"), re-adopted by recovery after a bridge restart with the right hwnd but NO stamp,
    // or never bound at all. One test covers them: unbound, bound to a dead hwnd, or bound and unstamped.
    let unbound: Vec<String> = ctx.sessions.all().into_iter()
        .filter(|s| crate::transport::is_ext(s) && (s.hwnd.is_none() || s.app_id.is_none() || !s.hwnd.map(|h| ctx.host.is_alive(h)).unwrap_or(false)))
        .map(|s| s.id).collect();
    if unbound.is_empty() { return 0; }
    let caller = crate::identity::internal_caller();
    let mut n = 0;
    for sid in unbound {
        let Some(sess) = ctx.sessions.get(&sid) else { continue };
        let Some(h) = lane_hwnd_for(ctx, &sess) else { continue };
        // Bind the OS handle AND Chrome's own windowId: windowId is exact and survives title changes, so a
        // later pass can verify this binding instead of re-deriving it from a title.
        let wid = window_id_for_hwnd(ctx, &sid);
        ctx.sessions.with_mut(&sid, |x| { x.hwnd = Some(h); if x.window_id.is_none() { x.window_id = wid; } });
        if let Some(s) = ctx.sessions.get(&sid) { ctx.sessions.save_file(&s); }
        crate::identity::stamp(ctx, &sid, &caller, true);
        crate::activity::on_open(ctx, &sid, h, &caller);
        pup_core::log(format!("[ext] bound window {h} to session \"{sid}\" and stamped its identity (late rebind)"));
        n += 1;
    }
    n
}

/// 2.0.694: adopt windows Chrome made outside pup. A tab dragged out of a pup window, or a page that opens a
/// new window, makes a browser window no session owns, and nothing ever stamped it, so it sat on the taskbar
/// with Chrome for Testing's stock icon (John, 2026-10-04: "why am i getting cft icons in my windows taskbar?").
/// Each such window becomes a session owned by the thread it came from: the thread whose window the tab was
/// dragged out of (pup still lists that tab there), else the thread whose tab opened it. rebind_unbound then
/// binds its OS window and stamps the thread's tile, exactly like a window pup opened itself. A window must
/// stay unowned for 5 s first, so a window pup is opening right now is never taken.
/// How many unowned windows adopt_stray_windows saw but has not adopted yet (they are inside their 5 s wait).
/// reload_stale_worker never cycles the browser while this is above zero: a window with no session has no file,
/// so the cycle would close it for good (John's laptop, 2.0.694 install: an "AI Flow" page and a YouTube video).
pub static STRAYS_WAITING: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(usize::MAX);   // MAX = not scanned yet
/// 2.0.724: a pup window whose tab is still on the birth page 20 s after it was born never reached its address (the
/// navigate after birth failed or timed out). It sat on John's taskbar titled PUPBIRTH-... (2026-10-17). Send it to the
/// address its session recorded; at most 3 tries per tab, a minute apart.
/// pup's own birth page. Never recorded as a tab's address: it would replace the address the window was opened for,
/// and rescue_birth_tabs could then never send a stuck window on (found testing 2.0.724 on ConfRoomROG).
pub fn is_birth_url(u: &str) -> bool { u.starts_with("chrome-extension://") && u.contains("/birth.html") }
/// Record a live address for a tab, unless it is the birth page and the tab already holds a real address.
pub fn keep_url(cur: &mut String, live: &str) { if live.is_empty() || (is_birth_url(live) && !cur.is_empty() && !is_birth_url(cur)) { return; } *cur = live.to_string(); }

pub fn rescue_birth_tabs(ctx: &Ctx) -> usize {
    static TRIED: Mutex<Option<std::collections::HashMap<i64, (u32, u64)>>> = Mutex::new(None);
    if !alive(ctx) { return 0; }
    let Ok(r) = run(json!({ "action": "tabs" }), Duration::from_secs(10)) else { return 0 };
    let tabs = r["tabs"].as_array().cloned().unwrap_or_default();
    let now = pup_core::now_ms();
    let sessions = ctx.sessions.all();
    let mut tried = TRIED.lock().unwrap();
    let tried = tried.get_or_insert_with(Default::default);
    let mut n = 0;
    for t in &tabs {
        let u = t["url"].as_str().unwrap_or("");
        if !is_birth_url(u) { continue; }
        let (Some(tab), Some(wid)) = (t["tabId"].as_i64(), t["windowId"].as_i64()) else { continue };
        let Some(s) = sessions.iter().find(|s| s.window_id == Some(wid)) else { continue };
        if now.saturating_sub(s.born_at_ms.max(s.created_ms)) < 20_000 { continue; }
        let target = s.tabs.iter().find(|x| x.target_id == tab.to_string()).or_else(|| s.tabs.get(0)).map(|x| x.url.clone()).unwrap_or_default();
        if target.is_empty() || is_blank_or_birth(&target) { continue; }
        let e = tried.entry(tab).or_insert((0, 0));
        if e.0 >= 3 || now.saturating_sub(e.1) < 60_000 { continue; }
        *e = (e.0 + 1, now);
        let ok = run(json!({ "action": "navigate", "url": target, "tabId": tab, "waitMs": 20000 }), Duration::from_secs(25)).is_ok();
        pup_core::log(format!("[birth] \"{}\" tab {tab} was still on the birth page {} s after birth; sent it to {target} (try {}): {}", s.id, now.saturating_sub(s.born_at_ms.max(s.created_ms)) / 1000, e.0, if ok { "ok" } else { "failed" }));
        if ok { n += 1; }
    }
    n
}

pub static FAST_ADOPT_UNTIL: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);

/// 2.0.735: after a click on an extension-lane window, give a popup it opened up to ~3 s to appear and adopt it now.
/// Returns the new pup window ids (for the click's answer).
pub fn adopt_popups_after_click(ctx: &Ctx, sid: &str) -> Vec<String> {
    let before: std::collections::HashSet<String> = ctx.sessions.all().iter().map(|s| s.id.clone()).collect();
    for _ in 0..6 {
        std::thread::sleep(Duration::from_millis(500));
        FAST_ADOPT_UNTIL.store(pup_core::now_ms() + 1500, std::sync::atomic::Ordering::Relaxed);
        if adopt_stray_windows(ctx) > 0 { break; }
    }
    ctx.sessions.all().iter().map(|s| s.id.clone()).filter(|id| !before.contains(id) && id.starts_with(sid)).collect()
}

pub fn adopt_stray_windows(ctx: &Ctx) -> usize {
    static SEEN: Mutex<Option<std::collections::HashMap<i64, u64>>> = Mutex::new(None);
    if !alive(ctx) { return 0; }
    let Ok(_birth) = crate::BIRTH_LOCK.try_lock() else { return 0 };
    let Ok(r) = run(json!({ "action": "tabs" }), Duration::from_secs(10)) else { return 0 };
    let tabs = r["tabs"].as_array().cloned().unwrap_or_default();
    let sessions = ctx.sessions.all();
    let owned: std::collections::HashSet<i64> = sessions.iter().filter_map(|s| s.window_id).collect();
    let mut wins: std::collections::BTreeMap<i64, Vec<Value>> = std::collections::BTreeMap::new();
    for t in &tabs { if let Some(w) = t["windowId"].as_i64() { if !owned.contains(&w) { wins.entry(w).or_default().push(t.clone()); } } }
    let now = pup_core::now_ms();
    let mut seen = SEEN.lock().unwrap();
    let seen = seen.get_or_insert_with(Default::default);
    seen.retain(|w, _| wins.contains_key(w));
    let mut n = 0;
    let mut waiting = 0;
    for (wid, wtabs) in wins {
        // about:blank-only windows and pup's own unfinished births (birth.html) are close_orphan_blank_windows' job.
        // 2.0.719: births too. A birth that never finished was adopted as "pup-window-2" with no thread and sat on John's
        // taskbar titled PUPBIRTH-... (2026-10-13).
        if wtabs.iter().all(|t| is_blank_or_birth(t["url"].as_str().unwrap_or(""))) { continue; }
        let first = *seen.entry(wid).or_insert(now);
        // 2.0.735: right after a pup_click (FAST_ADOPT_UNTIL), a window a pup page opened is adopted at once, so the
        // click's answer can name the popup (pup-bridge#136: the AI waited 0 s, saw nothing, and reached for the mouse).
        let fast = now < FAST_ADOPT_UNTIL.load(std::sync::atomic::Ordering::Relaxed) && wtabs.iter().any(|t| t["openerTabId"].as_i64().is_some());
        if !fast && now.saturating_sub(first) < 5000 { waiting += 1; continue; }
        let ids: Vec<String> = wtabs.iter().filter_map(|t| t["tabId"].as_i64()).map(|i| i.to_string()).collect();
        let openers: Vec<String> = wtabs.iter().filter_map(|t| t["openerTabId"].as_i64()).map(|i| i.to_string()).collect();
        let from_drag = sessions.iter().find(|s| s.tabs.iter().any(|t| ids.contains(&t.target_id)));
        let from_opener = sessions.iter().find(|s| s.tabs.iter().any(|t| openers.contains(&t.target_id)));
        let origin = from_drag.or(from_opener);
        let how = if from_drag.is_some() { "a tab dragged out of" } else if from_opener.is_some() { "a page in" } else { "nothing pup knows" };
        let base = origin.map(|o| o.id.clone()).unwrap_or_else(|| "pup-window".into());
        let mut k = 2; let mut sid = format!("{base}-{k}");
        while ctx.sessions.get(&sid).is_some() { k += 1; sid = format!("{base}-{k}"); }
        let profile = origin.map(|o| o.profile.clone()).unwrap_or_else(|| cur_profile());
        let mut s = pup_session::Session::new(&sid, &profile, origin.and_then(|o| o.owner.as_deref()), origin.and_then(|o| o.owner_container.as_deref()), 0, None);
        s.transport = "ext".into(); s.window_id = Some(wid);
        for t in &wtabs {
            let bid = t["tabId"].as_i64().unwrap_or(0);
            let mut tab = crate::transport::ext_tab(&mut s, bid, t["url"].as_str().unwrap_or(""), t["title"].as_str().unwrap_or(""));
            // a dragged-out tab keeps the id the AI knew it by
            if let Some(old) = from_drag.and_then(|o| o.tabs.iter().find(|x| x.target_id == bid.to_string())) { if !s.tabs.iter().any(|x| x.tab_id == old.tab_id) { tab.tab_id = old.tab_id.clone(); } }
            s.tabs.push(tab);
        }
        let top = s.tabs.iter().filter_map(|t| t.tab_id.strip_prefix("tab-")?.parse::<u32>().ok()).max().unwrap_or(0);
        s.next_tab_seq = s.next_tab_seq.max(top + 1);
        if let Some(i) = wtabs.iter().position(|t| t["active"] == json!(true)) { s.active_tab = i; }
        // the moved tabs leave the window they came from
        if let Some(o) = from_drag {
            ctx.sessions.with_mut(&o.id, |x| { x.tabs.retain(|t| !ids.contains(&t.target_id)); if x.active_tab >= x.tabs.len() { x.active_tab = x.tabs.len().saturating_sub(1); } });
            if let Some(o2) = ctx.sessions.get(&o.id) { ctx.sessions.save_file(&o2); }
        }
        ctx.sessions.insert(s.clone()); ctx.sessions.save_file(&s);
        pup_core::log(format!("[lane] adopted window {wid} ({} tab(s)) that Chrome opened outside pup, from {how} \"{}\": it is \"{sid}\", owned by {}", s.tabs.len(), origin.map(|o| o.id.as_str()).unwrap_or("-"), s.owner.as_deref().unwrap_or("no thread")));
        seen.remove(&wid);
        n += 1;
    }
    STRAYS_WAITING.store(waiting, std::sync::atomic::Ordering::Relaxed);
    n
}

/// An empty page, or pup's own birth page (a window being born, or one whose birth never finished).
fn is_blank_or_birth(u: &str) -> bool { u.is_empty() || u == "about:blank" || (u.starts_with("chrome-extension://") && u.contains("/birth.html")) }

/// Close windows no session owns whose every tab is about:blank (or pup's own unfinished birth page): the startup window a lane relaunch or a
/// concurrent open leaves behind (John, 2026-09-14: a stray "about:blank - Google Chrome for Testing" on
/// his desktop). The CDP lane does the same in close_orphan_blank_targets. `keep` is a window just
/// created for a session that has not been recorded yet.
pub(crate) fn close_orphan_blank_windows(ctx: &Ctx, keep: Option<i64>) -> usize {
    if !alive(ctx) { return 0; }
    // 2.0.595: never while a window is being born (a cold open's window is about:blank until its deferred
    // navigate), and never the lane's LAST window: closing it exits Chrome and takes the worker with it.
    if crate::BIRTH_LOCK.try_lock().is_err() { return 0; }
    let Ok(r) = run(json!({ "action": "tabs" }), Duration::from_secs(10)) else { return 0 };
    let owned: std::collections::HashSet<i64> = ctx.sessions.all().iter().filter_map(|s| s.window_id).collect();
    let mut wins: std::collections::HashMap<i64, Vec<String>> = std::collections::HashMap::new();
    for t in r["tabs"].as_array().cloned().unwrap_or_default() {
        if let Some(w) = t["windowId"].as_i64() { wins.entry(w).or_default().push(t["url"].as_str().unwrap_or("").to_string()); }
    }
    let mut n = 0;
    if wins.len() <= 1 { return 0; }
    for (w, urls) in wins {
        if Some(w) == keep || owned.contains(&w) || urls.is_empty() { continue; }
        if urls.iter().all(|u| is_blank_or_birth(u)) {
            if run(json!({ "action": "close", "windowId": w }), Duration::from_secs(10)).is_ok() { n += 1; pup_core::log(format!("[lane] closed orphan about:blank window {w} that no session owns")); }
        }
    }
    n
}

/// The OS window that belongs to ONE ext session. The lane is one browser, but several threads' windows
/// live in it, so "the lane window" is ambiguous (2026-09-14: the rebind bound kicad's session to another
/// thread's window and then to a dead handle). Chrome titles a window "<active tab title> - Google Chrome
/// for Testing", so match on the session's active tab title; with a single lane window there is nothing
/// to disambiguate and that window is it.
/// Chrome's own windowId for the window holding this session's active tab, read from the extension.
fn window_id_for_hwnd(ctx: &Ctx, sid: &str) -> Option<i64> {
    let s = ctx.sessions.get(sid)?;
    let tab = s.active()?;
    let want = tab.target_id.parse::<i64>().ok()?;
    let r = run(json!({ "action": "tabs" }), Duration::from_secs(10)).ok()?;
    r["tabs"].as_array()?.iter().find(|t| t["tabId"].as_i64() == Some(want)).and_then(|t| t["windowId"].as_i64())
}

/// 2.0.630: bind a newborn by its birth token, deterministically (pup-window-targeting R4, pup-no-wiggle). The
/// extension creates every window on birth.html, whose title is "PUPBIRTH-<token>", so the one lane window
/// carrying that title IS this session's, however many windows are being born at once and whatever placeholder
/// Chrome makes at launch. No width filter: the window is still MINIMIZED here, and a minimized window reports
/// a tiny rect at -32000, which is exactly the shape the geometry filters exclude.
pub(crate) fn hwnd_by_birth_token(ctx: &Ctx, token: &str, taken: &std::collections::HashSet<i64>, wait: Duration) -> Option<i64> {
    let want = format!("PUPBIRTH-{token}");
    let t0 = std::time::Instant::now();
    loop {
        let pids = crate::extdrive_pids();
        if !pids.is_empty() {
            let m: Vec<i64> = ctx.host.windows_for_pids(&pids, Some("Chrome_WidgetWin_1")).into_iter()
                .filter(|w| w.title.starts_with(&want) && !taken.contains(&w.hwnd)).map(|w| w.hwnd).collect();
            if m.len() == 1 { return Some(m[0]); }
        }
        if t0.elapsed() >= wait { return None; }
        std::thread::sleep(Duration::from_millis(25));
    }
}

pub(crate) fn lane_hwnd_for(ctx: &Ctx, s: &pup_session::Session) -> Option<i64> {
    let pids = crate::extdrive_pids();
    if pids.is_empty() { return None; }
    // A handle another session already owns is NEVER a candidate. Handing one handle to two sessions is
    // what put all-engineering, NSF and pup-bridge on hwnd 590484, each stamping its tile over the last
    // (John, 2026-09-14). This check is why readopt_ext never produced the bug and this path did.
    let taken = crate::taken_hwnds(ctx, &s.id);
    let wins: Vec<pup_host::HostWindow> = ctx.host.windows_for_pids(&pids, Some("Chrome_WidgetWin_1")).into_iter()
        .filter(|w| w.visible && w.rect.2 - w.rect.0 > 200 && !taken.contains(&w.hwnd)).collect();
    // The sole-window shortcut is pup-window-targeting rule 4 ("sole window OF A PROFILE"), and it is only
    // sound when this really is the only ext window in play. The ext lane is one profile shared by MANY
    // threads, so applying it per-session handed every asking session the same window. With owned handles
    // already excluded, one remaining candidate and exactly one claimant is the honest version of that rule.
    let ext_sessions = ctx.sessions.all().iter().filter(|x| crate::transport::is_ext(x)).count();
    if wins.len() == 1 && ext_sessions <= 1 { return wins.first().map(|w| w.hwnd); }
    let title = s.active().map(|t| t.title.trim().to_string()).unwrap_or_default();
    if !title.is_empty() {
        let m: Vec<&pup_host::HostWindow> = wins.iter().filter(|w| w.title.trim_start().starts_with(&title)).collect();
        // exactly one title match, or it is not a match: two windows on the same page are indistinguishable
        // by title and picking either is a coin flip that clobbers the loser's taskbar button.
        if m.len() == 1 { return Some(m[0].hwnd); }
        if m.len() > 1 { pup_core::log(format!("[ext] \"{}\": {} lane windows share the title \"{}\" - refusing to guess which is this thread's; leaving it BARE (bare > wrong)", s.id, m.len(), title)); return None; }
    }
    // no unambiguous match: never guess another thread's window; leave it unbound for the next pass
    None
}

/// The lane window's hwnd (there is one lane window; several tabs live in it).
pub(crate) fn lane_hwnd(ctx: &Ctx) -> Option<i64> {
    let pids = crate::extdrive_pids();
    if pids.is_empty() { return None; }
    ctx.host.windows_for_pids(&pids, Some("Chrome_WidgetWin_1")).into_iter()
        .filter(|w| w.visible && w.rect.2 - w.rect.0 > 200).map(|w| w.hwnd).next()
}

/// The taskbar contract the CDP lane keeps and this lane skipped (John, 2026-09-13: "why are you
/// causing flashing of the windows taskbar icon ... you're supposed to default to showing progress
/// bars in the icon. and why do i not see the overlay icon"). Same ab calls activity.rs makes:
/// an indeterminate bar while a command runs, cleared when it ends; taskbarActivity off/flash and
/// minimalTouch are honoured exactly as there.
fn with_lane_progress<T>(ctx: &Ctx, caller: &Caller, args: &Value, work: impl FnOnce() -> T) -> T {
    // ONE progress implementation, in activity.rs. This is just the lane's entry point. 2.0.699: the bar goes
    // on the window the command is FOR. It used to go on lane_hwnd, the first window Windows listed, so with
    // several threads' windows open it painted another thread's button. A window being born has no handle
    // yet: no bar here (start_icon_apply covers a new window's first seconds).
    match crate::resolve(ctx, caller, args).ok().and_then(|s| s.hwnd) {
        Some(hwnd) => crate::activity::progress_on_hwnd(ctx, hwnd, "ext-drive", work),
        None => work(),
    }
}

/// After an open / navigate / tab change on this lane: clear the attention tint and paint the overlay
/// for every session on the lane window, through the ONE composer (identity::overlay, in the binary
/// since 2.0.568). The in-page JS composer this used to carry is gone.
fn dress_after(ctx: &Ctx, caller: &Caller) {
    let Some(hwnd) = lane_hwnd(ctx) else { return };
    crate::activity::clear_attention_later(ctx, hwnd);   // 2.0.637: never ab's clear here (it raises + activates)
    if ctx.settings.get("overlayBadges", "on") == "off" { return; }
    for s in ctx.sessions.all().into_iter().filter(|s| crate::transport::is_ext(s) && s.hwnd.is_some()) { crate::identity::overlay(ctx, &s.id, caller); }
}
pub fn fit_now() {
    match run(json!({ "action": "fit" }), Duration::from_secs(10)) {
        Ok(r) => pup_core::log(format!("[extdrive] fit {} window(s) to the work area {}", r["fitted"], r["workArea"])),
        Err(e) => pup_core::log(format!("[extdrive] fit failed: {e}")),
    }
}

/// Once per lane browser: send real Chrome's brand list on every request (consumerBrands, default on).
pub fn apply_consumer_brands(ctx: &Ctx) {
    // 6 s, not 10: on a cold start this runs before the first page and every second here is on the open's clock.
    static DONE_LANE: Mutex<String> = Mutex::new(String::new());
    let lane = st().token.lock().unwrap().clone();
    if lane.is_empty() || *DONE_LANE.lock().unwrap() == lane { return; }
    let on = ctx.settings.get("consumerBrands", "on") != "off";
    let mut cmd = json!({ "action": "setBrands", "on": on });
    if on { match crate::consumer_identity_strings(ctx) { Some(v) => { for (k, val) in v.as_object().unwrap() { cmd[k] = val.clone(); } } None => { pup_core::log("[lane] consumer identity: no installed Chrome version known; leaving CfT's own".to_string()); return; } } }
    match run(cmd, Duration::from_secs(6)) {
        Ok(r) => { *DONE_LANE.lock().unwrap() = lane; pup_core::log(format!("[lane] consumer identity {}: ua={} brands={} err={}", if on { "ON" } else { "off" }, r["ua"].as_str().unwrap_or("-"), r["brands"].as_str().unwrap_or("-"), r["error"].as_str().unwrap_or("none"))); }
        Err(e) => pup_core::log(format!("[lane] consumer brands not applied: {e}")),
    }
}

pub fn dress_window(ctx: &Ctx, caller: &Caller) {
    apply_consumer_brands(ctx);
    static DRESSED: Mutex<Option<std::collections::HashSet<i64>>> = Mutex::new(None);
    let pids = crate::extdrive_pids();
    if pids.is_empty() { return; }
    let wins = ctx.host.windows_for_pids(&pids, Some("Chrome_WidgetWin_1"));
    let name = if caller.thread.is_empty() { "pup".to_string() } else { caller.thread.clone() };
    let mut fresh = 0;
    for w in wins.iter().filter(|w| w.visible && w.rect.2 - w.rect.0 > 200) {
        {
            let mut g = DRESSED.lock().unwrap();
            let set = g.get_or_insert_with(std::collections::HashSet::new);
            if !set.insert(w.hwnd) { continue; }
        }
        fresh += 1;
        crate::identity::stamp_hwnd(ctx, caller, "pup-extdrive", &name, w.hwnd);
    }
    // Geometry goes through the EXTENSION (chrome.windows.update, plain bounds, logical px both
    // sides), never through ab: pup-bridge-dev's rule is "CDP bounds only, NEVER windowState
    // maximized, NEVER ab desktop_set_window_bounds - both RAISE the window", and this lane's
    // stand-in for CDP is its own extension channel.
    if fresh > 0 {
        match run(json!({ "action": "fit" }), Duration::from_secs(10)) {
            Ok(r) => pup_core::log(format!("[extdrive] fit {} window(s) to the work area {}", r["fitted"], r["workArea"])),
            Err(e) => pup_core::log(format!("[extdrive] fit failed: {e}")),
        }
    }
}

/// 2.0.578: a browser handle for the extension lane whose Cdp forwards every command to the extension's
/// DevTools driver (nbe's cdp.js, via the worker's `cdp` action). Verbs written against BrowserHandle
/// (click, hover, type, press_key, scroll, fill, element/full-res screenshots) run unchanged on this lane.
// EXT_HANDLE lives in LaneState.handle since 2.0.659
pub fn ext_handle() -> std::sync::Arc<crate::BrowserHandle> {
    let lane_st = st(); let mut g = lane_st.handle.lock().unwrap();
    if let Some(h) = g.as_ref() { return h.clone(); }
    let via: pup_cdp::Via = Box::new(|session: Option<&str>, method: &str, params: Value, timeout: Duration| {
        let tab = session.and_then(|s| s.strip_prefix("ext:")).and_then(|x| x.parse::<i64>().ok());
        let mut cmd = json!({ "action": "cdp", "method": method, "params": params, "timeoutMs": timeout.as_millis() as u64 });
        if let Some(t) = tab { cmd["tabId"] = json!(t); }
        match run(cmd, timeout + Duration::from_secs(3)) {
            Ok(r) => Ok(r.get("result").cloned().unwrap_or(Value::Null)),
            Err(e) => Err(pup_cdp::CdpError::Protocol { method: method.into(), code: 0, message: e["error"].as_str().unwrap_or("the extension lane did not answer").to_string() }),
        }
    });
    let h = std::sync::Arc::new(crate::BrowserHandle { cdp: pup_cdp::Cdp::via(via), launched: pup_browser::Launched { pid: 0, cdp_port: 0, candidate: pup_browser::Candidate { kind: "extension-lane".into(), source: "nbe driver".into(), executable_path: std::path::PathBuf::new(), build_id: None }, profile_dir: std::path::PathBuf::new(), reused: true } });
    *g = Some(h.clone()); h
}

#[cfg(test)]
mod tests {
    // 2.0.723: a lane dropped while its thread still works in it (the idle sweep during a 20 s close) must never
    // resolve to the adom-you lane; that wiped the user's main browser record and ended it.
    #[test] fn birth_page_never_replaces_a_real_address() {
        let b = "chrome-extension://liomcaladmooagjiebkfgegciadapnpi/birth.html?token=x1";
        let mut u = "https://example.com/".to_string(); super::keep_url(&mut u, b); assert_eq!(u, "https://example.com/");
        super::keep_url(&mut u, "https://example.org/"); assert_eq!(u, "https://example.org/");
        let mut e = String::new(); super::keep_url(&mut e, b); assert_eq!(e, b);
        super::keep_url(&mut u, ""); assert_eq!(u, "https://example.org/");
    }
    #[test] fn dropped_lane_never_aliases_adom_you() {
        super::default_lane().token.lock().unwrap().clear();
        *super::default_lane().token.lock().unwrap() = "keep-me".into();
        super::enter_profile("iso-test-723");
        super::drop_lane_for_profile("iso-test-723");
        let l = super::st();
        assert_eq!(l.profile, "iso-test-723");
        l.token.lock().unwrap().clear();
        assert_eq!(*super::default_lane().token.lock().unwrap(), "keep-me");
        super::leave_profile();
        super::drop_lane_for_profile("iso-test-723");
    }
    #[test] fn blank_or_birth() {
        assert!(super::is_blank_or_birth("")); assert!(super::is_blank_or_birth("about:blank"));
        assert!(super::is_blank_or_birth("chrome-extension://liomcaladmooagjiebkfgegciadapnpi/birth.html?token=b1y2"));
        assert!(!super::is_blank_or_birth("https://example.com/birth.html")); assert!(!super::is_blank_or_birth("https://wiki.adom.inc/adom/pup-bridge"));
    }
}