Skip to content

Commit

Permalink
Fix for iframes and future timeout (#174)
Browse files Browse the repository at this point in the history
* Detach from service workers and commandfuture timeout fix

* Fixes logic of check_lifecycle

* remove unwanted logging

* Add doc and fix for service worker
  • Loading branch information
chirok11 authored Sep 7, 2023
1 parent 2832c1b commit 379d9c7
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 13 deletions.
24 changes: 20 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@ categories = ["web-programming", "api-bindings", "development-tools::testing"]
[dependencies]
async-tungstenite = "0.22"
serde = { version = "1", features = ["derive"] }
async-std = { version = "1.5", features = ["attributes", "unstable"], optional = true }
async-std = { version = "1.5", features = [
"attributes",
"unstable",
], optional = true }
futures = "0.3"
chromiumoxide_types = { path = "chromiumoxide_types", version = "0.5"}
chromiumoxide_cdp = { path = "chromiumoxide_cdp", version = "0.5"}
chromiumoxide_types = { path = "chromiumoxide_types", version = "0.5" }
chromiumoxide_cdp = { path = "chromiumoxide_cdp", version = "0.5" }
chromiumoxide_fetcher = { path = "chromiumoxide_fetcher", version = "0.5", default-features = false, optional = true }
serde_json = "1"
which = "4"
Expand All @@ -28,7 +31,14 @@ base64 = "0.21"
fnv = "1"
futures-timer = "3"
cfg-if = "1"
tokio = { version = "1", features = ["rt", "rt-multi-thread", "time", "fs", "macros", "process"], optional = true }
tokio = { version = "1", features = [
"rt",
"rt-multi-thread",
"time",
"fs",
"macros",
"process",
], optional = true }
tracing = "0.1"
pin-project-lite = "0.2"
dunce = "1"
Expand All @@ -41,6 +51,7 @@ quote = "1"
proc-macro2 = "1"
chrono = "0.4.1"
tracing-subscriber = "0.3"
tokio = { version = "1", features = ["rt-multi-thread", "time", "macros"] }

[features]
default = ["async-std-runtime"]
Expand All @@ -59,6 +70,11 @@ _fetcher-native-tokio = ["fetcher", "chromiumoxide_fetcher/_native-tokio"]
name = "wiki-tokio"
required-features = ["tokio-runtime"]

[[example]]
name = "iframe-workaround"
required-features = ["tokio-runtime", "tokio"]


[[example]]
name = "httpfuture"
required-features = ["tokio-runtime"]
Expand Down
44 changes: 44 additions & 0 deletions examples/iframe-workaround.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// This example is for checking the iframe workaround.
// a problem with the iframe workaround is that it will always fail to load the page
// and goto will cause a timeout.

use std::time::Duration;

use chromiumoxide::handler::HandlerConfig;
use chromiumoxide_cdp::cdp::browser_protocol::target::CreateBrowserContextParams;
use futures::StreamExt;

#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();

let (browser, mut handler) = chromiumoxide::Browser::connect(
"ws://127.0.0.1:9222/devtools/browser/191fdaef-494d-41b5-8b94-4abd04dff33c",
HandlerConfig::default(),
)
.await
.expect("failed to connect to browser");

let _ = tokio::task::spawn(async move {
while let Some(event) = handler.next().await {
tracing::debug!(event = ?event);
}
});

let page = browser
.new_page("about:blank")
.await
.expect("failed to create page");

// tokio::time::sleep(Duration::from_secs(5)).await;

// let _ = page
// .goto("https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe")
// .await
// .expect("failed to navigate");

let _ = page
.goto("https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe")
.await
.expect("failed to navigate");
}
23 changes: 23 additions & 0 deletions src/browser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,29 @@ impl Browser {
Ok((browser, fut))
}

// Connect to an already running chromium instance via websocket with HandlerConfig
pub async fn connect_with_config(
debug_ws_url: impl Into<String>,
config: HandlerConfig,
) -> Result<(Self, Handler)> {
let debug_ws_url = debug_ws_url.into();
let conn = Connection::<CdpEventMessage>::connect(&debug_ws_url).await?;

let (tx, rx) = channel(1);

let fut = Handler::new(conn, rx, config);
let browser_context = fut.default_browser_context().clone();

let browser = Self {
sender: tx,
config: None,
child: None,
debug_ws_url,
browser_context,
};
Ok((browser, fut))
}

