use crate::host::{Host, Surface};
use gpui::{Bounds, Context, FocusHandle, IntoElement, Pixels, Render, Window};
use raw_window_handle::{HasWindowHandle, RawWindowHandle};
use std::{
cell::{Cell, OnceCell, RefCell},
rc::Rc,
};
use wasm_bindgen::{JsCast, JsValue};
use web_sys::{HtmlCanvasElement, HtmlIFrameElement};
pub struct Frame {
frame: Rc<Iframe>,
}
impl Frame {
pub fn new(url: impl Into<String>) -> Self {
Self {
frame: Rc::new(Iframe {
url: RefCell::new(url.into()),
built: OnceCell::new(),
placed: Cell::new(None),
owner: Cell::new(0),
}),
}
}
pub fn load(&mut self, url: impl Into<String>) {
let url = url.into();
match self.frame.built() {
Some((_, iframe)) => iframe.set_src(&url),
None => *self.frame.url.borrow_mut() = url,
}
}
}
impl Render for Frame {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
Host {
surface: self.frame.clone(),
}
}
}
struct Iframe {
url: RefCell<String>,
built: OnceCell<Option<(HtmlCanvasElement, HtmlIFrameElement)>>,
placed: Cell<Option<Bounds<f64>>>,
owner: Cell<u64>,
}
impl Iframe {
fn built(&self) -> Option<&(HtmlCanvasElement, HtmlIFrameElement)> {
self.built.get()?.as_ref()
}
fn build(&self, window: &Window) -> Option<(HtmlCanvasElement, HtmlIFrameElement)> {
let handle = HasWindowHandle::window_handle(window).ok()?;
let RawWindowHandle::WebCanvas(handle) = handle.as_raw() else {
return None;
};
let canvas = unsafe { handle.obj.cast::<JsValue>().as_ref() };
let canvas = canvas.clone().dyn_into::<HtmlCanvasElement>().ok()?;
let parent = canvas.parent_node()?;
let iframe = canvas
.owner_document()?
.create_element("iframe")
.ok()?
.dyn_into::<HtmlIFrameElement>()
.ok()?;
iframe.set_src(&self.url.borrow());
let style = iframe.style();
for (property, value) in [
("position", "fixed"),
("border", "0"),
("margin", "0"),
("padding", "0"),
("display", "none"),
] {
let _ = style.set_property(property, value);
}
parent
.insert_before(&iframe, canvas.next_sibling().as_ref())
.ok()?;
Some((canvas, iframe))
}
}
impl Surface for Iframe {
fn focus(&self) -> Option<&FocusHandle> {
None
}
fn owner(&self) -> &Cell<u64> {
&self.owner
}
fn place(&self, bounds: Bounds<Pixels>, window: &Window) {
let Some((canvas, iframe)) = self.built.get_or_init(|| self.build(window)) else {
return;
};
let corner = canvas.get_bounding_client_rect();
let bounds = Bounds {
origin: gpui::point(
corner.left() + f64::from(f32::from(bounds.origin.x)),
corner.top() + f64::from(f32::from(bounds.origin.y)),
),
size: gpui::size(
f64::from(f32::from(bounds.size.width)),
f64::from(f32::from(bounds.size.height)),
),
};
if self.placed.get() == Some(bounds) {
return;
}
let style = iframe.style();
for (property, value) in [
("left", bounds.origin.x),
("top", bounds.origin.y),
("width", bounds.size.width),
("height", bounds.size.height),
] {
let _ = style.set_property(property, &format!("{value}px"));
}
let _ = style.set_property("display", "block");
self.placed.set(Some(bounds));
}
fn park(&self) {
if let Some((_, iframe)) = self.built()
&& self.placed.take().is_some()
{
let _ = iframe.style().set_property("display", "none");
}
}
}
impl Drop for Iframe {
fn drop(&mut self) {
if let Some((_, iframe)) = self.built() {
iframe.remove();
}
}
}