Skip to content

Commit 52138f9

Browse files
committed
fix(browser): resource filter hang and double-dash stealth arg bug
- set_resource_filter now spawns a requestPaused handler that continues allowed requests and fails blocked ones; previously Fetch.enable intercepted every request with no handler, causing 100% navigation timeouts (closes #9) - browser.rs strips the '--' prefix before passing args to chromiumoxide's ArgsBuilder, which prepends '--' itself; the stealth arg --disable-blink-features=AutomationControlled was reaching Chrome as '----disable-blink-features=...' (closes #10)
1 parent 5d3e8f4 commit 52138f9

File tree

5 files changed

+60
-8
lines changed

5 files changed

+60
-8
lines changed

CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.1.9] - 2026-03-02
11+
12+
### Fixed
13+
14+
- `stygian-browser`: `set_resource_filter` previously called `Fetch.enable` but never subscribed to `Fetch.requestPaused` events, causing Chrome to intercept and hang every network request indefinitely — navigation always timed out when a resource filter was active. A background task now processes each paused event: blocked resource types receive `Fetch.failRequest(BlockedByClient)`, all others receive `Fetch.continueRequest` (closes [#9](https://github.com/greysquirr3l/stygian/issues/9))
15+
- `stygian-browser`: Chrome launch arguments including `--disable-blink-features=AutomationControlled` were being passed with a double-dash prefix (`----arg`) because chromiumoxide's `ArgsBuilder` prepends `--` to every key and stygian was also including the `--` in the string. The arg builder in `browser.rs` now strips the `--` prefix before handing each argument to chromiumoxide, so the stealth flag reaches Chrome correctly (closes [#10](https://github.com/greysquirr3l/stygian/issues/10))
16+
1017
## [0.1.8] - 2026-03-02
1118

1219
### Changed
@@ -163,7 +170,8 @@ Both crates are functional and well-tested, but APIs may evolve based on communi
163170

164171
---
165172

166-
[Unreleased]: https://github.com/greysquirr3l/stygian/compare/v0.1.8...HEAD
173+
[Unreleased]: https://github.com/greysquirr3l/stygian/compare/v0.1.9...HEAD
174+
[0.1.9]: https://github.com/greysquirr3l/stygian/compare/v0.1.8...v0.1.9
167175
[0.1.8]: https://github.com/greysquirr3l/stygian/compare/v0.1.7...v0.1.8
168176
[0.1.7]: https://github.com/greysquirr3l/stygian/compare/v0.1.6...v0.1.7
169177
[0.1.6]: https://github.com/greysquirr3l/stygian/compare/v0.1.5...v0.1.6

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ members = [
66
]
77

88
[workspace.package]
9-
version = "0.1.8"
9+
version = "0.1.9"
1010
edition = "2024"
1111
rust-version = "1.93.1"
1212
authors = ["Nick Campbell <s0ma@protonmail.com>"]

crates/stygian-browser/src/browser.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,10 @@ impl BrowserInstance {
9898
}
9999

100100
for arg in &args {
101-
builder = builder.arg(arg.as_str());
101+
// chromiumoxide's ArgsBuilder prepends "--" when formatting args, so
102+
// we strip any existing "--" prefix first to avoid "----arg" in Chrome.
103+
let stripped = arg.strip_prefix("--").unwrap_or(arg.as_str());
104+
builder = builder.arg(stripped);
102105
}
103106

104107
if let Some((w, h)) = config.window_size {

crates/stygian-browser/src/page.rs

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,9 @@ pub struct PageHandle {
190190
/// HTTP status code of the most recent main-frame navigation, or `0` if not
191191
/// yet captured. Written atomically by the listener spawned in `navigate()`.
192192
last_status_code: Arc<AtomicU16>,
193+
/// Background task processing `Fetch.requestPaused` events. Aborted and
194+
/// replaced each time `set_resource_filter` is called.
195+
resource_filter_task: Option<tokio::task::JoinHandle<()>>,
193196
}
194197

195198
impl PageHandle {
@@ -199,6 +202,7 @@ impl PageHandle {
199202
page,
200203
cdp_timeout,
201204
last_status_code: Arc::new(AtomicU16::new(0)),
205+
resource_filter_task: None,
202206
}
203207
}
204208

@@ -430,19 +434,30 @@ impl PageHandle {
430434

431435
/// Set a resource filter to block specific network request types.
432436
///
433-
/// **Note:** Requires Network.enable; called automatically.
437+
/// Enables `Fetch` interception and spawns a background task that continues
438+
/// allowed requests and fails blocked ones with `BlockedByClient`. Any
439+
/// previously set filter task is cancelled first.
434440
///
435441
/// # Errors
436442
///
437443
/// Returns a [`BrowserError::CdpError`] if the CDP call fails.
438444
pub async fn set_resource_filter(&mut self, filter: ResourceFilter) -> Result<()> {
439-
use chromiumoxide::cdp::browser_protocol::fetch::{EnableParams, RequestPattern};
445+
use chromiumoxide::cdp::browser_protocol::fetch::{
446+
ContinueRequestParams, EnableParams, EventRequestPaused, FailRequestParams,
447+
RequestPattern,
448+
};
449+
use chromiumoxide::cdp::browser_protocol::network::ErrorReason;
450+
use futures::StreamExt as _;
440451

441452
if filter.is_empty() {
442453
return Ok(());
443454
}
444455

445-
// Both builders are infallible — they return the struct directly (not Result)
456+
// Cancel any previously running filter task.
457+
if let Some(task) = self.resource_filter_task.take() {
458+
task.abort();
459+
}
460+
446461
let pattern = RequestPattern::builder().url_pattern("*").build();
447462
let params = EnableParams::builder()
448463
.patterns(vec![pattern])
@@ -460,7 +475,33 @@ impl PageHandle {
460475
message: e.to_string(),
461476
})?;
462477

478+
// Subscribe to requestPaused events and dispatch each one so navigation
479+
// is never blocked. Without this handler Chrome holds every intercepted
480+
// request indefinitely and the page hangs.
481+
let mut events = self
482+
.page
483+
.event_listener::<EventRequestPaused>()
484+
.await
485+
.map_err(|e| BrowserError::CdpError {
486+
operation: "Fetch.requestPaused subscribe".to_string(),
487+
message: e.to_string(),
488+
})?;
489+
490+
let page = self.page.clone();
463491
debug!("Resource filter active: {:?}", filter);
492+
let task = tokio::spawn(async move {
493+
while let Some(event) = events.next().await {
494+
let request_id = event.request_id.clone();
495+
if filter.should_block(event.resource_type.as_ref()) {
496+
let params = FailRequestParams::new(request_id, ErrorReason::BlockedByClient);
497+
let _ = page.execute(params).await;
498+
} else {
499+
let _ = page.execute(ContinueRequestParams::new(request_id)).await;
500+
}
501+
}
502+
});
503+
504+
self.resource_filter_task = Some(task);
464505
Ok(())
465506
}
466507

0 commit comments

Comments
 (0)