diff --git a/README.md b/README.md index 9b84106..7abc41f 100644 --- a/README.md +++ b/README.md @@ -224,6 +224,9 @@ ff-rdp wait --text "Success" --wait-timeout 10000 # Wait for a JavaScript expression to become truthy ff-rdp wait --eval "document.readyState === 'complete'" +# Plain sleep, no condition or Firefox connection needed (--time is a legacy alias) +ff-rdp wait --sleep-ms 2000 + # List cookies ff-rdp cookies @@ -380,6 +383,17 @@ ff-rdp --no-daemon eval "1+1" whether that owner process is still alive and, if so, keeps the profile regardless of age. This is a positive "still in use" signal that closes the gap where a fully-idle-but-running Firefox could look stale by mtime alone. +- A marker naming a **dead** PID is reclaimed by the very next `launch` + immediately, without waiting out the age threshold — a dead owner is + definitive proof of abandonment, not just "old enough to guess at" (iter-142; + fixes an observed 62 profiles / 2.7 GB accumulating in a single day, all + younger than the old 7-day gate). +- Every `ff-rdp launch` also sweeps `~/.ff-rdp/` housekeeping files: stale + per-port spawn locks, the legacy port-less `daemon.spawn.lock` name, and + `daemon..throttle.json` state files whose recorded daemon PID is no + longer alive (iter-142). Previously this only ran on the rare + daemon-autostart path, so a session that reused an already-running daemon + never triggered it at all. - `ff-rdp profiles list` / `ff-rdp profiles prune` inspect and reclaim the profile directory explicitly; `ff-rdp doctor` warns when the profile store grows past 100 entries or 1 GiB. `profiles prune --all` skips the age gate diff --git a/crates/ff-rdp-cli/src/cli/args.rs b/crates/ff-rdp-cli/src/cli/args.rs index 740ed7a..39b1d9d 100644 --- a/crates/ff-rdp-cli/src/cli/args.rs +++ b/crates/ff-rdp-cli/src/cli/args.rs @@ -493,9 +493,16 @@ scope and `const`/`let` declarations never leak across calls. The Top-level `await` works (iter-132): `ff-rdp eval 'await Promise.resolve(41) + 1'` resolves to 42. Scripts containing `await` are transparently wrapped in an async IIFE before evaluation — no `--async` flag or extra syntax needed. A -single-expression script (no top-level `;`) auto-returns its value; a -multi-statement script needs an explicit `return` to surface a value (it -still runs either way — no SyntaxError). +single-expression script auto-returns its value. A multi-statement script +(statements separated by `;` OR by a plain newline — iter-142 fixed +ASI-separated scripts being misclassified and leaking the wrapper into a +SyntaxError) also auto-returns its value if the LAST statement is a bare +expression, e.g. `let x = await foo(); x + 1` returns `x + 1` — every +earlier statement still runs unwrapped, so an explicit `return` earlier in +the script keeps working. Only when the last statement is not a bare +expression (a declaration, a control-flow construct) does the script need +its own explicit `return` to surface a value (it still runs either way — no +SyntaxError). Output: {\"results\": , \"total\": 1, \"meta\": {...}} @@ -742,16 +749,24 @@ other. On success, the output gains {\"match_count\": N, \"chosen_index\": N}. Output: {\"results\": {\"typed\": true, \"tag\": \"INPUT\", \"value\": \"...\"}, \"total\": 1, \"meta\": {...}}")] Type(TypeArgs), - /// Wait for a condition to become true (polls every 100ms). - /// Exactly one of --selector, --text, --eval, or --ref must be specified. - #[command(long_about = "Wait for a condition to become true (polls every 100ms). + /// Wait for a condition to become true (polls every 100ms), or sleep for a fixed duration. + /// Exactly one of --selector, --text, --eval, --ref, or --sleep-ms must be specified. + #[command( + long_about = "Wait for a condition to become true (polls every 100ms), or sleep for a fixed duration. -Exactly one of --selector, --text, --eval, or --ref must be specified. +Exactly one of --selector, --text, --eval, --ref, or --sleep-ms must be specified. Use --ref to wait for an element identified by its ARIA-tree ref ID (daemon mode only). Equivalent to --selector but uses a stable ref handle. -Output: {\"results\": {\"matched\": true, \"elapsed_ms\": N, \"condition\": \"selector|text|eval\"}, \"total\": 1, \"meta\": {...}}")] +Use --sleep-ms for a plain delay with no condition and no Firefox +connection at all — e.g. `ff-rdp wait --sleep-ms 2000`. Prefer a real +condition (--selector/--text/--eval/--ref) whenever one exists; a fixed +sleep is always a guess about how long something takes. --timeout-ms does +not apply to --sleep-ms, which always runs for exactly its own duration. + +Output: {\"results\": {\"matched\": true, \"elapsed_ms\": N, \"condition\": \"selector|text|eval|sleep\"}, \"total\": 1, \"meta\": {...}}" + )] Wait(WaitArgs), /// List cookies via the Firefox StorageActor (includes httpOnly, secure, sameSite, etc.) #[command( @@ -1695,8 +1710,22 @@ pub struct WaitArgs { /// ARIA-tree ref ID from a previous dom/snapshot call (daemon mode only, e.g. 'e3') #[arg(long = "ref", value_name = "REF_ID", group = "condition")] pub ref_id: Option, + /// Plain sleep for this many milliseconds — no condition, no Firefox + /// connection, just a delay. For when you need to pace commands rather + /// than wait for a specific page state (use --selector/--text/--eval/--ref + /// instead whenever a real condition exists — a fixed sleep is always a + /// guess). The legacy spelling `--time` is also accepted as a hidden + /// alias (iter-142: this was the flag dogfooders reached for first). + #[arg( + long = "sleep-ms", + alias = "time", + value_name = "MS", + group = "condition" + )] + pub sleep_ms: Option, /// Timeout in milliseconds before giving up (canonical flag — use this one). /// The legacy spelling `--wait-timeout` is also accepted as a hidden alias. + /// Not used by --sleep-ms, which always runs for exactly its own duration. #[arg(long = "timeout-ms", alias = "wait-timeout", default_value_t = 5000)] pub wait_timeout: u64, } diff --git a/crates/ff-rdp-cli/src/commands/eval.rs b/crates/ff-rdp-cli/src/commands/eval.rs index f15e07c..a04fb37 100644 --- a/crates/ff-rdp-cli/src/commands/eval.rs +++ b/crates/ff-rdp-cli/src/commands/eval.rs @@ -104,6 +104,17 @@ pub(crate) fn load_script( /// `evaluateJSAsync` already knows how to await. `eval_path` stays /// `"page-await"` either way; from the caller's perspective only the /// previously-broken await scripts start working, nothing else changes. +/// +/// iter-142 Theme E fixed two follow-on defects in the same wrap: (1) the +/// single-vs-multi-statement heuristic (used to decide whether the wrap +/// synthesizes a `return`) only recognized `;` as a statement separator, so +/// an ASI-separated (newline-only) multi-statement script like +/// `await Promise.resolve(1)\n42` was misclassified as one expression and +/// wrapped into invalid JS — a syntax error reported past the end of the +/// user's input; (2) even when correctly classified as multi-statement, the +/// wrap never returned anything, so a trailing bare expression silently +/// became `{"type":"undefined"}` instead of its real value. See +/// [`top_level_statement_boundaries`] and [`wrap_top_level_await`]. pub(crate) fn build_script(user_script: &str, stringify: bool, _isolate: bool) -> String { // The stringify helper: if the value is already a string, return it as-is; // otherwise JSON.stringify it. This prevents double-encoding when the JS @@ -148,13 +159,15 @@ const STATEMENT_LEADING_KEYWORDS: &[&str] = &[ /// Best-effort (not a JS parser) check for whether `script` is a single /// expression, safe to wrap as `return (