Skip to content

Commit

Permalink
Add the ability to follow links (#173)
Browse files Browse the repository at this point in the history
* #170 Follow Links - Added the ability to follow links.
 For this we added another Variant to the BlitzEvent Enum and also made
 DocumentLike return an Option<DocumentEvent> which currently just has
 one Variant for following links.

 I consider this a working POC, which needs to be fleshed out.
 I didn't invest any time to make sure the DioxusSide still works. It
 most likely won't because of the change in the trait definition.

* #170 Follow Links
 Instead of bloating the document with another optional Event on
 `DocumentLike`'s' `handle_event` method, we introduce
 a `NavigationProvider` similar to the existing `NetProvider`.

 Currently it only has one method for navigating to a new page, but this
 may change in the future.

* #170 Incorporate feedback and make the whole workspace compile again

* #170 make the freestanding `resolve_url` function return an Option<Url>
 instead of panicking when parsing is not possible

* #170 make examples/screenshot run again!
  • Loading branch information
mogambro authored Dec 30, 2024
1 parent 849d08a commit 72978dd
Show file tree
Hide file tree
Showing 14 changed files with 125 additions and 9 deletions.
28 changes: 26 additions & 2 deletions apps/readme/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod readme_application;

use blitz_html::HtmlDocument;
use blitz_net::Provider;
use blitz_traits::navigation::NavigationProvider;
use markdown::{markdown_to_html, BLITZ_MD_STYLES, GITHUB_MD_STYLES};
use notify::{Error as NotifyError, Event as NotifyEvent, RecursiveMode, Watcher as _};
use readme_application::{ReadmeApplication, ReadmeEvent};
Expand All @@ -14,9 +15,21 @@ use std::fs;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use url::Url;
use winit::event_loop::EventLoopProxy;
use winit::window::WindowAttributes;

const USER_AGENT: &str = "Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/81.0";

struct ReadmeNavigationProvider {
proxy: EventLoopProxy<BlitzEvent>,
}

impl NavigationProvider for ReadmeNavigationProvider {
fn navigate_new_page(&self, url: String) {
let _ = self.proxy.send_event(BlitzEvent::Navigate(url));
}
}

fn main() {
let raw_url = std::env::args().nth(1).unwrap_or_else(|| {
let cwd = current_dir().unwrap();
Expand Down Expand Up @@ -55,19 +68,30 @@ fn main() {
let net_callback = BlitzShellNetCallback::shared(proxy.clone());
let net_provider = Provider::shared(net_callback);

let proxy = event_loop.create_proxy();
let navigation_provider = ReadmeNavigationProvider {
proxy: proxy.clone(),
};
let navigation_provider = Arc::new(navigation_provider);

let doc = HtmlDocument::from_html(
&html,
Some(base_url),
stylesheets,
net_provider.clone(),
None,
navigation_provider.clone(),
);
let attrs = WindowAttributes::default().with_title(title);
let window = WindowConfig::with_attributes(doc, attrs);

// Create application
let mut application =
ReadmeApplication::new(event_loop.create_proxy(), raw_url.clone(), net_provider);
let mut application = ReadmeApplication::new(
proxy.clone(),
raw_url.clone(),
net_provider,
navigation_provider,
);
application.add_window(window);

if let Some(path) = file_path {
Expand Down
9 changes: 9 additions & 0 deletions apps/readme/src/readme_application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use blitz_dom::net::Resource;
use blitz_html::HtmlDocument;
use blitz_renderer_vello::BlitzVelloRenderer;
use blitz_shell::{BlitzApplication, BlitzEvent, View, WindowConfig};
use blitz_traits::navigation::NavigationProvider;
use blitz_traits::net::NetProvider;
use tokio::runtime::Handle;
use winit::application::ApplicationHandler;
Expand All @@ -23,13 +24,15 @@ pub struct ReadmeApplication {
net_provider: Arc<dyn NetProvider<Data = Resource>>,
raw_url: String,
keyboard_modifiers: Modifiers,
navigation_provider: Arc<dyn NavigationProvider>,
}

impl ReadmeApplication {
pub fn new(
proxy: EventLoopProxy<BlitzEvent>,
raw_url: String,
net_provider: Arc<dyn NetProvider<Data = Resource>>,
navigation_provider: Arc<dyn NavigationProvider>,
) -> Self {
let handle = Handle::current();
Self {
Expand All @@ -38,6 +41,7 @@ impl ReadmeApplication {
raw_url,
net_provider,
keyboard_modifiers: Default::default(),
navigation_provider,
}
}

Expand Down Expand Up @@ -66,6 +70,7 @@ impl ReadmeApplication {
stylesheets,
self.net_provider.clone(),
None,
self.navigation_provider.clone(),
);
self.window_mut().replace_document(doc);
}
Expand Down Expand Up @@ -124,6 +129,10 @@ impl ApplicationHandler<BlitzEvent> for ReadmeApplication {
self.reload_document();
}
}
BlitzEvent::Navigate(url) => {
self.raw_url = url;
self.reload_document();
}
event => self.inner.user_event(event_loop, event),
}
}
Expand Down
1 change: 1 addition & 0 deletions apps/wpt/src/attr_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ pub async fn parse_and_resolve_document(
Vec::new(),
Arc::clone(&ctx.net_provider) as SharedProvider<Resource>,
Some(clone_font_ctx(&ctx.font_ctx)),
ctx.navigation_provider.clone(),
);

document.as_mut().set_viewport(ctx.viewport.clone());
Expand Down
4 changes: 4 additions & 0 deletions apps/wpt/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use blitz_dom::net::Resource;
use blitz_renderer_vello::VelloImageRenderer;
use blitz_traits::navigation::{DummyNavigationProvider, NavigationProvider};
use blitz_traits::{ColorScheme, Viewport};
use parley::FontContext;
use pollster::FutureExt as _;
Expand Down Expand Up @@ -170,6 +171,7 @@ impl Buffers {
struct ThreadCtx {
viewport: Viewport,
net_provider: Arc<WptNetProvider<Resource>>,
navigation_provider: Arc<dyn NavigationProvider>,
renderer: VelloImageRenderer,
font_ctx: FontContext,
buffers: Buffers,
Expand Down Expand Up @@ -327,6 +329,7 @@ fn main() {
.unwrap();

let dummy_base_url = Url::parse("http://dummy.local").unwrap();
let navigation_provider = Arc::new(DummyNavigationProvider);

RefCell::new(ThreadCtx {
viewport,
Expand All @@ -349,6 +352,7 @@ fn main() {
out_dir: out_dir.clone(),
wpt_dir: wpt_dir.clone(),
dummy_base_url,
navigation_provider,
})
})
.borrow_mut();
Expand Down
1 change: 1 addition & 0 deletions apps/wpt/src/ref_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ async fn render_html_to_buffer(
Vec::new(),
Arc::clone(&ctx.net_provider) as SharedProvider<Resource>,
Some(clone_font_ctx(&ctx.font_ctx)),
ctx.navigation_provider.clone(),
);

document.as_mut().set_viewport(ctx.viewport.clone());
Expand Down
4 changes: 4 additions & 0 deletions examples/screenshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use blitz_dom::net::Resource;
use blitz_html::HtmlDocument;
use blitz_net::{MpscCallback, Provider};
use blitz_renderer_vello::render_to_buffer;
use blitz_traits::navigation::DummyNavigationProvider;
use blitz_traits::net::SharedProvider;
use blitz_traits::{ColorScheme, Viewport};
use reqwest::Url;
Expand Down Expand Up @@ -64,6 +65,8 @@ async fn main() {
let callback = Arc::new(callback);
let net = Arc::new(Provider::new(callback));

let navigation_provider = Arc::new(DummyNavigationProvider);

timer.time("Setup document prerequisites");

// Create HtmlDocument
Expand All @@ -73,6 +76,7 @@ async fn main() {
Vec::new(),
Arc::clone(&net) as SharedProvider<Resource>,
None,
navigation_provider,
);

timer.time("Parsed document");
Expand Down
42 changes: 38 additions & 4 deletions packages/blitz-dom/src/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::stylo_to_cursor_icon::stylo_to_cursor_icon;
use crate::util::ImageType;
use crate::{ElementNodeData, Node, NodeData, TextNodeData};
use app_units::Au;
use blitz_traits::navigation::{DummyNavigationProvider, NavigationProvider};
use blitz_traits::net::{DummyNetProvider, SharedProvider};
use blitz_traits::{ColorScheme, Viewport};
use cursor_icon::CursorIcon;
Expand Down Expand Up @@ -145,6 +146,10 @@ pub struct Document {

/// Network provider. Can be used to fetch assets.
pub net_provider: SharedProvider<Resource>,

/// Navigation provider. Can be used to navigate to a new page (bubbles up the event
/// on e.g. clicking a Link)
pub navigation_provider: Arc<dyn NavigationProvider>,
}

fn make_device(viewport: &Viewport) -> Device {
Expand Down Expand Up @@ -222,6 +227,19 @@ impl DocumentLike for Document {
}
self.set_focus_to(node_id);
}
} else if el.name.local == local_name!("a") {
if let Some(href) = el.attr(local_name!("href")) {
if let Some(url) = resolve_url(&self.base_url, href) {
self.navigation_provider.navigate_new_page(url.into());
} else {
println!(
"{href} is not parseable as a url. : {base_url:?}",
base_url = self.base_url
)
}
} else {
println!("Clicked link without href: {:?}", el.attrs());
}
}
}
}
Expand Down Expand Up @@ -331,6 +349,7 @@ impl Document {
focus_node_id: None,
changed: HashSet::new(),
net_provider: Arc::new(DummyNetProvider::default()),
navigation_provider: Arc::new(DummyNavigationProvider {}),
};

// Initialise document with root Document node
Expand All @@ -357,6 +376,11 @@ impl Document {
self.net_provider = net_provider;
}

/// Set the Document's navigation provider
pub fn set_navigation_provider(&mut self, navigation_provider: Arc<dyn NavigationProvider>) {
self.navigation_provider = navigation_provider;
}

/// Set base url for resolving linked resources (stylesheets, images, fonts, etc)
pub fn set_base_url(&mut self, url: &str) {
self.base_url = Some(Url::parse(url).unwrap());
Expand Down Expand Up @@ -586,10 +610,12 @@ impl Document {
}

pub fn resolve_url(&self, raw: &str) -> url::Url {
match &self.base_url {
Some(base_url) => base_url.join(raw).unwrap(),
None => url::Url::parse(raw).unwrap(),
}
resolve_url(&self.base_url, raw).unwrap_or_else(|| {
panic!(
"to be able to resolve {raw} with the base_url: {base_url:?}",
base_url = self.base_url
)
})
}

pub fn print_tree(&self) {
Expand Down Expand Up @@ -1224,3 +1250,11 @@ impl AsMut<Document> for Document {
self
}
}

fn resolve_url(base_url: &Option<url::Url>, raw: &str) -> Option<url::Url> {
match base_url {
Some(base_url) => base_url.join(raw),
None => url::Url::parse(raw),
}
.ok()
}
10 changes: 8 additions & 2 deletions packages/blitz-html/src/html_document.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use std::sync::Arc;

use crate::DocumentHtmlParser;

use blitz_dom::{
events::RendererEvent, net::Resource, Document, DocumentLike, FontContext, DEFAULT_CSS,
};
use blitz_traits::{net::SharedProvider, ColorScheme, Viewport};
use blitz_traits::{navigation::NavigationProvider, net::SharedProvider, ColorScheme, Viewport};

pub struct HtmlDocument {
inner: Document,
Expand All @@ -28,7 +30,7 @@ impl From<HtmlDocument> for Document {
}
impl DocumentLike for HtmlDocument {
fn handle_event(&mut self, event: RendererEvent) {
self.inner.as_mut().handle_event(event);
self.inner.as_mut().handle_event(event)
}
}

Expand All @@ -39,6 +41,7 @@ impl HtmlDocument {
stylesheets: Vec<String>,
net_provider: SharedProvider<Resource>,
font_ctx: Option<FontContext>,
navigation_provider: Arc<dyn NavigationProvider>,
) -> Self {
// Spin up the virtualdom and include the default stylesheet
let viewport = Viewport::new(0, 0, 1.0, ColorScheme::Light);
Expand All @@ -55,6 +58,9 @@ impl HtmlDocument {
// Set the net provider
doc.set_net_provider(net_provider.clone());

// Set the navigation provider
doc.set_navigation_provider(navigation_provider.clone());

// Include default and user-specified stylesheets
doc.add_user_agent_stylesheet(DEFAULT_CSS);
for ss in &stylesheets {
Expand Down
3 changes: 3 additions & 0 deletions packages/blitz-shell/src/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ impl<Doc: DocumentLike, Rend: DocumentRenderer> ApplicationHandler<BlitzEvent>
BlitzEvent::Embedder(_) => {
// Do nothing. Should be handled by embedders (if required).
}
BlitzEvent::Navigate(_url) => {
// Do nothing. Should be handled by embedders (if required).
}
}
}
}
3 changes: 3 additions & 0 deletions packages/blitz-shell/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ pub enum BlitzEvent {

/// An arbitary event from the Blitz embedder
Embedder(Arc<dyn Any + Send + Sync>),

/// Navigate to another URL (triggered by e.g. clicking a link)
Navigate(String),
}
impl BlitzEvent {
pub fn embedder_event<T: Any + Send + Sync>(value: T) -> Self {
Expand Down
2 changes: 2 additions & 0 deletions packages/blitz-traits/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
pub mod net;

pub mod navigation;

mod devtools;
pub use devtools::Devtools;

Expand Down
12 changes: 12 additions & 0 deletions packages/blitz-traits/src/navigation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/// A provider to enable a document to bubble up navigation events (e.g. clicking a link)
pub trait NavigationProvider: Send + Sync + 'static {
fn navigate_new_page(&self, url: String);
}

pub struct DummyNavigationProvider;

impl NavigationProvider for DummyNavigationProvider {
fn navigate_new_page(&self, _url: String) {
// Default impl: do nothing
}
}
1 change: 1 addition & 0 deletions packages/blitz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ blitz-renderer-vello = { path = "../blitz-renderer-vello" }
blitz-html = { path = "../blitz-html" }
blitz-shell = { path = "../blitz-shell" }
blitz-net = { path = "../blitz-net", optional = true }
blitz-traits = { path = "../blitz-traits" }

# IO & Networking
url = { workspace = true, features = ["serde"], optional = true }
Expand Down
14 changes: 13 additions & 1 deletion packages/blitz/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@
//! - `menu`: Enables the [`muda`] menubar.
//! - `tracing`: Enables tracing support.
use std::sync::Arc;

use blitz_html::HtmlDocument;
use blitz_renderer_vello::BlitzVelloRenderer;
use blitz_shell::{
create_default_event_loop, BlitzApplication, BlitzEvent, BlitzShellNetCallback, Config,
WindowConfig,
};
use blitz_traits::navigation::DummyNavigationProvider;

#[cfg(feature = "net")]
pub fn launch_url(url: &str) {
Expand Down Expand Up @@ -74,7 +77,16 @@ fn launch_internal(html: &str, cfg: Config) {
Arc::new(DummyNetProvider::default())
};

let doc = HtmlDocument::from_html(html, cfg.base_url, cfg.stylesheets, net_provider, None);
let navigation_provider = Arc::new(DummyNavigationProvider);

let doc = HtmlDocument::from_html(
html,
cfg.base_url,
cfg.stylesheets,
net_provider,
None,
navigation_provider,
);
let window: WindowConfig<HtmlDocument, BlitzVelloRenderer> = WindowConfig::new(doc);

// Create application
Expand Down

0 comments on commit 72978dd

Please sign in to comment.