api: add request/response interception API to Page (#306)
Expose the interception infrastructure that already existed internally but was
private, so embedders can intercept, observe, mock, or block JS fetch()/XHR
without forking or unsafe casts.
On obscura::Page:
- add_preload_script: inject JS before any page <script> (CDP
Page.addScriptToEvaluateOnNewDocument). The field already ran the scripts;
this only adds the method.
- enable_interception: returns a receiver for every JS fetch()/XHR request;
resolve each through its resolver with InterceptResolution::{Continue,
Fulfill, Fail} to pass, mock, or block (CDP Fetch.enable equivalent).
- on_request / on_response: passive callbacks. on_response is the main path for
capturing API response payloads from SPAs.
on_request/on_response previously fired only for navigation; op_fetch_url now
invokes them for JS fetch()/XHR too (both reqwest and stealth paths), gated on
a non-empty callback vector so the common no-callback case pays nothing.
Re-export InterceptedRequest, InterceptResolution, RequestInfo, ResourceType,
RequestCallback, ResponseCallback, Response from the obscura crate.
Integration test tests/interception.rs exercises all three against a local echo
server.
This commit is contained in:
1 parent
12b927f476
commit
1f5066fc30
8 files changed
+261
-7
No files matched your search
@@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||
use base64::{engine::general_purpose::STANDARD as BASE64, Engine as _};
|
||||
use obscura_dom::{parse_html, DomTree};
|
||||
use obscura_js::runtime::ObscuraJsRuntime;
|
||||
use obscura_net::{ObscuraHttpClient, ObscuraNetError, Response};
|
||||
use obscura_net::{ObscuraHttpClient, ObscuraNetError, RequestCallback, Response, ResponseCallback};
|
||||
use url::Url;
|
||||
|
||||
use crate::context::BrowserContext;
|
||||
@@ -1526,6 +1526,46 @@ impl Page {
|
||||
self.preload_scripts = scripts;
|
||||
}
|
||||
|
||||
/// Append a script that runs in the page before any of the page's own
|
||||
/// `<script>` tags, matching CDP `Page.addScriptToEvaluateOnNewDocument`.
|
||||
/// Takes effect on the next navigation (`goto` / `navigate*`).
|
||||
pub fn add_preload_script(&mut self, script: &str) {
|
||||
self.preload_scripts.push(script.to_string());
|
||||
}
|
||||
|
||||
/// Enable CDP-Fetch-style interception of JS-initiated `fetch()`/XHR.
|
||||
/// Returns a receiver yielding every such request; resolve each through its
|
||||
/// `resolver` with `InterceptResolution::{Continue, Fulfill, Fail}` to pass,
|
||||
/// mock, or block it. Works in stealth and non-stealth. Mirrors how the CDP
|
||||
/// server wires the channel (`obscura-cdp/src/server.rs`).
|
||||
pub fn enable_interception(
|
||||
&mut self,
|
||||
) -> tokio::sync::mpsc::UnboundedReceiver<obscura_js::ops::InterceptedRequest> {
|
||||
let (tx, rx) =
|
||||
tokio::sync::mpsc::unbounded_channel::<obscura_js::ops::InterceptedRequest>();
|
||||
self.set_intercept_tx(tx);
|
||||
self.enable_intercept(true);
|
||||
rx
|
||||
}
|
||||
|
||||
/// Register a passive callback fired for every JS `fetch()`/XHR (and
|
||||
/// navigation) request, once the method/headers/body are known and before it
|
||||
/// is sent. Non-blocking; use `enable_interception` to mutate or block.
|
||||
pub fn on_request(&mut self, cb: RequestCallback) {
|
||||
if let Ok(mut v) = self.http_client.on_request.try_write() {
|
||||
v.push(cb);
|
||||
}
|
||||
}
|
||||
|
||||
/// Register a passive callback fired with every JS `fetch()`/XHR (and
|
||||
/// navigation) response, including its body. Non-blocking. The main path for
|
||||
/// crawlers that need to capture API response payloads.
|
||||
pub fn on_response(&mut self, cb: ResponseCallback) {
|
||||
if let Ok(mut v) = self.http_client.on_response.try_write() {
|
||||
v.push(cb);
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn process_pending_navigation(&mut self) -> Result<bool, PageError> {
|
||||
if let Some((url, method, body)) = self.take_pending_navigation() {
|
||||
self.navigate_with_wait_post(
|
||||
|
||||
Reference in new issue
Block a user