use crate::handler::commandfuture::CommandFuture;
use crate::handler::http::HttpRequest;
use crate::handler::sender::PageSender;
use crate::handler::target_message_future::TargetMessageFuture;
use crate::{ArcHttpRequest, Result};
use chromiumoxide_cdp::cdp::browser_protocol::page::NavigateReturns;
use chromiumoxide_types::Command;
use futures_util::future::{Fuse, FusedFuture};
use futures_util::FutureExt;
use pin_project_lite::pin_project;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
type ArcRequest = ArcHttpRequest;
const ERR_ABORTED: &str = "net::ERR_ABORTED";
pub fn navigation_continues(err: &str) -> bool {
err == ERR_ABORTED || err.starts_with("net::ERR_HTTP_RESPONSE_CODE_FAILURE")
}
pub fn navigate_error_text(response: &NavigateReturns) -> Option<&str> {
response.error_text.as_deref()
}
pin_project! {
pub struct HttpFuture<T: Command> {
#[pin]
command: Fuse<CommandFuture<T>>,
#[pin]
navigation: TargetMessageFuture<ArcHttpRequest>,
failure_check: Option<fn(&T::Response) -> Option<&str>>,
url: Option<String>,
}
}
impl<T: Command> HttpFuture<T> {
pub fn new(
sender: PageSender,
command: CommandFuture<T>,
request_timeout: std::time::Duration,
) -> Self {
Self {
command: command.fuse(),
navigation: TargetMessageFuture::<T>::wait_for_navigation(sender, request_timeout),
failure_check: None,
url: None,
}
}
pub fn with_failure_check(
sender: PageSender,
command: CommandFuture<T>,
request_timeout: std::time::Duration,
failure_check: fn(&T::Response) -> Option<&str>,
url: Option<String>,
) -> Self {
Self {
command: command.fuse(),
navigation: TargetMessageFuture::<T>::wait_for_navigation(sender, request_timeout),
failure_check: Some(failure_check),
url,
}
}
}
impl<T> Future for HttpFuture<T>
where
T: Command,
{
type Output = Result<ArcRequest>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.project();
if this.command.is_terminated() {
this.navigation.poll(cx)
} else {
match this.command.poll(cx) {
Poll::Ready(Ok(command_response)) => {
if let Some(check) = *this.failure_check {
if let Some(err) = check(&command_response.result) {
if !err.is_empty() && !navigation_continues(err) {
let req = HttpRequest {
failure_text: Some(err.to_owned()),
is_navigation_request: true,
url: this.url.take(),
..Default::default()
};
return Poll::Ready(Ok(Some(Arc::new(req))));
}
}
}
this.navigation.as_mut().reset_deadline();
this.navigation.poll(cx)
}
Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
Poll::Pending => Poll::Pending,
}
}
}
}