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> {
run_script_with_cookies(html_source, css_source, js_source, viewport, commands, "")
.map(|(frame, _writes)| frame)
}
pub fn run_script_with_cookies(
html_source: &str,
css_source: &str,
js_source: &str,
viewport: Viewport,
commands: &HostCommands,
cookie_string: &str,
) -> Result<(Frame, Vec<String>), Error> {
run_script_with_state(
html_source,
css_source,
js_source,
viewport,
commands,
cookie_string,
&[],
&[],
)
.map(|outcome| {
let (frame, writes, _local, _session) = outcome.into_parts();
(frame, writes)
})
}
#[must_use]
pub struct ScriptOutcome {
frame: Frame,
cookie_writes: Vec<String>,
local_storage: Vec<(String, String)>,
session_storage: Vec<(String, String)>,
}
impl ScriptOutcome {
#[must_use]
pub fn frame(&self) -> &Frame {
&self.frame
}
#[must_use]
pub fn cookie_writes(&self) -> &[String] {
&self.cookie_writes
}
#[must_use]
pub fn local_storage(&self) -> &[(String, String)] {
&self.local_storage
}
#[must_use]
pub fn session_storage(&self) -> &[(String, String)] {
&self.session_storage
}
#[must_use]
#[allow(clippy::type_complexity)]
pub fn into_parts(
self,
) -> (
Frame,
Vec<String>,
Vec<(String, String)>,
Vec<(String, String)>,
) {
(
self.frame,
self.cookie_writes,
self.local_storage,
self.session_storage,
)
}
}
#[allow(clippy::too_many_arguments)]
pub fn run_script_with_state(
html_source: &str,
css_source: &str,
js_source: &str,
viewport: Viewport,
commands: &HostCommands,
cookie_string: &str,
local_seed: &[(String, String)],
session_seed: &[(String, String)],
) -> Result<ScriptOutcome, Error> {
let prepared = prepare(html_source, css_source, viewport, commands)?;
let document_value = prepared.document_value.clone();
let stylesheet = prepared.stylesheet.clone();
let local_storage_value = web_api_cat::lookup_local_storage(&prepared.env, &prepared.heap);
let session_storage_value = web_api_cat::lookup_session_storage(&prepared.env, &prepared.heap);
let heap = web_api_cat::set_document_cookie(&document_value, prepared.heap, cookie_string);
let heap = maybe_seed_storage(local_storage_value.as_ref(), heap, local_seed);
let heap = maybe_seed_storage(session_storage_value.as_ref(), heap, session_seed);
let (value, heap) = evaluate(js_source, prepared.env, heap)?;
let cookie_writes = web_api_cat::read_cookie_writes(&document_value, &heap);
let local_items = local_storage_value
.as_ref()
.map_or_else(Vec::new, |v| web_api_cat::read_storage_items(v, &heap));
let session_items = session_storage_value
.as_ref()
.map_or_else(Vec::new, |v| web_api_cat::read_storage_items(v, &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(ScriptOutcome {
frame: Frame::new(dom, layout_tree, display_list, value, heap),
cookie_writes,
local_storage: local_items,
session_storage: session_items,
})
}
fn maybe_seed_storage(
storage_value: Option<&Value>,
heap: Heap,
seed: &[(String, String)],
) -> Heap {
if let Some(value) = storage_value {
web_api_cat::seed_storage(value, heap, seed)
} else {
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()),
})
}