diff --git a/README.md b/README.md index 891978bf..1ed3fd49 100644 --- a/README.md +++ b/README.md @@ -18,13 +18,15 @@ async fn main() -> Result<(), Box> { // create a `Browser` that spawns a `chromium` process running with UI (`with_head()`, headless is default) // and the handler that drives the websocket etc. - let (browser, mut handler) = + let (mut browser, mut handler) = Browser::launch(BrowserConfig::builder().with_head().build()?).await?; // spawn a new task that continuously polls the handler let handle = async_std::task::spawn(async move { - loop { - let _ = handler.next().await.unwrap(); + while let Some(h) = handler.next().await { + if h.is_err() { + break; + } } }); @@ -44,6 +46,7 @@ async fn main() -> Result<(), Box> { let html = page.wait_for_navigation().await?.content().await?; + browser.close().await?; handle.await; Ok(()) } @@ -89,12 +92,47 @@ Every chrome pdl domain is put in its own rust module, the types for the page do [vanilla.aslushnikov.com](https://vanilla.aslushnikov.com/) is a great resource to browse all the types defined in the pdl files. This site displays `Command` types as defined in the pdl files as `Method`. `chromiumoxid` sticks to the `Command` nomenclature. So for everything that is defined as a command type in the pdl (=marked as `Method` on [vanilla.aslushnikov.com](https://vanilla.aslushnikov.com/)) `chromiumoxide` contains a type for command and a designated type for the return type. For every command there is a `Params` type with builder support (`Params::builder()`) and its corresponding return type: `Returns`. All commands share an implementation of the `chromiumoxide_types::Command` trait. All Events are bundled in single enum (`CdpEvent`) - + +## Fetcher + +By default `chromiumoxide` will try to find an installed version of chromium on the computer it runs on. +It is possible to download and install one automatically for some platforms using the `fetcher`. + +Ther features are currently a bit messy due to a Cargo bug and will be changed once it is resolved. +Based on your runtime and TLS configuration you should enable one of the following: +- `_fetcher-rustls-async-std` +- `_fetcher-rusttls-tokio` +- `_fetcher-native-async-std` +- `_fetcher-native-tokio` + +```rust +use std::path::Path; + +use futures::StreamExt; + +use chromiumoxide::browser::{BrowserConfig}; +use chromiumoxide::fetcher::{BrowserFetcher, BrowserFetcherOptions}; + +#[async_std::main] +async fn main() -> Result<(), Box> { + let download_path = Path::new("./download"); + async_std::fs::create_dir_all(&download_path).await?; + let fetcher = BrowserFetcher::new( + BrowserFetcherOptions::builder() + .with_path(&download_path) + .build()?, + ); + let info = fetcher.fetch().await?; + + let config = BrowserConfig::builder() + .chrome_executable(info.executable_path) + .build()?, +} +``` ## Known Issues * The rust files generated for the PDL files in [chromiumoxide_cdp](./chromiumoxide_cdp) don't compile when support for experimental types is manually turned off (`export CDP_NO_EXPERIMENTAL=true`). This is because the use of some experimental pdl types in the `*.pdl` files themselves are not marked as experimental. -* `chromiumoxide` requires an installed chromium application and may not be able to find it on its own. The option to download chromium certainly would be a handy feature. ## Troubleshooting diff --git a/examples/fetcher.rs b/examples/fetcher.rs index 6f3b3269..4b797ddf 100644 --- a/examples/fetcher.rs +++ b/examples/fetcher.rs @@ -30,11 +30,18 @@ async fn main() -> Result<(), Box> { match handler.next().await { Some(h) => match h { Ok(_) => continue, - Err(_) => break, + Err(e) => { + println!("Err: {}", e); + break; + } }, - None => break, + None => { + println!("None"); + break; + } } } + println!("Done"); }); let page = browser.new_page("about:blank").await?; @@ -44,6 +51,7 @@ async fn main() -> Result<(), Box> { println!("it worked!"); browser.close().await?; + browser.wait().await?; handle.await; Ok(()) } diff --git a/src/handler/mod.rs b/src/handler/mod.rs index e9c820cb..215cd4a0 100644 --- a/src/handler/mod.rs +++ b/src/handler/mod.rs @@ -81,6 +81,8 @@ pub struct Handler { config: HandlerConfig, /// All registered event subscriptions event_listeners: EventListeners, + /// Keeps track is the browser is closing + closing: bool, } impl Handler { @@ -118,6 +120,7 @@ impl Handler { next_navigation_id: 0, config, event_listeners: Default::default(), + closing: false, } } @@ -221,6 +224,10 @@ impl Handler { target.on_response(resp, method.as_ref()); } } + PendingRequest::CloseBrowser(tx) => { + self.closing = true; + let _ = tx.send(Ok(CloseReturns {})).ok(); + } } } } @@ -275,6 +282,23 @@ impl Handler { .insert(call_id, (PendingRequest::Navigate(id), req.method, now)); } + fn submit_close(&mut self, tx: OneshotSender>, now: Instant) { + let close_msg = CloseParams::default(); + let method = close_msg.identifier(); + + let call_id = self + .conn + .submit_command( + method.clone(), + None, + serde_json::to_value(close_msg).unwrap(), + ) + .unwrap(); + + self.pending_commands + .insert(call_id, (PendingRequest::CloseBrowser(tx), method, now)); + } + /// Process a message received by the target's page via channel fn on_target_message(&mut self, target: &mut Target, msg: CommandMessage, now: Instant) { // if let some @@ -445,6 +469,9 @@ impl Handler { let _ = tx.send(Err(CdpError::Timeout)); } PendingRequest::InternalCommand(_) => {} + PendingRequest::CloseBrowser(tx) => { + let _ = tx.send(Err(CdpError::Timeout)); + } } } } @@ -472,16 +499,7 @@ impl Stream for Handler { pin.submit_external_command(cmd, now)?; } HandlerMessage::CloseBrowser(tx) => { - let close_msg = CloseParams::default(); - - pin.conn.submit_command( - close_msg.identifier(), - None, - serde_json::to_value(close_msg).unwrap(), - )?; - tx.send(Ok(CloseReturns {})).ok(); - - return Poll::Ready(None); + pin.submit_close(tx, now); } HandlerMessage::CreatePage(params, tx) => { pin.create_page(params, tx); @@ -554,7 +572,13 @@ impl Stream for Handler { while let Poll::Ready(Some(ev)) = Pin::new(&mut pin.conn).poll_next(cx) { match ev { - Ok(Message::Response(resp)) => pin.on_response(resp), + Ok(Message::Response(resp)) => { + pin.on_response(resp); + if pin.closing { + // handler should stop processing + return Poll::Ready(None); + } + } Ok(Message::Event(ev)) => { pin.on_event(ev); } @@ -667,6 +691,8 @@ enum PendingRequest { /// Requests that are initiated directly from a `Target` (all the /// initialization commands). InternalCommand(TargetId), + // A Request to close the browser. + CloseBrowser(OneshotSender>), } /// Events used internally to communicate with the handler, which are executed