diff --git a/src/app/browser/window/tab/item/page.rs b/src/app/browser/window/tab/item/page.rs index cf660327..d7f56051 100644 --- a/src/app/browser/window/tab/item/page.rs +++ b/src/app/browser/window/tab/item/page.rs @@ -5,7 +5,6 @@ mod error; mod input; mod mode; mod navigation; -mod redirect; mod search; mod status; mod widget; @@ -16,7 +15,6 @@ use error::Error; use input::Input; use mode::Mode; use navigation::Navigation; -use redirect::Redirect; use search::Search; use status::Status; use widget::Widget; @@ -38,7 +36,6 @@ pub struct Page { profile: Rc, status: Rc>, title: Rc>, - redirect: Rc, // Actions browser_action: Rc, tab_action: Rc, @@ -90,7 +87,6 @@ impl Page { Self { id: id.clone(), profile: profile.clone(), - redirect: Rc::new(Redirect::new()), status: Rc::new(RefCell::new(Status::New)), title: Rc::new(RefCell::new(gformat!("New page"))), // Actions @@ -170,10 +166,6 @@ impl Page { /// Main loader for different protocols, that routed by scheme /// * every protocol has it own (children) method implementation pub fn load(&self, is_history: bool) { - /// Global limit to prevent infinitive redirects (ALL protocols) - /// * every protocol implementation has own value checker, according to specification - const DEFAULT_MAX_REDIRECT_COUNT: usize = 10; - // Move focus out from navigation entry self.browser_action .escape @@ -186,27 +178,22 @@ impl Page { self.search.unset(); self.input.unset(); - // Prevent infinitive redirection - if self.redirect.count() > DEFAULT_MAX_REDIRECT_COUNT { - todo!() - } - // Try redirect request - let request = if let Some(redirect) = self.redirect.last() { + let request = if let Some(redirect) = self.client.redirect.last() { // Gemini protocol may provide background (temporarily) redirects if redirect.is_foreground { self.navigation .request .widget .entry - .set_text(&redirect.request); + .set_text(&redirect.request.to_string()); } // Return value from redirection holder - Mode::from(&redirect.request, redirect.referrer.as_ref()) + Mode::from(&redirect.request.to_str(), None /*redirect.referrer*/) // @TODO } else { // Reset redirect counter as request value taken from user input - self.redirect.clear(); + self.client.redirect.clear(); // Return value from navigation entry Mode::from(&self.navigation.request.widget.entry.text(), None) @@ -424,7 +411,7 @@ impl Page { let search = self.search.clone(); let tab_action = self.tab_action.clone(); let window_action = self.window_action.clone(); - let redirect = self.redirect.clone(); + let redirect = self.client.redirect.clone(); // Listen for connection status updates self.client.gemini.socket.connect_event({ @@ -775,11 +762,11 @@ impl Page { redirect.add( // skip query and fragment by protocol requirements // @TODO review fragment specification - resolved_uri.to_string_partial( + Uri::parse(&resolved_uri.to_string_partial( UriHideFlags::FRAGMENT | UriHideFlags::QUERY - ), + ), UriFlags::NONE).unwrap(), // @TODO handle // referrer - Some(navigation.request.widget.entry.text()), + Some(uri.clone()), // set follow policy based on status code matches!(response.meta.status, response::meta::Status::PermanentRedirect), ); diff --git a/src/app/browser/window/tab/item/page/client.rs b/src/app/browser/window/tab/item/page/client.rs index 1ed255a3..d7910eb7 100644 --- a/src/app/browser/window/tab/item/page/client.rs +++ b/src/app/browser/window/tab/item/page/client.rs @@ -1,5 +1,14 @@ +mod redirect; +mod status; + +use redirect::Redirect; +use status::Status; + use gtk::{gio::Cancellable, prelude::CancellableExt}; -use std::cell::Cell; +use std::{ + cell::{Cell, RefCell}, + rc::Rc, +}; /// Multi-client holder for single `Page` object /// @@ -10,7 +19,11 @@ use std::cell::Cell; pub struct Client { // Shared reference to cancel async operations cancellable: Cell, - // Clients + // Redirects resolver for different protocols + pub redirect: Rc, + // Track update status + status: Rc>, + // Drivers pub gemini: gemini::Client, // other clients.. } @@ -28,6 +41,8 @@ impl Client { pub fn new() -> Self { Self { cancellable: Cell::new(Cancellable::new()), + redirect: Rc::new(Redirect::new()), + status: Rc::new(RefCell::new(Status::Cancellable)), gemini: gemini::Client::new(), } } @@ -41,9 +56,23 @@ impl Client { let previous = self.cancellable.replace(cancellable.clone()); if !previous.is_cancelled() { previous.cancel(); + self.status.replace(Status::Cancelled); + } else { + self.status.replace(Status::Cancellable); } // Done cancellable } + + pub fn request(&self, query: &str) { + self.status.replace(Status::Request(query.to_string())); + + // Forcefully prevent infinitive redirection + // * this condition just to make sure that client will never stuck by driver implementation issue + if self.redirect.count() > redirect::LIMIT { + self.status.replace(Status::RedirectLimit(redirect::LIMIT)); + // @TODO return; + } + } } diff --git a/src/app/browser/window/tab/item/page/client/redirect.rs b/src/app/browser/window/tab/item/page/client/redirect.rs new file mode 100644 index 00000000..1c7cbb2c --- /dev/null +++ b/src/app/browser/window/tab/item/page/client/redirect.rs @@ -0,0 +1,67 @@ +mod item; +use item::Item; + +use gtk::glib::Uri; +use std::cell::{Cell, RefCell}; + +/// Global limit to prevent infinitive redirection issues +/// * defined value is globally applicable to ALL drivers +/// * every driver implement its own value, according to protocol specification +/// * the `Client` will forcefully break redirection loop when iteration reach this value +pub const LIMIT: usize = 10; // @TODO make optional + +pub struct Redirect { + chain: RefCell>, +} + +impl Default for Redirect { + fn default() -> Self { + Self::new() + } +} + +impl Redirect { + // Constructors + + /// Create new `Self` + pub fn new() -> Self { + Self { + chain: RefCell::new(Vec::new()), + } + } + + // Actions + + /// Register new redirect in chain + pub fn add(&self, request: Uri, referrer: Option, is_foreground: bool) -> &Self { + self.chain.borrow_mut().push(Item { + request, + referrer, + is_foreground, + is_processed: Cell::new(false), + }); + self + } + + /// Clear redirect chain + pub fn clear(&self) { + self.chain.borrow_mut().clear() + } + + // Getters + + /// Get total redirects count in chain + pub fn count(&self) -> usize { + self.chain.borrow().len() + 1 + } + + /// Get last redirection `Item` copy + pub fn last(&self) -> Option { + if let Some(redirect) = self.chain.borrow().last() { + if !redirect.is_processed.replace(true) { + return Some(redirect.clone()); + } + } + None + } +} diff --git a/src/app/browser/window/tab/item/page/client/redirect/item.rs b/src/app/browser/window/tab/item/page/client/redirect/item.rs new file mode 100644 index 00000000..af34cfc6 --- /dev/null +++ b/src/app/browser/window/tab/item/page/client/redirect/item.rs @@ -0,0 +1,11 @@ +use gtk::glib::Uri; +use std::cell::Cell; + +/// Single redirect `Item` +#[derive(Clone)] +pub struct Item { + pub is_foreground: bool, + pub is_processed: Cell, + pub referrer: Option, + pub request: Uri, +} diff --git a/src/app/browser/window/tab/item/page/client/status.rs b/src/app/browser/window/tab/item/page/client/status.rs new file mode 100644 index 00000000..1e0777a0 --- /dev/null +++ b/src/app/browser/window/tab/item/page/client/status.rs @@ -0,0 +1,36 @@ +use std::fmt::{Display, Formatter, Result}; + +/// Local `Client` status +/// * not same as the Gemini status! +pub enum Status { + /// Ready to use (or cancel from outside) + Cancellable, + /// Operation cancelled, new `Cancellable` required to continue + Cancelled, + /// Redirection count limit reached by protocol driver or global settings + RedirectLimit(usize), + /// New `request` begin + Request(String), +} + +impl Display for Status { + fn fmt(&self, f: &mut Formatter) -> Result { + match self { + Self::Cancellable => { + write!(f, "Ready to use (or cancel from outside)") + } + Self::Cancelled => { + write!( + f, + "Operation cancelled, new `Cancellable` required to continue" + ) + } + Self::RedirectLimit(count) => { + write!(f, "Redirection count limit ({count}) reached by protocol driver or global settings") + } + Self::Request(value) => { + write!(f, "Request `{value}`...") + } + } + } +} diff --git a/src/app/browser/window/tab/item/page/redirect.rs b/src/app/browser/window/tab/item/page/redirect.rs deleted file mode 100644 index ba469a70..00000000 --- a/src/app/browser/window/tab/item/page/redirect.rs +++ /dev/null @@ -1,57 +0,0 @@ -mod item; -use item::Item; - -use gtk::glib::GString; -use std::cell::{Cell, RefCell}; - -pub struct Redirect { - index: RefCell>, -} - -impl Default for Redirect { - fn default() -> Self { - Self::new() - } -} - -impl Redirect { - // Constructors - - /// Create new `Self` - pub fn new() -> Self { - Self { - index: RefCell::new(Vec::new()), - } - } - - // Actions - - pub fn add(&self, request: GString, referrer: Option, is_foreground: bool) -> &Self { - self.index.borrow_mut().push(Item { - request, - referrer, - is_foreground, - is_processed: Cell::new(false), - }); - self - } - - pub fn clear(&self) { - self.index.borrow_mut().clear() - } - - // Getters - - pub fn count(&self) -> usize { - self.index.borrow().len() + 1 - } - - pub fn last(&self) -> Option { - if let Some(redirect) = self.index.borrow().last() { - if !redirect.is_processed.replace(true) { - return Some(redirect.clone()); - } - } - None - } -}