use boa_cat::Value;
use boa_cat::env::{Binding, Env};
use boa_cat::evaluate_program_with;
use boa_cat::fuel::Fuel;
use boa_cat::heap::Heap;
use ecma_lex_cat::lex;
use ecma_parse_cat::parse_script;
use layout_cat::Viewport;
use web_api_cat::extract_document;
use crate::error::Error;
use crate::frame::Frame;
use crate::ipc::HostCommands;
pub const DEFAULT_FUEL: u64 = 1_000_000;
pub fn run_script(
html_source: &str,
css_source: &str,
js_source: &str,
viewport: Viewport,
) -> Result<Frame, Error> {
run_script_with_commands(
html_source,
css_source,
js_source,
viewport,
&HostCommands::new(),
)
}
pub fn run_script_with_commands(
html_source: &str,
css_source: &str,
js_source: &str,
viewport: Viewport,
commands: &HostCommands,
) -> Result<Frame, Error> {
let prepared = prepare(html_source, css_source, viewport, commands)?;
let (value, heap) = evaluate(js_source, prepared.env, prepared.heap)?;
Ok(Frame::new(
prepared.dom,
prepared.layout_tree,
prepared.display_list,
value,
heap,
))
}
pub fn run_script_with_backprop(
html_source: &str,
css_source: &str,
js_source: &str,
viewport: Viewport,
commands: &HostCommands,
) -> Result<Frame, Error> {
let prepared = prepare(html_source, css_source, viewport, commands)?;
let document_value = prepared.document_value.clone();
let stylesheet = prepared.stylesheet.clone();
let (value, heap) = evaluate(js_source, prepared.env, prepared.heap)?;
let (dom, layout_tree, display_list) = extract_document(&document_value, &heap)
.map(|extracted_dom| {
let new_layout = layout_cat::layout(&extracted_dom, &stylesheet, viewport);
let new_display = paint_cat::build(&new_layout, &extracted_dom);
(extracted_dom, new_layout, new_display)
})
.unwrap_or((prepared.dom, prepared.layout_tree, prepared.display_list));
Ok(Frame::new(dom, layout_tree, display_list, value, heap))
}
struct Prepared {
env: Env,
heap: Heap,
document_value: Value,
dom: dom_cat::Document,
stylesheet: css_cat::Stylesheet,
layout_tree: layout_cat::LayoutTree,
display_list: paint_cat::DisplayList,
}
fn prepare(
html_source: &str,
css_source: &str,
viewport: Viewport,
commands: &HostCommands,
) -> Result<Prepared, Error> {
let html_doc = html_cat::parse(html_source)?;
let dom = dom_cat::Document::from_html_doc(&html_doc);
let stylesheet = css_cat::parse(css_source)?;
let layout_tree = layout_cat::layout(&dom, &stylesheet, viewport);
let display_list = paint_cat::build(&layout_tree, &dom);
let (env, heap) = ecma_runtime_cat::install(Env::empty(), Heap::new());
let (env, heap) = web_api_cat::install(env, heap, &html_doc);
let (env, heap) = commands.install(env, heap);
let document_value =
lookup_document(&env, &heap).ok_or(Error::Engine(boa_cat::Error::Unsupported {
feature: "document binding missing after web_api_cat::install",
}))?;
Ok(Prepared {
env,
heap,
document_value,
dom,
stylesheet,
layout_tree,
display_list,
})
}
fn evaluate(js_source: &str, env: Env, heap: Heap) -> Result<(Value, Heap), Error> {
let tokens = lex(js_source).map_err(boa_cat::Error::from)?;
let program = parse_script(&tokens).map_err(boa_cat::Error::from)?;
let (value, heap) = evaluate_program_with(&program, env, heap, Fuel::new(DEFAULT_FUEL))?;
Ok((value, heap))
}
fn lookup_document(env: &Env, heap: &Heap) -> Option<Value> {
env.lookup("document").and_then(|binding| match binding {
Binding::Cell(cell_id) => heap.cell(*cell_id).map(|cell| cell.value().clone()),
Binding::Direct(value) => Some(value.clone()),
})
}