Skip to content

Commit

Permalink
fix: Properly handle the close (mattsse#140)
Browse files Browse the repository at this point in the history
  • Loading branch information
Sytten authored Feb 13, 2023
1 parent 84b9f4e commit 8fc6fed
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 18 deletions.
48 changes: 43 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {

// 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;
}
}
});

Expand All @@ -44,6 +46,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {

let html = page.wait_for_navigation().await?.content().await?;

browser.close().await?;
handle.await;
Ok(())
}
Expand Down Expand Up @@ -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 `<name of command>Params` type with builder support (`<name of command>Params::builder()`) and its corresponding return type: `<name of command>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<dyn std::error::Error>> {
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

Expand Down
12 changes: 10 additions & 2 deletions examples/fetcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,18 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
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?;
Expand All @@ -44,6 +51,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("it worked!");

browser.close().await?;
browser.wait().await?;
handle.await;
Ok(())
}
48 changes: 37 additions & 11 deletions src/handler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -118,6 +120,7 @@ impl Handler {
next_navigation_id: 0,
config,
event_listeners: Default::default(),
closing: false,
}
}

Expand Down Expand Up @@ -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();
}
}
}
}
Expand Down Expand Up @@ -275,6 +282,23 @@ impl Handler {
.insert(call_id, (PendingRequest::Navigate(id), req.method, now));
}

fn submit_close(&mut self, tx: OneshotSender<Result<CloseReturns>>, 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
Expand Down Expand Up @@ -445,6 +469,9 @@ impl Handler {
let _ = tx.send(Err(CdpError::Timeout));
}
PendingRequest::InternalCommand(_) => {}
PendingRequest::CloseBrowser(tx) => {
let _ = tx.send(Err(CdpError::Timeout));
}
}
}
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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<Result<CloseReturns>>),
}

/// Events used internally to communicate with the handler, which are executed
Expand Down

0 comments on commit 8fc6fed

Please sign in to comment.