/// Launches a new instance of `chromium` in the background and attaches to
/// its debug web socket.
///
Expand Down
12 changes: 12 additions & 0 deletions src/handler/commandfuture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ pin_project! {
rx_command: oneshot::Receiver<M>,
#[pin]
target_sender: mpsc::Sender<TargetMessage>,
// We need delay to be pinned because it's a future
// and we need to be able to poll it
// it is used to timeout the command if page was closed while waiting for response
#[pin]
delay: futures_timer::Delay,

message: Option<TargetMessage>,

Expand All @@ -42,10 +47,15 @@ impl<T: Command> CommandFuture<T> {
cmd, tx, session,
)?));

let delay = futures_timer::Delay::new(std::time::Duration::from_millis(
crate::handler::REQUEST_TIMEOUT,
));

Ok(Self {
target_sender,
rx_command,
message,
delay,
method,
_marker: PhantomData,
})
Expand Down Expand Up @@ -73,6 +83,8 @@ where
}
Poll::Pending => Poll::Pending,
}
} else if this.delay.poll(cx).is_ready() {
Poll::Ready(Err(crate::error::CdpError::Timeout))
} else {
match this.rx_command.as_mut().poll(cx) {
Poll::Ready(Ok(Ok(response))) => {
Expand Down
15 changes: 7 additions & 8 deletions src/handler/frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,15 +254,14 @@ impl FrameManager {
}

fn check_lifecycle(&self, watcher: &NavigationWatcher, frame: &Frame) -> bool {
watcher
.expected_lifecycle
watcher.expected_lifecycle.iter().all(|ev| {
frame.lifecycle_events.contains(ev)
|| (frame.url.is_none() && frame.lifecycle_events.contains("DOMContentLoaded"))
}) && frame
.child_frames
.iter()
.all(|ev| frame.lifecycle_events.contains(ev))
&& frame
.child_frames
.iter()
.filter_map(|f| self.frames.get(f))
.all(|f| self.check_lifecycle(watcher, f))
.filter_map(|f| self.frames.get(f))
.all(|f| self.check_lifecycle(watcher, f))
}

fn check_lifecycle_complete(
Expand Down
30 changes: 29 additions & 1 deletion src/handler/target.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::pin::Pin;
use std::sync::Arc;
use std::time::Instant;

use chromiumoxide_cdp::cdp::browser_protocol::target::DetachFromTargetParams;
use futures::channel::oneshot::Sender;
use futures::stream::Stream;
use futures::task::{Context, Poll};
Expand Down Expand Up @@ -34,7 +35,9 @@ use crate::handler::viewport::Viewport;
use crate::handler::{PageInner, REQUEST_TIMEOUT};
use crate::listeners::{EventListenerRequest, EventListeners};
use crate::{page::Page, ArcHttpRequest};
use chromiumoxide_cdp::cdp::js_protocol::runtime::ExecutionContextId;
use chromiumoxide_cdp::cdp::js_protocol::runtime::{
ExecutionContextId, RunIfWaitingForDebuggerParams,
};
use std::time::Duration;

macro_rules! advance_state {
Expand Down Expand Up @@ -246,6 +249,31 @@ impl Target {
self.frame_manager.on_frame_started_loading(ev);
}

// `Target` events
CdpEvent::TargetAttachedToTarget(ev) => {
if ev.waiting_for_debugger {
let runtime_cmd = RunIfWaitingForDebuggerParams::default();

self.queued_events.push_back(TargetEvent::Request(Request {
method: runtime_cmd.identifier(),
session_id: Some(ev.session_id.clone().into()),
params: serde_json::to_value(runtime_cmd).unwrap(),
}));
}

if "service_worker" == &ev.target_info.r#type {
let detach_command = DetachFromTargetParams::builder()
.session_id(ev.session_id.clone())
.build();

self.queued_events.push_back(TargetEvent::Request(Request {
method: detach_command.identifier(),
session_id: self.session_id.clone().map(Into::into),
params: serde_json::to_value(detach_command).unwrap(),
}));
}
}

// `NetworkManager` events
CdpEvent::FetchRequestPaused(ev) => self.network_manager.on_fetch_request_paused(ev),
CdpEvent::FetchAuthRequired(ev) => self.network_manager.on_fetch_auth_required(ev),
Expand Down

0 comments on commit 379d9c7

Please sign in to comment.