///|
fn BidiProtocol::handle_script_get_element_for_test(
self : BidiProtocol,
request_id : Int,
params : Json?,
) -> Unit {
match self.resolve_script_get_element_for_test(request_id, params) {
Some(result) => self.send_success(request_id, Some(result))
None => ()
}
}
///|
fn BidiProtocol::handle_script_prepare_loaded_static_test_page(
self : BidiProtocol,
request_id : Int,
params : Json?,
) -> Unit {
match
self.resolve_script_prepare_loaded_static_test_page(request_id, params) {
Some((ctx_id, page, phase, scripts)) => {
if phase == "all" || phase == "inlineScripts" {
self.run_inline_test_scripts(ctx_id, scripts)
}
if phase == "all" || phase == "beforePageSpecific" {
self.run_test_setup_function(
ctx_id,
js_test_page_dom_content_loaded_source(),
)
}
if phase == "all" || phase == "pageSpecific" {
self.run_page_specific_test_setup(ctx_id, page)
}
if phase == "all" || phase == "afterPageSpecific" {
self.run_test_setup_function(ctx_id, js_test_page_reset_state_source())
self.run_test_setup_function(
ctx_id,
js_test_page_all_events_fallback_source(),
)
}
self.send_success(request_id, Some(make_object({})))
}
None => ()
}
}
///|
fn BidiProtocol::handle_script_load_static_test_page_for_test(
self : BidiProtocol,
request_id : Int,
params : Json?,
) -> Unit {
match self.resolve_script_load_static_test_page_for_test(request_id, params) {
Some((ctx_id, page, html)) =>
match self.manager.get_session(ctx_id) {
Some(session) => {
let url = build_html_data_url(html)
self.input_synthetic_events_by_context.remove(ctx_id)
self.context_synthetic_scrolled.remove(ctx_id)
self.clear_synthetic_location_href(ctx_id)
match session.navigate_to(url) {
Ok(_) => {
self.remember_requested_navigation_url(ctx_id, url)
let navigation_id = self.next_navigation_id.to_string()
self.next_navigation_id += 1
self.apply_navigation_commit_state(ctx_id, url, navigation_id)
if html.contains("allEvents") {
let scripts = extract_inline_script_blocks(html)
self.run_inline_test_scripts(ctx_id, scripts)
self.run_test_setup_function(
ctx_id,
js_test_page_dom_content_loaded_source(),
)
self.run_page_specific_test_setup(ctx_id, page)
self.run_test_setup_function(
ctx_id,
js_test_page_reset_state_source(),
)
self.run_test_setup_function(
ctx_id,
js_test_page_all_events_fallback_source(),
)
}
self.send_success(request_id, Some(make_object({})))
}
Err(e) =>
self.send_error(
request_id,
"unknown error",
"Navigation failed: " + e.message,
)
}
}
None =>
self.send_error(
request_id,
"no such frame",
"Unknown context: " + ctx_id,
)
}
None => ()
}
}
///|
fn BidiProtocol::resolve_script_get_element_for_test(
self : BidiProtocol,
request_id : Int,
params : Json?,
) -> Json? {
let map = match params {
Some(Object(map)) => map
_ => {
self.send_error(
request_id, "invalid argument", "params must be an object",
)
return None
}
}
let ctx_id = match map.get("context") {
Some(String(ctx_id)) =>
if ctx_id == "" || self.manager.get_session(ctx_id) is None {
self.send_error(
request_id,
"no such frame",
"Unknown context: " + ctx_id,
)
return None
} else {
ctx_id
}
_ => {
self.send_error(
request_id, "invalid argument", "context must be a string",
)
return None
}
}
let selector = match map.get("selector") {
Some(String(selector)) => selector
_ => {
self.send_error(
request_id, "invalid argument", "selector must be a string",
)
return None
}
}
let allow_frame_fallback = match map.get("allowFrameFallback") {
Some(True) => true
Some(False) => false
_ => false
}
Some(
self.resolve_test_element_remote_value(
ctx_id, selector, allow_frame_fallback,
),
)
}
///|
fn BidiProtocol::resolve_script_prepare_loaded_static_test_page(
self : BidiProtocol,
request_id : Int,
params : Json?,
) -> (String, String, String, Array[String])? {
let map = match params {
Some(Object(map)) => map
_ => {
self.send_error(
request_id, "invalid argument", "params must be an object",
)
return None
}
}
let ctx_id = match map.get("context") {
Some(String(ctx_id)) =>
if ctx_id == "" || self.manager.get_session(ctx_id) is None {
self.send_error(
request_id,
"no such frame",
"Unknown context: " + ctx_id,
)
return None
} else {
ctx_id
}
_ => {
self.send_error(
request_id, "invalid argument", "context must be a string",
)
return None
}
}
let page = match map.get("page") {
Some(String(page)) => page
_ => {
self.send_error(request_id, "invalid argument", "page must be a string")
return None
}
}
let phase = match map.get("phase") {
Some(String("inlineScripts")) => "inlineScripts"
Some(String("beforePageSpecific")) => "beforePageSpecific"
Some(String("pageSpecific")) => "pageSpecific"
Some(String("afterPageSpecific")) => "afterPageSpecific"
Some(String("all")) => "all"
Some(String(_)) => {
self.send_error(
request_id, "invalid argument", "phase must be a known string",
)
return None
}
_ => "all"
}
let scripts : Array[String] = []
let mut html : String? = None
match map.get("html") {
Some(String(value)) => html = Some(value)
Some(_) => {
self.send_error(request_id, "invalid argument", "html must be a string")
return None
}
None => ()
}
match map.get("scripts") {
Some(Array(raw_scripts)) =>
for raw_script in raw_scripts {
match raw_script {
String(script) => scripts.push(script)
_ => {
self.send_error(
request_id, "invalid argument", "scripts must be an array of strings",
)
return None
}
}
}
Some(_) => {
self.send_error(
request_id, "invalid argument", "scripts must be an array of strings",
)
return None
}
None => ()
}
if scripts.length() == 0 && (phase == "all" || phase == "inlineScripts") {
match html {
Some(content) =>
for script in extract_inline_script_blocks(content) {
scripts.push(script)
}
None => ()
}
}
Some((ctx_id, page, phase, scripts))
}
///|
fn BidiProtocol::resolve_script_load_static_test_page_for_test(
self : BidiProtocol,
request_id : Int,
params : Json?,
) -> (String, String, String)? {
let map = match params {
Some(Object(map)) => map
_ => {
self.send_error(
request_id, "invalid argument", "params must be an object",
)
return None
}
}
let ctx_id = match map.get("context") {
Some(String(ctx_id)) =>
if ctx_id == "" || self.manager.get_session(ctx_id) is None {
self.send_error(
request_id,
"no such frame",
"Unknown context: " + ctx_id,
)
return None
} else {
ctx_id
}
_ => {
self.send_error(
request_id, "invalid argument", "context must be a string",
)
return None
}
}
let page = match map.get("page") {
Some(String(page)) => page
_ => {
self.send_error(request_id, "invalid argument", "page must be a string")
return None
}
}
let html = match map.get("html") {
Some(String(html)) => html
_ => {
self.send_error(request_id, "invalid argument", "html must be a string")
return None
}
}
Some((ctx_id, page, html))
}
///|
fn BidiProtocol::resolve_test_script_context_runtime(
self : BidiProtocol,
request_id : Int,
params : Json?,
) -> (String, String, String)? {
let map = match params {
Some(Object(map)) => map
_ => {
self.send_error(
request_id, "invalid argument", "params must be an object",
)
return None
}
}
let ctx_id = match map.get("context") {
Some(String(ctx_id)) =>
if ctx_id == "" || self.manager.get_session(ctx_id) is None {
self.send_error(
request_id,
"no such frame",
"Unknown context: " + ctx_id,
)
return None
} else {
ctx_id
}
_ => {
self.send_error(
request_id, "invalid argument", "context must be a string",
)
return None
}
}
let sandbox_name = match map.get("sandbox") {
Some(String(name)) if name != "" => Some(name)
Some(String(_)) | None | Some(Null) => None
_ => {
self.send_error(
request_id, "invalid argument", "sandbox must be a string",
)
return None
}
}
let realm_id = match sandbox_name {
Some(name) => self.get_or_create_sandbox_realm(ctx_id, name)
None => self.context_realm.get(ctx_id).unwrap_or("realm-" + ctx_id)
}
let runtime_ctx_id = match sandbox_name {
Some(_) => realm_id
None => ctx_id
}
Some((ctx_id, realm_id, runtime_ctx_id))
}
///|
fn BidiProtocol::handle_script_create_iframe_context_for_test(
self : BidiProtocol,
request_id : Int,
params : Json?,
) -> Unit {
match self.resolve_script_create_iframe_context_result(request_id, params) {
Some(result) => self.send_success(request_id, Some(result))
None => ()
}
}
///|
fn BidiProtocol::handle_script_create_iframe_context_id_for_test(
self : BidiProtocol,
request_id : Int,
params : Json?,
) -> Unit {
match self.resolve_script_create_iframe_context_result(request_id, params) {
Some(result) =>
match get_string_field(result, "context") {
Some(ctx_id) =>
self.send_success(request_id, Some(Json::string(ctx_id)))
None =>
self.send_error(
request_id, "unknown error", "Failed to create iframe context",
)
}
None => ()
}
}
///|
fn BidiProtocol::resolve_script_create_iframe_context_result(
self : BidiProtocol,
request_id : Int,
params : Json?,
) -> Json? {
match self.resolve_script_create_iframe_context_for_test(request_id, params) {
Some((parent_context, url)) => {
let user_context_id = self.context_user_context
.get(parent_context)
.unwrap_or("default")
let remote_value = self.evaluate_test_element_remote_expression(
parent_context,
js_test_page_create_iframe_source(),
[url],
)
let result = match get_string_field(remote_value, "type") {
Some("window") =>
match get_string_field(remote_value, "value") {
Some(ctx_id) if ctx_id != "" =>
make_object({
"context": Json::string(ctx_id),
"parentContext": Json::string(parent_context),
"userContext": Json::string(user_context_id),
"url": Json::string(self.current_context_url(ctx_id)),
})
_ =>
self.create_synthetic_child_context_result(
parent_context, url, user_context_id,
)
}
_ =>
self.create_synthetic_child_context_result(
parent_context, url, user_context_id,
)
}
Some(result)
}
None => None
}
}
///|
fn BidiProtocol::resolve_script_create_iframe_context_for_test(
self : BidiProtocol,
request_id : Int,
params : Json?,
) -> (String, String)? {
let map = match params {
Some(Object(map)) => map
_ => {
self.send_error(
request_id, "invalid argument", "params must be an object",
)
return None
}
}
let ctx_id = match map.get("context") {
Some(String(ctx_id)) =>
if ctx_id == "" || self.manager.get_session(ctx_id) is None {
self.send_error(
request_id,
"no such frame",
"Unknown context: " + ctx_id,
)
return None
} else {
ctx_id
}
_ => {
self.send_error(
request_id, "invalid argument", "context must be a string",
)
return None
}
}
let url = match map.get("url") {
Some(String(url)) => url
_ => {
self.send_error(request_id, "invalid argument", "url must be a string")
return None
}
}
Some((ctx_id, url))
}
///|
fn BidiProtocol::resolve_test_element_remote_value(
self : BidiProtocol,
ctx_id : String,
selector : String,
allow_frame_fallback : Bool,
) -> Json {
match self.locate_nodes_css(ctx_id, selector, [], 1, "{}") {
Array(nodes) =>
if nodes.length() > 0 {
nodes[0]
} else if allow_frame_fallback && selector == "textarea" {
self.evaluate_test_element_remote_expression(
ctx_id,
"selector => {" +
"let target = document.querySelector(selector);" +
"if (!target && selector === 'textarea' && document && document.body && typeof document.createElement === 'function') {" +
"target = document.createElement('textarea');" +
"if (target && target.style) {" +
"target.style.width = '100px';" +
"target.style.height = '40px';" +
"}" +
"if (target && typeof target.setAttribute === 'function') {" +
"target.setAttribute('data-crater-frame-fallback', '1');" +
"}" +
"document.body.appendChild(target);" +
"}" +
"if (typeof window.allEvents === 'undefined') {" +
"window.allEvents = { events: [] };" +
"}" +
"return target;" +
"}",
[selector],
)
} else {
make_object({ "type": Json::string("null") })
}
_ => make_object({ "type": Json::string("null") })
}
}
///|
fn BidiProtocol::evaluate_test_element_remote_expression(
self : BidiProtocol,
ctx_id : String,
function_declaration : String,
string_arguments : Array[String],
) -> Json {
let args_literal : Array[String] = []
for argument in string_arguments {
args_literal.push("\"" + escape_js_string(argument) + "\"")
}
let expression = "(" +
function_declaration +
")(" +
args_literal.join(",") +
")"
set_runtime_context(ctx_id)
set_runtime_context_frames(
ctx_id,
self.context_children.get(ctx_id).unwrap_or([]),
)
self.apply_effective_viewport_to_runtime_context(ctx_id, ctx_id)
let eval_result_json = evaluate_js_with_console(
expression, false, false, false, "{}",
)
let eval_result = @json.parse(eval_result_json) catch {
_ => return make_object({ "type": Json::string("null") })
}
match get_string_field(eval_result, "type") {
Some("success") =>
match get_field(eval_result, "result") {
Some(result) => result
None => make_object({ "type": Json::string("null") })
}
_ => make_object({ "type": Json::string("null") })
}
}
///|
fn BidiProtocol::run_test_setup_function(
self : BidiProtocol,
ctx_id : String,
function_declaration : String,
) -> Unit {
ignore(
self.evaluate_test_element_remote_expression(ctx_id, function_declaration, []),
)
}
///|
fn BidiProtocol::run_inline_test_scripts(
self : BidiProtocol,
ctx_id : String,
scripts : Array[String],
) -> Unit {
for script in scripts {
ignore(
self.evaluate_test_element_remote_expression(
ctx_id,
"source => { (0, eval)(source); return null; }",
[script],
),
)
}
}
///|
fn BidiProtocol::run_page_specific_test_setup(
self : BidiProtocol,
ctx_id : String,
page : String,
) -> Unit {
match page {
"test_actions.html" =>
self.run_test_setup_function(
ctx_id,
js_test_actions_page_specific_source(),
)
"test_actions_scroll.html" =>
self.run_test_setup_function(
ctx_id,
js_test_actions_scroll_page_specific_source(),
)
_ => ()
}
}
///|
fn BidiProtocol::handle_script_setup_beforeunload_page_for_test(
self : BidiProtocol,
request_id : Int,
params : Json?,
) -> Unit {
match self.resolve_test_script_context_runtime(request_id, params) {
Some((ctx_id, _, _)) => {
self.run_test_setup_function(ctx_id, js_beforeunload_page_setup_source())
self.send_success(request_id, Some(make_object({})))
}
None => ()
}
}
///|
fn BidiProtocol::handle_script_prepare_beforeunload_page_for_test(
self : BidiProtocol,
request_id : Int,
params : Json?,
) -> Unit {
self.handle_script_prepare_beforeunload_page_for_test_internal(
request_id, params, false,
)
}
///|
fn BidiProtocol::handle_script_prepare_beforeunload_page_url_for_test(
self : BidiProtocol,
request_id : Int,
params : Json?,
) -> Unit {
self.handle_script_prepare_beforeunload_page_for_test_internal(
request_id, params, true,
)
}
///|
fn BidiProtocol::handle_script_prepare_beforeunload_page_for_test_internal(
self : BidiProtocol,
request_id : Int,
params : Json?,
return_url_only : Bool,
) -> Unit {
match
self.resolve_script_prepare_beforeunload_page_for_test(request_id, params) {
Some((ctx_id, requested_url)) => {
let resolved_url = self.resolve_effective_navigation_target_url(
ctx_id, requested_url,
)
self.input_synthetic_events_by_context.remove(ctx_id)
self.context_synthetic_scrolled.remove(ctx_id)
self.clear_synthetic_location_href(ctx_id)
let _ = self.fail_in_flight_navigation(
ctx_id, "aborted", "Navigation interrupted by another navigation",
)
if !is_valid_navigation_target_url(resolved_url) {
self.send_error(request_id, "invalid argument", "url is invalid")
return
}
if self.navigation_blocked_by_network_conditions(ctx_id, resolved_url) {
let nav_id = self.next_navigation_id.to_string()
self.next_navigation_id += 1
self.emit_navigation_started_event(ctx_id, resolved_url, nav_id)
self.send_error(
request_id, "unknown error", "Navigation failed: network offline",
)
return
}
if should_fail_navigation_with_unknown_error(resolved_url) {
let nav_id = self.next_navigation_id.to_string()
self.next_navigation_id += 1
self.emit_navigation_started_event(ctx_id, resolved_url, nav_id)
self.send_error(
request_id, "unknown error", "Navigation failed: navigation target is unreachable",
)
return
}
if self.is_iframe_navigation_blocked(ctx_id, resolved_url) {
let nav_id = self.next_navigation_id.to_string()
self.next_navigation_id += 1
self.emit_navigation_started_event(ctx_id, resolved_url, nav_id)
self.emit_navigation_failed_event(ctx_id, resolved_url, nav_id)
self.send_error(
request_id, "unknown error", "Navigation failed: blocked by embedding policy",
)
return
}
match self.manager.get_session(ctx_id) {
Some(session) => {
let previous_url = session.get_url()
match session.navigate_to(resolved_url) {
Ok(_) => {
self.remember_requested_navigation_url(ctx_id, requested_url)
if is_fragment_navigation(previous_url, resolved_url) {
self.clear_pending_navigation_state(ctx_id)
let nav_id = self.next_navigation_id.to_string()
self.next_navigation_id += 1
self.emit_fragment_navigated_event(
ctx_id,
resolved_url,
Some(nav_id),
)
self.emit_history_updated_event(ctx_id, resolved_url)
self.run_test_setup_function(
ctx_id,
js_beforeunload_page_setup_source(),
)
if return_url_only {
self.send_navigation_url_result(
request_id, ctx_id, requested_url, resolved_url, nav_id,
)
} else {
let result = make_object({
"navigation": Json::string(nav_id),
"url": Json::string(resolved_url),
})
self.send_navigation_state_result(
request_id, ctx_id, requested_url, resolved_url, nav_id, result,
)
}
return
}
if return_url_only {
self.pending_navigation_response_mode[ctx_id] = "url"
} else {
self.pending_navigation_response_mode.remove(ctx_id)
}
let completion = self.build_navigation_completion(
request_id,
ctx_id,
resolved_url,
"complete",
None,
)
self.run_test_setup_function(
ctx_id,
js_beforeunload_page_setup_source(),
)
if completion.deferred_response {
return
}
self.pending_navigation_response_mode.remove(ctx_id)
if return_url_only {
self.send_success(request_id, Some(Json::string(resolved_url)))
} else {
self.send_navigation_state_result(
request_id,
ctx_id,
requested_url,
resolved_url,
completion.navigation_id,
completion.result,
)
}
}
Err(e) => {
let nav_id = self.next_navigation_id.to_string()
self.next_navigation_id += 1
self.emit_navigation_started_event(ctx_id, resolved_url, nav_id)
self.send_error(
request_id,
"unknown error",
"Navigation failed: " + e.message,
)
}
}
}
None => {
self.send_error(
request_id,
"no such frame",
"Unknown context: " + ctx_id,
)
return
}
}
}
None => ()
}
}
///|
fn BidiProtocol::resolve_script_prepare_beforeunload_page_for_test(
self : BidiProtocol,
request_id : Int,
params : Json?,
) -> (String, String)? {
let map = match params {
Some(Object(map)) => map
_ => {
self.send_error(
request_id, "invalid argument", "params must be an object",
)
return None
}
}
let ctx_id = match map.get("context") {
Some(String(ctx_id)) =>
if ctx_id == "" || self.manager.get_session(ctx_id) is None {
self.send_error(
request_id,
"no such frame",
"Unknown context: " + ctx_id,
)
return None
} else {
ctx_id
}
_ => {
self.send_error(
request_id, "invalid argument", "context must be a string",
)
return None
}
}
let url = match map.get("url") {
Some(String(url)) => url
Some(Null) | None => default_beforeunload_test_page_url()
_ => {
self.send_error(request_id, "invalid argument", "url must be a string")
return None
}
}
Some((ctx_id, url))
}
///|
fn BidiProtocol::handle_script_sync_document_cookies_for_test(
self : BidiProtocol,
request_id : Int,
params : Json?,
) -> Unit {
match self.resolve_test_script_context_runtime(request_id, params) {
Some((ctx_id, _, runtime_ctx_id)) => {
let (cookie_text, cookie_count) = self.sync_document_cookies_for_test_context(
ctx_id, runtime_ctx_id,
)
self.send_success(
request_id,
Some(
make_object({
"cookieText": Json::string(cookie_text),
"cookieCount": Json::number(cookie_count.to_double()),
}),
),
)
}
None => ()
}
}
///|
fn BidiProtocol::sync_document_cookies_for_test_context(
self : BidiProtocol,
ctx_id : String,
runtime_ctx_id : String,
) -> (String, Int) {
set_runtime_context(runtime_ctx_id)
set_runtime_context_frames(
runtime_ctx_id,
self.context_children.get(ctx_id).unwrap_or([]),
)
self.apply_effective_viewport_to_runtime_context(runtime_ctx_id, ctx_id)
if !self.dom_initialized {
let _ = evaluate_js_with_console("undefined", false, false, false, "{}")
self.dom_initialized = true
}
let eval_result_json = evaluate_js_with_console(
"document.cookie", false, false, false, "{}",
)
self.dom_initialized = true
let eval_result = @json.parse(eval_result_json) catch { _ => make_object({}) }
let cookie_text = match get_string_field(eval_result, "type") {
Some("success") =>
match get_field(eval_result, "result") {
Some(remote_value) =>
remote_value_as_string(remote_value).unwrap_or(
self.resolve_document_cookie(ctx_id).unwrap_or(""),
)
None => self.resolve_document_cookie(ctx_id).unwrap_or("")
}
_ => self.resolve_document_cookie(ctx_id).unwrap_or("")
}
let mut cookie_count = 0
if cookie_text.trim() != "" {
for raw_entry in cookie_text.split(";") {
if raw_entry.contains("=") {
self.remember_document_cookie(ctx_id, raw_entry.trim().to_owned())
cookie_count += 1
}
}
}
(cookie_text, cookie_count)
}
///|
fn BidiProtocol::handle_script_fetch_for_test(
self : BidiProtocol,
request_id : Int,
params : Json?,
) -> Unit {
let map = match params {
Some(Object(map)) => map
_ => {
self.send_error(
request_id, "invalid argument", "params must be an object",
)
return
}
}
let ctx_id = match map.get("context") {
Some(String(ctx_id)) =>
if ctx_id == "" || self.manager.get_session(ctx_id) is None {
self.send_error(
request_id,
"no such frame",
"Unknown context: " + ctx_id,
)
return
} else {
ctx_id
}
Some(Null) | None => self.ensure_default_context()
_ => {
self.send_error(
request_id, "invalid argument", "context must be a string",
)
return
}
}
let sandbox_name = match map.get("sandbox") {
Some(String(name)) if name != "" => Some(name)
Some(String(_)) | Some(Null) | None => None
_ => {
self.send_error(
request_id, "invalid argument", "sandbox must be a string",
)
return
}
}
let realm_id = match sandbox_name {
Some(name) => self.get_or_create_sandbox_realm(ctx_id, name)
None => self.context_realm.get(ctx_id).unwrap_or("realm-" + ctx_id)
}
let runtime_ctx_id = match sandbox_name {
Some(_) => realm_id
None => ctx_id
}
let target_url = match map.get("url") {
Some(String(url)) => url
_ => {
self.send_error(request_id, "invalid argument", "url must be a string")
return
}
}
let raw_headers = map.get("headers")
let headers_json = match map.get("headersJson") {
Some(String(headers_json)) => headers_json
Some(Null) | None =>
match raw_headers {
Some(raw_headers) => raw_headers.stringify()
None => "null"
}
_ => {
self.send_error(
request_id, "invalid argument", "headersJson must be a string",
)
return
}
}
let raw_post_data = get_map_field_with_alias(map, "postData", "post_data")
let post_data_json = match map.get("postDataJson") {
Some(String(post_data_json)) => post_data_json
Some(Null) | None =>
match raw_post_data {
Some(raw_post_data) => raw_post_data.stringify()
None => "null"
}
_ => {
self.send_error(
request_id, "invalid argument", "postDataJson must be a string",
)
return
}
}
let post_data_mode = match map.get("postDataMode") {
Some(String("none")) => "none"
Some(String("formData")) => "formData"
Some(String("value")) => "value"
Some(String(_)) => {
self.send_error(
request_id, "invalid argument", "postDataMode must be a known string",
)
return
}
Some(Null) | None =>
if get_map_field_with_alias(map, "requestData", "request_data") != None {
"value"
} else if raw_post_data is Some(raw_post_data) {
synthetic_fetch_post_data_mode_from_value(raw_post_data)
} else if post_data_json != "null" {
"value"
} else {
"none"
}
_ => {
self.send_error(
request_id, "invalid argument", "postDataMode must be a string",
)
return
}
}
let request_headers = match
get_map_field_with_alias(map, "requestHeaders", "request_headers") {
Some(raw_headers) =>
match
self.normalize_synthetic_network_header_entries(
request_id,
Some(raw_headers),
) {
Some(request_headers) => request_headers
None => return
}
None =>
match raw_headers {
Some(raw_headers) =>
synthetic_fetch_request_headers_from_value(raw_headers)
None => synthetic_fetch_request_headers_from_json(headers_json)
}
}
let request_value = match
get_map_field_with_alias(map, "requestData", "request_data") {
Some(raw_value) =>
match
self.normalize_synthetic_network_bytes_value(
request_id, raw_value, "requestData",
) {
Some(value) => Some(value)
None => return
}
None =>
match raw_post_data {
Some(raw_post_data) =>
synthetic_fetch_request_value_from_post_data(
raw_post_data, post_data_mode,
)
None =>
synthetic_fetch_request_value_from_post_data_json(
post_data_json, post_data_mode,
)
}
}
let request_method = match map.get("method") {
Some(String(request_method)) => request_method
Some(Null) | None =>
if request_value != None || post_data_mode != "none" {
"POST"
} else {
"GET"
}
_ => {
self.send_error(request_id, "invalid argument", "method must be a string")
return
}
}
let timeout_ms = match map.get("timeoutMs") {
Some(Number(n, ..)) if n >= 0.0 && n == n.to_int64().to_double() =>
n.to_int64().to_int()
Some(Number(_)) => {
self.send_error(
request_id, "invalid argument", "timeoutMs must be a non-negative integer",
)
return
}
Some(Null) | None => 0
_ => {
self.send_error(
request_id, "invalid argument", "timeoutMs must be a non-negative integer",
)
return
}
}
let should_abort = match
get_map_field_with_alias(map, "shouldAbort", "should_abort") {
None | Some(Null) => false
Some(True) => true
Some(False) => false
Some(_) => {
self.send_error(
request_id, "invalid argument", "shouldAbort must be a boolean",
)
return
}
}
let resolved_url = self.resolve_fetch_target_url(ctx_id, target_url)
if self.resolve_storage_request_cookies(ctx_id, Some(resolved_url)).length() ==
0 {
ignore(self.sync_document_cookies_for_test_context(ctx_id, runtime_ctx_id))
}
if @crater_network.canonicalize_network_http_url(resolved_url) is Some(_) ||
resolved_url.has_prefix("data:") {
let result = self.perform_synthetic_fetch_internal(
request_id, ctx_id, resolved_url, request_method, request_headers, request_value,
should_abort,
)
match result.blocked_text {
Some(text) =>
self.send_success(
request_id,
Some(
make_object({
"exceptionDetails": make_object({ "text": Json::string(text) }),
}),
),
)
None => {
let response_value = self.build_script_fetch_for_test_response_value(
ctx_id,
resolved_url,
request_headers,
result.response_value,
)
self.send_success(request_id, Some(response_value))
}
}
return
}
self.send_script_fetch_from_context_for_test(
request_id, ctx_id, realm_id, runtime_ctx_id, resolved_url, request_method, headers_json,
post_data_json, post_data_mode, timeout_ms,
)
}
///|
fn synthetic_fetch_request_headers_from_json(
headers_json : String,
) -> Array[Json] {
let parsed = @json.parse(headers_json) catch { _ => return [] }
synthetic_fetch_request_headers_from_value(parsed)
}
///|
fn synthetic_fetch_request_headers_from_value(
raw_headers : Json,
) -> Array[Json] {
match raw_headers {
Object(headers_map) => {
let headers : Array[Json] = []
for name, raw_value in headers_map {
headers.push(
synthetic_network_header_string_entry(
name,
synthetic_fetch_json_text_value(raw_value),
),
)
}
headers
}
_ => []
}
}
///|
fn synthetic_fetch_post_data_mode_from_value(raw_post_data : Json) -> String {
match raw_post_data {
Null => "none"
Object(_) => "formData"
_ => "value"
}
}
///|
fn synthetic_fetch_request_value_from_post_data_json(
post_data_json : String,
post_data_mode : String,
) -> Json? {
if post_data_mode == "none" {
return None
}
let parsed = @json.parse(post_data_json) catch { _ => return None }
synthetic_fetch_request_value_from_post_data(parsed, post_data_mode)
}
///|
fn synthetic_fetch_request_value_from_post_data(
post_data : Json,
post_data_mode : String,
) -> Json? {
if post_data_mode == "formData" {
match post_data {
Object(form_data) =>
Some(synthetic_fetch_multipart_request_value(form_data))
_ => None
}
} else {
Some(
make_object({
"type": Json::string("string"),
"value": Json::string(synthetic_fetch_json_text_value(post_data)),
}),
)
}
}
///|
fn BidiProtocol::build_script_fetch_for_test_response_value(
self : BidiProtocol,
ctx_id : String,
url : String,
request_headers : Array[Json],
default_value : Json,
) -> Json {
if !synthetic_fetch_is_headers_echo_url(url) {
return default_value
}
make_object({
"type": Json::string("string"),
"value": Json::string(
self.build_script_fetch_headers_echo_payload(ctx_id, request_headers),
),
})
}
///|
fn BidiProtocol::build_script_fetch_headers_echo_payload(
self : BidiProtocol,
context_id : String,
request_headers : Array[Json],
) -> String {
let headers : Map[String, Json] = {}
for
header in self.resolve_network_extra_header_entries(
context_id, request_headers,
) {
let name = match synthetic_network_header_entry_name(header) {
Some(name) => name
None => continue
}
let value = match synthetic_network_header_entry_string_value(header) {
Some(value) => value
None => continue
}
headers[name] = Json::array([Json::string(value)])
}
make_object({ "headers": make_object(headers) }).stringify()
}
///|
fn synthetic_fetch_multipart_request_value(
form_data : Map[String, Json],
) -> Json {
let boundary = "----crater-boundary"
let mut has_binary = false
let mut body = ""
for field_name, raw_value in form_data {
match synthetic_fetch_form_data_file_entry(raw_value) {
Some((filename, content_type, payload)) => {
has_binary = true
body = body +
"--" +
boundary +
"\r\n" +
"Content-Disposition: form-data; name=\"" +
field_name +
"\"; filename=\"" +
filename +
"\"\r\n" +
"Content-Type: " +
content_type +
"\r\n\r\n" +
payload +
"\r\n"
}
None =>
body = body +
"--" +
boundary +
"\r\n" +
"Content-Disposition: form-data; name=\"" +
field_name +
"\"\r\n\r\n" +
synthetic_fetch_json_text_value(raw_value) +
"\r\n"
}
}
body = body + "--" + boundary + "--\r\n"
if has_binary {
make_object({
"type": Json::string("base64"),
"value": Json::string(encode_base64_raw(body)),
})
} else {
make_object({ "type": Json::string("string"), "value": Json::string(body) })
}
}
///|
fn synthetic_fetch_form_data_file_entry(
raw_value : Json,
) -> (String, String, String)? {
let value_map = match raw_value {
Object(value_map) => value_map
_ => return None
}
let encoded_value = match value_map.get("value") {
Some(String(encoded_value)) => encoded_value
_ => return None
}
let filename = match value_map.get("filename") {
Some(String(filename)) => filename
_ => "blob.bin"
}
let content_type = match value_map.get("type") {
Some(String(content_type)) => content_type
_ => "application/octet-stream"
}
Some((filename, content_type, decode_base64(encoded_value)))
}
///|
fn synthetic_fetch_json_text_value(raw_value : Json) -> String {
match raw_value {
String(value) => value
Number(value, ..) => value.to_string()
True => "true"
False => "false"
Null => "null"
Array(_) | Object(_) => raw_value.stringify()
}
}
///|
fn BidiProtocol::handle_script_fetch_from_context_for_test(
self : BidiProtocol,
request_id : Int,
params : Json?,
) -> Unit {
let map = match params {
Some(Object(map)) => map
_ => {
self.send_error(
request_id, "invalid argument", "params must be an object",
)
return
}
}
let (ctx_id, realm_id, runtime_ctx_id) = match
self.resolve_test_script_context_runtime(request_id, params) {
Some(value) => value
None => return
}
let url = match map.get("url") {
Some(String(url)) => url
_ => {
self.send_error(request_id, "invalid argument", "url must be a string")
return
}
}
let request_method = match map.get("method") {
Some(String(request_method)) => request_method
_ => {
self.send_error(request_id, "invalid argument", "method must be a string")
return
}
}
let headers_json = match map.get("headersJson") {
Some(String(headers_json)) => headers_json
Some(Null) | None => "null"
_ => {
self.send_error(
request_id, "invalid argument", "headersJson must be a string",
)
return
}
}
let post_data_json = match map.get("postDataJson") {
Some(String(post_data_json)) => post_data_json
Some(Null) | None => "null"
_ => {
self.send_error(
request_id, "invalid argument", "postDataJson must be a string",
)
return
}
}
let post_data_mode = match map.get("postDataMode") {
Some(String("none")) => "none"
Some(String("formData")) => "formData"
Some(String("value")) => "value"
Some(String(_)) => {
self.send_error(
request_id, "invalid argument", "postDataMode must be a known string",
)
return
}
None => "none"
_ => {
self.send_error(
request_id, "invalid argument", "postDataMode must be a string",
)
return
}
}
let timeout_ms = match map.get("timeoutMs") {
Some(Number(n, ..)) if n >= 0.0 && n == n.to_int64().to_double() =>
n.to_int64().to_int()
Some(Number(_)) => {
self.send_error(
request_id, "invalid argument", "timeoutMs must be a non-negative integer",
)
return
}
None => 0
_ => {
self.send_error(
request_id, "invalid argument", "timeoutMs must be a non-negative integer",
)
return
}
}
self.send_script_fetch_from_context_for_test(
request_id, ctx_id, realm_id, runtime_ctx_id, url, request_method, headers_json,
post_data_json, post_data_mode, timeout_ms,
)
}
///|
fn BidiProtocol::send_script_fetch_from_context_for_test(
self : BidiProtocol,
request_id : Int,
ctx_id : String,
realm_id : String,
runtime_ctx_id : String,
url : String,
request_method : String,
headers_json : String,
post_data_json : String,
post_data_mode : String,
timeout_ms : Int,
) -> Unit {
set_runtime_context(runtime_ctx_id)
set_runtime_context_frames(
runtime_ctx_id,
self.context_children.get(ctx_id).unwrap_or([]),
)
self.apply_effective_viewport_to_runtime_context(runtime_ctx_id, ctx_id)
if !self.dom_initialized {
let _ = evaluate_js_with_console("undefined", false, false, false, "{}")
self.dom_initialized = true
}
let expression = js_fetch_from_context_for_test_expression(
url, request_method, headers_json, post_data_json, post_data_mode, timeout_ms,
)
match self.socket {
Some(socket) =>
eval_and_send_async_with_console(
socket, request_id, expression, runtime_ctx_id, realm_id, false, false, false,
true,
)
None =>
self.send_error(
request_id, "unknown error", "WebSocket is required for async fetch evaluation",
)
}
}
///|
extern "js" fn js_test_page_dom_content_loaded_source() -> String =
#| () => String.raw`(() => {
#| const event =
#| typeof globalThis.__bidiCreateEvent === "function"
#| ? globalThis.__bidiCreateEvent("DOMContentLoaded", { bubbles: true, cancelable: true })
#| : { type: "DOMContentLoaded" };
#| if (document && typeof document.dispatchEvent === "function") {
#| try { document.dispatchEvent(event); } catch (_e) {}
#| }
#| return null;
#| })`
///|
extern "js" fn js_test_page_reset_state_source() -> String =
#| () => String.raw`(() => {
#| if (window && typeof window === "object") {
#| window.__craterAllEventsFallbackInit = false;
#| window.__bidiFocusedElement = null;
#| }
#| globalThis.__bidiFocusedElement = null;
#| globalThis.__bidiRecordedTestActionsMousemove = false;
#| if (typeof allEvents !== "undefined") {
#| window.allEvents = allEvents;
#| }
#| if (typeof window.allEvents === "undefined") {
#| window.allEvents = { events: [] };
#| }
#| if (window.allEvents && Array.isArray(window.allEvents.events)) {
#| window.allEvents.events.length = 0;
#| }
#| return null;
#| })`
///|
extern "js" fn js_test_page_all_events_fallback_source() -> String =
#| () => String.raw`(() => {
#| if (typeof window.allEvents === "undefined") {
#| window.allEvents = { events: [] };
#| }
#|
#| let keyboardRecorder =
#| typeof globalThis.recordKeyboardEvent === "function"
#| ? globalThis.recordKeyboardEvent
#| : (window && typeof window.recordKeyboardEvent === "function"
#| ? window.recordKeyboardEvent
#| : null);
#| let pointerRecorder =
#| typeof globalThis.recordPointerEvent === "function"
#| ? globalThis.recordPointerEvent
#| : (window && typeof window.recordPointerEvent === "function"
#| ? window.recordPointerEvent
#| : null);
#| let wheelRecorder =
#| typeof globalThis.recordWheelEvent === "function"
#| ? globalThis.recordWheelEvent
#| : (window && typeof window.recordWheelEvent === "function"
#| ? window.recordWheelEvent
#| : null);
#|
#| if (!keyboardRecorder) {
#| keyboardRecorder = (event) => {
#| if (!window.allEvents || !Array.isArray(window.allEvents.events)) {
#| window.allEvents = { events: [] };
#| }
#| window.allEvents.events.push({
#| code: event.code,
#| key: event.key,
#| which: event.which,
#| location: event.location,
#| ctrl: event.ctrlKey,
#| meta: event.metaKey,
#| shift: event.shiftKey,
#| repeat: event.repeat,
#| type: event.type,
#| });
#| };
#| }
#| if (!pointerRecorder) {
#| pointerRecorder = (event) => {
#| if (!window.allEvents || !Array.isArray(window.allEvents.events)) {
#| window.allEvents = { events: [] };
#| }
#| if (event.type === "contextmenu" && typeof event.preventDefault === "function") {
#| event.preventDefault();
#| }
#| window.allEvents.events.push({
#| type: event.type,
#| button: event.button,
#| buttons: event.buttons,
#| pageX: event.pageX,
#| pageY: event.pageY,
#| ctrlKey: event.ctrlKey,
#| metaKey: event.metaKey,
#| altKey: event.altKey,
#| shiftKey: event.shiftKey,
#| clientX: event.clientX,
#| clientY: event.clientY,
#| isTrusted: event.isTrusted,
#| detail: event.detail,
#| target: event.target && event.target.id ? event.target.id : "",
#| pointerType: event.pointerType || "",
#| width: event.width,
#| height: event.height,
#| pressure: event.pressure,
#| tangentialPressure: event.tangentialPressure,
#| tiltX: event.tiltX,
#| tiltY: event.tiltY,
#| twist: event.twist,
#| altitudeAngle: event.altitudeAngle,
#| azimuthAngle: event.azimuthAngle,
#| });
#| };
#| }
#| if (typeof globalThis.resetEvents !== "function") {
#| globalThis.resetEvents = () => {
#| if (!window.allEvents || !Array.isArray(window.allEvents.events)) {
#| window.allEvents = { events: [] };
#| }
#| window.allEvents.events.length = 0;
#| };
#| }
#|
#| if (keyboardRecorder && typeof globalThis.recordKeyboardEvent !== "function") {
#| globalThis.recordKeyboardEvent = keyboardRecorder;
#| }
#| if (pointerRecorder && typeof globalThis.recordPointerEvent !== "function") {
#| globalThis.recordPointerEvent = pointerRecorder;
#| }
#| if (wheelRecorder && typeof globalThis.recordWheelEvent !== "function") {
#| globalThis.recordWheelEvent = wheelRecorder;
#| }
#| try {
#| if (typeof globalThis.recordKeyboardEvent === "function") {
#| (0, eval)("var recordKeyboardEvent = globalThis.recordKeyboardEvent");
#| }
#| if (typeof globalThis.recordPointerEvent === "function") {
#| (0, eval)("var recordPointerEvent = globalThis.recordPointerEvent");
#| }
#| if (typeof globalThis.recordWheelEvent === "function") {
#| (0, eval)("var recordWheelEvent = globalThis.recordWheelEvent");
#| }
#| if (typeof globalThis.resetEvents === "function") {
#| (0, eval)("var resetEvents = globalThis.resetEvents");
#| }
#| } catch (_e) {}
#|
#| if (keyboardRecorder) {
#| const keyReporter = document.getElementById("keys");
#| const hasKeyListeners = !!(
#| keyReporter &&
#| keyReporter._listeners &&
#| (
#| (Array.isArray(keyReporter._listeners.keydown) && keyReporter._listeners.keydown.length > 0) ||
#| (Array.isArray(keyReporter._listeners.keyup) && keyReporter._listeners.keyup.length > 0) ||
#| (Array.isArray(keyReporter._listeners.keypress) && keyReporter._listeners.keypress.length > 0)
#| )
#| );
#| if (keyReporter && !hasKeyListeners && !keyReporter.__craterKeyboardListeners) {
#| keyReporter.addEventListener("keyup", keyboardRecorder);
#| keyReporter.addEventListener("keypress", keyboardRecorder);
#| keyReporter.addEventListener("keydown", keyboardRecorder);
#| keyReporter.__craterKeyboardListeners = true;
#| }
#| }
#|
#| if (pointerRecorder) {
#| const outer = document.getElementById("outer");
#| const hasPointerListeners = !!(
#| outer &&
#| outer._listeners &&
#| (
#| (Array.isArray(outer._listeners.click) && outer._listeners.click.length > 0) ||
#| (Array.isArray(outer._listeners.dblclick) && outer._listeners.dblclick.length > 0) ||
#| (Array.isArray(outer._listeners.mousedown) && outer._listeners.mousedown.length > 0) ||
#| (Array.isArray(outer._listeners.mouseup) && outer._listeners.mouseup.length > 0) ||
#| (Array.isArray(outer._listeners.contextmenu) && outer._listeners.contextmenu.length > 0)
#| )
#| );
#| if (outer && !hasPointerListeners && !outer.__craterPointerListeners) {
#| outer.addEventListener("click", pointerRecorder);
#| outer.addEventListener("dblclick", pointerRecorder);
#| outer.addEventListener("mousedown", pointerRecorder);
#| outer.addEventListener("mouseup", pointerRecorder);
#| outer.addEventListener("contextmenu", pointerRecorder);
#| outer.__craterPointerListeners = true;
#| }
#| const hasWindowMouseMove = !!(
#| window &&
#| window._listeners &&
#| Array.isArray(window._listeners.mousemove) &&
#| window._listeners.mousemove.length > 0
#| );
#| if (window && typeof window.addEventListener === "function" && !hasWindowMouseMove && !window.__craterFirstPointerMoveListener) {
#| const recordFirstPointerMove = (event) => {
#| pointerRecorder(event);
#| try {
#| window.removeEventListener("mousemove", recordFirstPointerMove);
#| } catch (_e) {}
#| };
#| window.addEventListener("mousemove", recordFirstPointerMove);
#| window.__craterFirstPointerMoveListener = true;
#| }
#| }
#|
#| if (wheelRecorder) {
#| eventReporter = document.getElementById("event-reporter");
#| const notScrollable = document.getElementById("not-scrollable");
#| const hasNotScrollableWheel = !!(
#| notScrollable &&
#| notScrollable._listeners &&
#| Array.isArray(notScrollable._listeners.wheel) &&
#| notScrollable._listeners.wheel.length > 0
#| );
#| if (notScrollable && !hasNotScrollableWheel && !notScrollable.__craterWheelListener) {
#| notScrollable.addEventListener("wheel", wheelRecorder);
#| notScrollable.__craterWheelListener = true;
#| }
#| const scrollable = document.getElementById("scrollable");
#| const hasScrollableWheel = !!(
#| scrollable &&
#| scrollable._listeners &&
#| Array.isArray(scrollable._listeners.wheel) &&
#| scrollable._listeners.wheel.length > 0
#| );
#| if (scrollable && !hasScrollableWheel && !scrollable.__craterWheelListener) {
#| scrollable.addEventListener("wheel", wheelRecorder);
#| scrollable.__craterWheelListener = true;
#| }
#| }
#|
#| if (!window.__craterAllEventsFallbackInit) {
#| const pushEvent = (event) => {
#| window.allEvents.events.push({
#| type: event.type,
#| code: event.code,
#| key: event.key,
#| which: event.which,
#| location: event.location,
#| ctrl: event.ctrlKey,
#| meta: event.metaKey,
#| shift: event.shiftKey,
#| repeat: event.repeat,
#| button: event.button,
#| buttons: event.buttons,
#| pageX: event.pageX,
#| pageY: event.pageY,
#| deltaX: event.deltaX,
#| deltaY: event.deltaY,
#| deltaZ: event.deltaZ,
#| deltaMode: event.deltaMode,
#| clientX: event.clientX,
#| clientY: event.clientY,
#| isTrusted: event.isTrusted,
#| detail: event.detail,
#| target: event.target && event.target.id ? event.target.id : "",
#| pointerType: event.pointerType || "",
#| width: event.width,
#| height: event.height,
#| pressure: event.pressure,
#| tangentialPressure: event.tangentialPressure,
#| tiltX: event.tiltX,
#| tiltY: event.tiltY,
#| twist: event.twist,
#| altitudeAngle: event.altitudeAngle,
#| azimuthAngle: event.azimuthAngle,
#| altKey: event.altKey,
#| ctrlKey: event.ctrlKey,
#| metaKey: event.metaKey,
#| shiftKey: event.shiftKey,
#| });
#| };
#| const hasKeyboardRecorder = !!keyboardRecorder;
#| const hasPointerRecorder = !!pointerRecorder;
#| const hasWheelRecorder = !!wheelRecorder;
#|
#| const keyReporter = document.getElementById("keys");
#| if (keyReporter && !hasKeyboardRecorder) {
#| keyReporter.addEventListener("keydown", pushEvent);
#| keyReporter.addEventListener("keypress", pushEvent);
#| keyReporter.addEventListener("keyup", pushEvent);
#| }
#|
#| if (!hasPointerRecorder) {
#| const pointerTarget = document;
#| for (const eventName of ["mousemove", "mousedown", "mouseup", "click", "dblclick", "contextmenu", "auxclick"]) {
#| pointerTarget.addEventListener(eventName, pushEvent);
#| }
#| }
#|
#| const notScrollable = document.getElementById("not-scrollable");
#| if (notScrollable && !hasWheelRecorder) {
#| notScrollable.addEventListener("wheel", pushEvent);
#| }
#| const scrollable = document.getElementById("scrollable");
#| if (scrollable && !hasWheelRecorder) {
#| scrollable.addEventListener("wheel", pushEvent);
#| }
#| window.__craterAllEventsFallbackInit = true;
#| }
#| return null;
#| })`
///|
extern "js" fn js_test_actions_page_specific_source() -> String =
#| () => String.raw`(() => {
#| const setStyle = (el, styles) => {
#| if (!el || !el.style) return;
#| for (const [key, value] of Object.entries(styles)) {
#| try { el.style[key] = value; } catch (_e) {}
#| }
#| };
#| for (const el of Array.from(document.querySelectorAll(".area"))) {
#| setStyle(el, {
#| width: "100px",
#| height: "50px",
#| backgroundColor: "#ccc",
#| });
#| }
#| for (const el of Array.from(document.querySelectorAll(".block"))) {
#| setStyle(el, {
#| width: "5px",
#| height: "5px",
#| borderWidth: "1px",
#| borderStyle: "solid",
#| borderColor: "red",
#| });
#| }
#| setStyle(document.getElementById("trackPointer"), { position: "fixed" });
#| setStyle(document.getElementById("resultContainer"), { width: "600px", height: "60px" });
#| setStyle(document.getElementById("dragArea"), { position: "relative" });
#| setStyle(document.getElementById("dragTarget"), {
#| position: "absolute",
#| top: "22px",
#| left: "47px",
#| });
#| return null;
#| })`
///|
extern "js" fn js_test_actions_scroll_page_specific_source() -> String =
#| () => String.raw`(() => {
#| const ensure = (id, tagName = "div", parent = document.body) => {
#| let element = document.getElementById(id);
#| if (!element && document && typeof document.createElement === "function") {
#| element = document.createElement(tagName);
#| element.id = id;
#| parent.appendChild(element);
#| }
#| return element;
#| };
#| const setStyle = (el, styles) => {
#| if (!el || !el.style) return;
#| for (const [key, value] of Object.entries(styles)) {
#| try { el.style[key] = value; } catch (_e) {}
#| }
#| };
#| if (!window.allEvents || !Array.isArray(window.allEvents.events)) {
#| window.allEvents = { events: [] };
#| }
#| window.recordWheelEvent = (event) => {
#| if (!window.allEvents || !Array.isArray(window.allEvents.events)) {
#| window.allEvents = { events: [] };
#| }
#| const target = event && event.target ? event.target : null;
#| window.allEvents.events.push({
#| type: event?.type || "wheel",
#| button: event?.button ?? 0,
#| buttons: event?.buttons ?? 0,
#| pageX: event?.pageX ?? 0,
#| pageY: event?.pageY ?? 0,
#| deltaX: event?.deltaX ?? 0,
#| deltaY: event?.deltaY ?? 0,
#| deltaZ: event?.deltaZ ?? 0,
#| deltaMode: event?.deltaMode ?? 0,
#| target: target && target.id ? target.id : "",
#| altKey: !!event?.altKey,
#| ctrlKey: !!event?.ctrlKey,
#| metaKey: !!event?.metaKey,
#| shiftKey: !!event?.shiftKey,
#| });
#| };
#| const notScrollable = ensure("not-scrollable");
#| const notScrollableContent = ensure("not-scrollable-content", "div", notScrollable || document.body);
#| setStyle(notScrollable, { width: "100px", height: "50px", marginBottom: "100px" });
#| setStyle(notScrollableContent, { width: "200px", height: "100px", backgroundColor: "#ccc" });
#| const scrollable = ensure("scrollable");
#| const scrollableContent = ensure("scrollable-content", "div", scrollable || document.body);
#| setStyle(scrollable, { width: "100px", height: "100px", overflow: "scroll" });
#| setStyle(scrollableContent, { width: "600px", height: "1000px", backgroundColor: "blue" });
#| if (notScrollable && !notScrollable.__craterWheelListener) {
#| notScrollable.addEventListener("wheel", window.recordWheelEvent);
#| notScrollable.__craterWheelListener = true;
#| }
#| if (scrollable && !scrollable.__craterWheelListener) {
#| scrollable.addEventListener("wheel", window.recordWheelEvent);
#| scrollable.__craterWheelListener = true;
#| }
#| const iframe = ensure("iframe", "iframe");
#| setStyle(iframe, { width: "100px", height: "100px" });
#| if (iframe && !iframe.__craterWheelIframeSetup) {
#| iframe.srcdoc =
#| '