1use super::*;
4
5pub(crate) use alloc::rc::Rc as PromiseRc;
8pub(crate) use core::cell::RefCell as PromiseRefCell;
9
10pub(crate) fn new_pending() -> PromiseRc<PromiseRefCell<PromiseState>> {
11 PromiseRc::new(PromiseRefCell::new(PromiseState::pending()))
12}
13
14pub(crate) fn this_promise_state(this: &Value) -> Option<PromiseRc<PromiseRefCell<PromiseState>>> {
15 if let Value::Object(o) = this {
16 let o = unwrap_proxy_target(o);
17 let b = o.borrow();
18 if let ObjKind::PromiseObj(s) = &b.kind {
19 return Some(s.clone());
20 }
21 }
22 None
23}
24
25pub(crate) fn callable_or_none(v: Value) -> Option<Value> {
26 if let Value::Object(o) = &v {
27 if o.borrow().is_callable() {
28 return Some(v);
29 }
30 }
31 None
32}
33
34pub(crate) fn promise_ctor(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
36 let state = new_pending();
37 let executor = arg(a, 0);
38 if let Some(exec) = callable_or_none(executor) {
39 let resolve = Value::Object(Obj::resolver(state.clone(), false));
40 let reject = Value::Object(Obj::resolver(state.clone(), true));
41 if let Err(e) = it.call_value(&exec, Value::Undefined, &[resolve, reject]) {
42 if !it.aborted {
43 it.promise_reject(&state, e);
44 }
45 }
46 }
47 Ok(Value::Object(Obj::promise(state)))
48}
49
50pub(crate) fn promise_resolve_static(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
51 let v = arg(a, 0);
52 if this_promise_state(&v).is_some() {
54 return Ok(v);
55 }
56 let state = new_pending();
57 it.promise_resolve(&state, v);
58 Ok(Value::Object(Obj::promise(state)))
59}
60
61pub(crate) fn promise_reject_static(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
62 let state = new_pending();
63 it.promise_reject(&state, arg(a, 0));
64 Ok(Value::Object(Obj::promise(state)))
65}
66
67pub(crate) fn promise_with_resolvers_static(_it: &mut Interp, _t: Value, _a: &[Value]) -> Result<Value, Value> {
69 let state = new_pending();
70 let promise = Value::Object(Obj::promise(state.clone()));
71 let resolve = Value::Object(Obj::resolver(state.clone(), false));
72 let reject = Value::Object(Obj::resolver(state, true));
73 let o = Obj::plain();
74 {
75 let mut b = o.borrow_mut();
76 b.props.insert("promise".into(), promise);
77 b.props.insert("resolve".into(), resolve);
78 b.props.insert("reject".into(), reject);
79 }
80 Ok(Value::Object(o))
81}
82
83pub(crate) fn promise_try_static(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
87 let state = new_pending();
88 let callback = arg(a, 0);
89 let rest: Vec<Value> = if a.len() > 1 { a[1..].to_vec() } else { Vec::new() };
90 match it.call_value(&callback, Value::Undefined, &rest) {
91 Ok(v) => it.promise_resolve(&state, v),
92 Err(e) => {
93 if !it.aborted {
94 it.promise_reject(&state, e);
95 }
96 }
97 }
98 Ok(Value::Object(Obj::promise(state)))
99}
100
101pub(crate) fn resolved_promise(it: &mut Interp, v: Value) -> Value {
103 let state = new_pending();
104 it.promise_resolve(&state, v);
105 Value::Object(Obj::promise(state))
106}
107pub fn make_empty_time_ranges() -> super::super::value::ObjRef {
110 let o = Obj::plain();
111 {
112 let mut b = o.borrow_mut();
113 b.props.insert("length".into(), Value::Number(0.0));
114 b.props.insert("start".into(), nv("start", time_ranges_out_of_bounds));
115 b.props.insert("end".into(), nv("end", time_ranges_out_of_bounds));
116 }
117 o
118}
119pub(crate) fn time_ranges_out_of_bounds(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
120 Err(it.error(alloc::format!(
121 "Failed to execute on 'TimeRanges': The index provided ({}) is greater than or equal to the maximum bound (0).",
122 arg(a, 0).to_number() as i64
123 )))
124}
125pub fn dom_media_play(it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
130 if let Some(idx) = this_dom_idx(&this) {
131 let was_paused = it.dom.borrow().get_attr(idx, "_paused").as_deref() != Some("false");
132 it.dom.borrow_mut().set_attr(idx, "_paused", "false");
133 if was_paused {
137 let _ = it.dispatch_event_in_interp(idx, "play", &[]);
138 let _ = it.dispatch_event_in_interp(idx, "playing", &[]);
139 }
140 let src = it.dom.borrow().get_attr(idx, "src").unwrap_or_default();
142 crate::warn!("[VIDEO] play() src={}", src.chars().take(80).collect::<String>());
143 if !src.is_empty() {
144 *crate::os_lib::mse::MEDIA_REQUEST.lock() = Some(crate::os_lib::mse::MediaRequest::Play(src));
145 }
146 }
147 Ok(resolved_promise(it, Value::Undefined))
148}
149pub fn dom_media_pause(it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
153 if let Some(idx) = this_dom_idx(&this) {
154 let was_playing = it.dom.borrow().get_attr(idx, "_paused").as_deref() == Some("false");
155 it.dom.borrow_mut().set_attr(idx, "_paused", "true");
156 if was_playing {
157 let _ = it.dispatch_event_in_interp(idx, "pause", &[]);
158 *crate::os_lib::mse::MEDIA_REQUEST.lock() = Some(crate::os_lib::mse::MediaRequest::Pause);
159 }
160 }
161 Ok(Value::Undefined)
162}
163pub(crate) fn clipboard_write_text(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
167 let text = arg(a, 0).to_js_string();
168 crate::os_lib::clipboard::set_text(&text);
169 if let Value::Object(o) = &this {
170 o.borrow_mut().props.insert("_clipboard".into(), Value::str(text));
171 }
172 Ok(resolved_promise(it, Value::Undefined))
173}
174pub(crate) fn clipboard_read_text(it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
176 let prop_text = if let Value::Object(o) = &this {
177 o.borrow().props.get("_clipboard").map(|v| v.to_js_string())
178 } else {
179 None
180 };
181 let text = match prop_text {
182 Some(t) if !t.is_empty() => t,
183 _ => crate::os_lib::clipboard::get_text(),
184 };
185 Ok(resolved_promise(it, Value::str(text)))
186}
187pub(crate) fn clipboard_item_ctor(_it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
191 let data = arg(a, 0);
192 let types: Vec<Value> = if let Value::Object(d) = &data {
193 d.borrow().props.keys().map(Value::str).collect()
194 } else {
195 Vec::new()
196 };
197 let o = Obj::plain();
198 {
199 let mut b = o.borrow_mut();
200 b.props.insert("_clipboard_item_data".into(), data);
201 b.props.insert("types".into(), Value::Object(Obj::array(types)));
202 b.props
203 .insert("getType".into(), nv("ClipboardItem.getType", clipboard_item_get_type));
204 }
205 Ok(Value::Object(o))
206}
207pub(crate) fn clipboard_item_get_type(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
209 let ty = arg(a, 0).to_js_string();
210 let value = if let Value::Object(o) = &this {
211 match o.borrow().props.get("_clipboard_item_data").cloned() {
212 Some(Value::Object(d)) => d.borrow().props.get(&ty).cloned().unwrap_or(Value::Undefined),
213 _ => Value::Undefined,
214 }
215 } else {
216 Value::Undefined
217 };
218 Ok(resolved_promise(it, value))
219}
220pub(crate) fn blob_bytes_to_string(blob: &Value) -> Option<String> {
224 let Value::Object(o) = blob else { return None };
225 let bytes_obj = o.borrow().props.get("_blob_bytes").cloned();
226 let Some(Value::Object(bytes)) = bytes_obj else { return None };
227 let byte_vals: alloc::vec::Vec<u8> = match &bytes.borrow().kind {
228 ObjKind::Array(items) => items.iter().map(|v| v.to_number() as u8).collect(),
229 _ => return None,
230 };
231 Some(String::from_utf8_lossy(&byte_vals).into_owned())
232}
233pub(crate) fn navigator_clipboard_write(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
237 if let Some(Value::Object(item)) = this_items(&arg(a, 0)).into_iter().next() {
238 let data = item.borrow().props.get("_clipboard_item_data").cloned();
239 if let Some(Value::Object(d)) = data {
240 let plain = d.borrow().props.get("text/plain").cloned();
241 if let Some(v) = plain {
242 let text = blob_bytes_to_string(&v).unwrap_or_else(|| v.to_js_string());
243 crate::os_lib::clipboard::set_text(&text);
244 if let Value::Object(o) = &this {
245 o.borrow_mut().props.insert("_clipboard".into(), Value::str(text));
246 }
247 }
248 }
249 }
250 Ok(resolved_promise(it, Value::Undefined))
251}
252pub(crate) fn navigator_get_high_entropy_values(
258 it: &mut Interp,
259 this: Value,
260 a: &[Value],
261) -> Result<Value, Value> {
262 let hints: alloc::vec::Vec<String> = this_items(&arg(a, 0)).iter().map(|v| v.to_js_string()).collect();
263 let result = Obj::plain();
264 {
265 let mut r = result.borrow_mut();
266 if let Value::Object(ua) = &this {
267 for key in ["brands", "mobile", "platform"] {
268 if let Some(v) = ua.borrow().props.get(key).cloned() {
269 r.props.insert(key.into(), v);
270 }
271 }
272 }
273 for hint in &hints {
274 match hint.as_str() {
275 "architecture" => {
276 r.props.insert("architecture".into(), Value::str("arm"));
277 }
278 "bitness" => {
279 r.props.insert("bitness".into(), Value::str("64"));
280 }
281 "model" => {
282 r.props.insert("model".into(), Value::str(""));
283 }
284 "platformVersion" => {
285 r.props.insert("platformVersion".into(), Value::str("1.0"));
286 }
287 "uaFullVersion" | "fullVersionList" => {
288 if let Value::Object(ua) = &this {
289 if let Some(v) = ua.borrow().props.get("brands").cloned() {
290 r.props.insert(hint.clone(), v);
291 }
292 }
293 }
294 _ => {}
295 }
296 }
297 }
298 Ok(resolved_promise(it, Value::Object(result)))
299}
300pub(crate) fn navigator_storage_estimate(it: &mut Interp, _this: Value, _a: &[Value]) -> Result<Value, Value> {
304 let result = Obj::plain();
305 {
306 let mut r = result.borrow_mut();
307 r.props.insert("usage".into(), Value::Number(0.0));
308 r.props.insert("quota".into(), Value::Number(1024.0 * 1024.0 * 1024.0));
309 }
310 Ok(resolved_promise(it, Value::Object(result)))
311}
312pub(crate) fn navigator_storage_persist(it: &mut Interp, _this: Value, _a: &[Value]) -> Result<Value, Value> {
316 Ok(resolved_promise(it, Value::Bool(true)))
317}
318pub(crate) fn navigator_clipboard_read(it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
321 let text = if let Value::Object(o) = &this {
322 o.borrow().props.get("_clipboard").map(|v| v.to_js_string()).unwrap_or_default()
323 } else {
324 String::new()
325 };
326 let blob = make_blob(text.bytes().map(|b| b as f64).collect(), String::from("text/plain"));
327 let data_obj = Obj::plain();
328 data_obj.borrow_mut().props.insert("text/plain".into(), blob);
329 let item = clipboard_item_ctor(it, Value::Undefined, &[Value::Object(data_obj)])?;
330 Ok(resolved_promise(it, Value::Object(Obj::array(alloc::vec![item]))))
331}
332
333pub(crate) fn rejected_promise(it: &mut Interp, reason: Value) -> Value {
335 let state = new_pending();
336 it.promise_reject(&state, reason);
337 Value::Object(Obj::promise(state))
338}
339
340pub(crate) fn split_http_url(url: &str) -> Option<(bool, String, String)> {
343 let (is_https, rest) = if let Some(r) = url.strip_prefix("https://") {
344 (true, r)
345 } else if let Some(r) = url.strip_prefix("http://") {
346 (false, r)
347 } else {
348 return None;
349 };
350 let (host, path) = match rest.split_once('/') {
351 Some((h, p)) => (h.to_string(), format!("/{}", p)),
352 None => (rest.to_string(), String::from("/")),
353 };
354 if host.is_empty() {
355 return None;
356 }
357 Some((is_https, host, path))
358}
359
360pub(crate) fn data_url_body(url: &str) -> Option<String> {
362 let rest = url.strip_prefix("data:")?;
363 let (meta, body) = rest.split_once(',')?;
364 let bytes = if meta.to_ascii_lowercase().ends_with(";base64") {
367 super::base64_to_bytes(body)
368 } else {
369 let b = body.as_bytes();
370 let mut out = Vec::with_capacity(b.len());
371 let mut i = 0;
372 while i < b.len() {
373 if b[i] == b'%' && i + 2 < b.len() {
374 let h = core::str::from_utf8(&b[i + 1..i + 3]).ok().and_then(|h| u8::from_str_radix(h, 16).ok());
375 if let Some(v) = h {
376 out.push(v);
377 i += 3;
378 continue;
379 }
380 }
381 out.push(b[i]);
382 i += 1;
383 }
384 out
385 };
386 Some(String::from_utf8_lossy(&bytes).into_owned())
387}
388
389pub(crate) fn http_status_text(status: u16) -> &'static str {
396 match status {
397 200 => "OK",
398 201 => "Created",
399 202 => "Accepted",
400 204 => "No Content",
401 301 => "Moved Permanently",
402 302 => "Found",
403 304 => "Not Modified",
404 400 => "Bad Request",
405 401 => "Unauthorized",
406 403 => "Forbidden",
407 404 => "Not Found",
408 405 => "Method Not Allowed",
409 408 => "Request Timeout",
410 409 => "Conflict",
411 410 => "Gone",
412 429 => "Too Many Requests",
413 500 => "Internal Server Error",
414 501 => "Not Implemented",
415 502 => "Bad Gateway",
416 503 => "Service Unavailable",
417 504 => "Gateway Timeout",
418 _ => "",
419 }
420}
421pub(crate) fn make_response(status: u16, body: String) -> Value {
422 make_response_with_url(status, body, String::new())
423}
424pub(crate) fn response_init_status(init: &Value, default: u16) -> u16 {
426 match init {
427 Value::Object(o) => o
428 .borrow()
429 .props
430 .get("status")
431 .map(|v| v.to_number() as u16)
432 .unwrap_or(default),
433 _ => default,
434 }
435}
436pub(crate) fn response_ctor(_it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
438 let body = match arg(a, 0) {
439 Value::Undefined => String::new(),
440 v => v.to_js_string(),
441 };
442 let init = arg(a, 1);
443 let status = response_init_status(&init, 200);
444 let headers_init = match &init {
445 Value::Object(o) => o.borrow().props.get("headers").cloned().unwrap_or(Value::Undefined),
446 _ => Value::Undefined,
447 };
448 let resp = make_response(status, body);
449 if let Value::Object(r) = &resp {
450 r.borrow_mut().props.insert("headers".into(), make_headers(headers_init));
451 }
452 Ok(resp)
453}
454pub(crate) fn response_json_static(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
457 let body = json_stringify(it, Value::Undefined, &[arg(a, 0)])?.to_js_string();
458 let status = response_init_status(&arg(a, 1), 200);
459 let resp = make_response(status, body);
460 if let Value::Object(r) = &resp {
461 let headers = make_headers(Value::Undefined);
462 if let Value::Object(h) = &headers {
463 h.borrow_mut()
464 .props
465 .insert("content-type".into(), Value::str("application/json"));
466 }
467 r.borrow_mut().props.insert("headers".into(), headers);
468 }
469 Ok(resp)
470}
471pub(crate) fn response_error_static(_it: &mut Interp, _t: Value, _a: &[Value]) -> Result<Value, Value> {
474 let resp = make_response(0, String::new());
475 if let Value::Object(r) = &resp {
476 let mut b = r.borrow_mut();
477 b.props.insert("type".into(), Value::str("error"));
478 b.props.insert("ok".into(), Value::Bool(false));
479 }
480 Ok(resp)
481}
482pub(crate) fn response_redirect_static(_it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
485 let url = arg(a, 0).to_js_string();
486 let status = match arg(a, 1) {
487 Value::Undefined => 302,
488 v => v.to_number() as u16,
489 };
490 let resp = make_response(status, String::new());
491 if let Value::Object(r) = &resp {
492 let headers = make_headers(Value::Undefined);
493 if let Value::Object(h) = &headers {
494 h.borrow_mut().props.insert("location".into(), Value::str(url));
495 }
496 r.borrow_mut().props.insert("headers".into(), headers);
497 }
498 Ok(resp)
499}
500
501pub(crate) fn make_response_with_url(status: u16, body: String, url: String) -> Value {
506 let resp = Obj::plain();
507 {
508 let mut r = resp.borrow_mut();
509 r.props
510 .insert("status".into(), Value::Number(status as f64));
511 r.props
512 .insert("ok".into(), Value::Bool((200..300).contains(&status)));
513 r.props
514 .insert("statusText".into(), Value::str(http_status_text(status)));
515 r.props.insert("url".into(), Value::str(url));
516 r.props.insert("type".into(), Value::str("basic"));
520 r.props.insert("redirected".into(), Value::Bool(false));
521 r.props.insert("headers".into(), make_headers(Value::Undefined));
526 r.props.insert("body".into(), make_readable_stream_from_string(&body));
527 r.props.insert("_body".into(), Value::str(body));
528 r.props.insert("text".into(), nv("text", response_text));
529 r.props.insert("json".into(), nv("json", response_json));
530 r.props.insert("blob".into(), nv("blob", response_blob));
533 r.props
536 .insert("arrayBuffer".into(), nv("arrayBuffer", response_array_buffer));
537 r.props.insert("clone".into(), nv("clone", response_clone));
541 }
542 Value::Object(resp)
543}
544
545pub(crate) fn response_clone(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
550 if let Value::Object(o) = &this {
551 let b = o.borrow();
552 let status = b.props.get("status").map(|v| v.to_number() as u16).unwrap_or(200);
553 let body = b.props.get("_body").map(|v| v.to_js_string()).unwrap_or_default();
554 let url = b.props.get("url").map(|v| v.to_js_string()).unwrap_or_default();
555 drop(b);
556 return Ok(make_response_with_url(status, body, url));
557 }
558 Ok(this)
559}
560
561pub(crate) fn response_body_string(this: &Value) -> String {
563 if let Value::Object(o) = this {
564 if let Some(v) = o.borrow().props.get("_body") {
565 return v.to_js_string();
566 }
567 }
568 String::new()
569}
570
571pub(crate) fn response_text(it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
573 let body = response_body_string(&this);
574 Ok(resolved_promise(it, Value::str(body)))
575}
576
577pub(crate) fn response_json(it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
579 let body = response_body_string(&this);
580 let parsed = json_parse(it, Value::Undefined, &[Value::str(body)])?;
581 Ok(resolved_promise(it, parsed))
582}
583
584pub(crate) fn attach_raw_body(resp: &Value, bytes: &[u8]) {
589 let Value::Object(o) = resp else { return };
590 let same = o.borrow().props.get("_body").is_some_and(|v| v.to_js_string().as_bytes() == bytes);
591 if !same {
592 let latin1: String = bytes.iter().map(|&b| b as char).collect();
593 o.borrow_mut().props.insert("_body_latin1".into(), Value::str(latin1));
594 }
595}
596
597fn response_body_bytes(this: &Value) -> alloc::vec::Vec<f64> {
599 if let Value::Object(o) = this {
600 if let Some(v) = o.borrow().props.get("_body_latin1") {
601 return v.to_js_string().chars().map(|c| (c as u32 & 0xff) as f64).collect();
602 }
603 }
604 response_body_string(this).as_bytes().iter().map(|b| *b as f64).collect()
605}
606
607pub(crate) fn response_blob(it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
609 let bytes = response_body_bytes(&this);
610 let blob = make_blob(bytes, String::new());
611 Ok(resolved_promise(it, blob))
612}
613
614pub(crate) fn response_array_buffer(it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
617 let bytes = response_body_bytes(&this);
618 let buf = make_arraybuffer_with_bytes(bytes);
619 Ok(resolved_promise(it, buf))
620}
621
622pub(crate) fn location_to_string(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
624 if let Value::Object(o) = &this {
625 if let Some(v) = o.borrow().props.get("href") {
626 return Ok(v.clone());
627 }
628 }
629 Ok(Value::str(""))
630}
631
632pub(crate) fn queue_location_nav(this: &Value, url: &str, mode: &str) {
636 crate::warn!("[JSNAV] location {} {:?}", mode, url.chars().take(120).collect::<String>());
637 if let Value::Object(o) = this {
638 let mut b = o.borrow_mut();
639 b.props
640 .insert(String::from("_pending_location"), Value::str(url));
641 b.props
642 .insert(String::from("_pending_location_mode"), Value::str(mode));
643 }
644}
645
646pub(crate) fn location_assign(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
648 let url = a.first().map(|v| v.to_js_string()).unwrap_or_default();
649 if !url.is_empty() {
650 queue_location_nav(&this, &url, "assign");
651 }
652 Ok(Value::Undefined)
653}
654
655pub(crate) fn location_replace(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
657 let url = a.first().map(|v| v.to_js_string()).unwrap_or_default();
658 if !url.is_empty() {
659 queue_location_nav(&this, &url, "replace");
660 }
661 Ok(Value::Undefined)
662}
663
664pub(crate) fn location_reload(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
666 let cur = if let Value::Object(o) = &this {
668 o.borrow()
669 .props
670 .get("href")
671 .map(|v| v.to_js_string())
672 .unwrap_or_default()
673 } else {
674 String::new()
675 };
676 queue_location_nav(&this, &cur, "reload");
677 Ok(Value::Undefined)
678}
679
680pub(crate) fn location_components(url: &str) -> Vec<(&'static str, String)> {
683 let (protocol, after) = if let Some(a) = url.strip_prefix("https://") {
684 ("https:", a)
685 } else if let Some(a) = url.strip_prefix("http://") {
686 ("http:", a)
687 } else {
688 ("", url)
689 };
690 let (hostport, pathpart) = match after.find(['/', '?', '#']) {
693 Some(i) => {
694 let (h, rest) = after.split_at(i);
695 let p = if rest.starts_with('/') { String::from(rest) } else { format!("/{}", rest) };
696 (String::from(h), p)
697 }
698 None => (String::from(after), String::from("/")),
699 };
700 let (userinfo, hostport) = match hostport.split_once('@') {
707 Some((ui, hp)) => (String::from(ui), String::from(hp)),
708 None => (String::new(), hostport),
709 };
710 let (username, password) = match userinfo.split_once(':') {
711 Some((u, p)) => (String::from(u), String::from(p)),
712 None => (userinfo.clone(), String::new()),
713 };
714 let (before_hash, hash) = match pathpart.split_once('#') {
715 Some((b, h)) => (String::from(b), format!("#{}", h)),
716 None => (pathpart.clone(), String::new()),
717 };
718 let (pathname, search) = match before_hash.split_once('?') {
719 Some((p, q)) => (String::from(p), format!("?{}", q)),
720 None => (before_hash.clone(), String::new()),
721 };
722 let (hostname, port) = match hostport.split_once(':') {
723 Some((h, p)) => (String::from(h), String::from(p)),
724 None => (hostport.clone(), String::new()),
725 };
726 let origin = if protocol.is_empty() {
727 String::new()
728 } else {
729 format!("{}//{}", protocol, hostport)
730 };
731 let href = if protocol.is_empty() {
733 String::from(url)
734 } else {
735 let auth = if url.contains('@') && !userinfo.is_empty() { format!("{}@{}", userinfo, hostport) } else { hostport.clone() };
736 format!("{}//{}{}{}{}", protocol, auth, pathname, search, hash)
737 };
738 alloc::vec![
739 ("href", href),
740 ("protocol", String::from(protocol)),
741 ("username", username),
742 ("password", password),
743 ("host", hostport),
744 ("hostname", hostname),
745 ("port", port),
746 ("origin", origin),
747 ("pathname", pathname),
748 ("search", search),
749 ("hash", hash),
750 ]
751}
752
753pub fn location_href(global: &Rc<RefCell<Scope>>) -> Option<String> {
755 let loc = match global.borrow().vars.get("location") {
756 Some(Value::Object(o)) => o.clone(),
757 _ => return None,
758 };
759 let href = loc
760 .borrow()
761 .props
762 .get("href")
763 .map(|v| v.to_js_string())
764 .unwrap_or_default();
765 if href.is_empty() {
766 None
767 } else {
768 Some(href)
769 }
770}
771
772pub fn location_hostname(global: &Rc<RefCell<Scope>>) -> Option<String> {
775 let loc = match global.borrow().vars.get("location") {
776 Some(Value::Object(o)) => o.clone(),
777 _ => return None,
778 };
779 let hostname = loc.borrow().props.get("hostname").map(|v| v.to_js_string());
780 hostname
781}
782
783pub fn format_last_modified() -> String {
787 let (y, mo, d, hh, mm, ss, ..) = decompose_ms(epoch_ms_now());
788 alloc::format!("{mo:02}/{d:02}/{y:04} {hh:02}:{mm:02}:{ss:02}")
789}
790
791pub(crate) fn history_change_state(
794 it: &mut Interp,
795 this: &Value,
796 a: &[Value],
797 push: bool,
798) -> Result<Value, Value> {
799 let new_state = arg(a, 0);
800 let url_arg = arg(a, 2);
801
802 if let Value::Object(o) = this {
803 if push {
804 let cur_url = location_href(&it.global).unwrap_or_default();
806 let cur_state = o.borrow().props.get("state").cloned().unwrap_or(Value::Null);
807 let stack_val = o.borrow().props.get("_spa_back_stack").cloned();
808 let stack = match stack_val {
809 Some(Value::Object(arr)) => arr,
810 _ => Obj::array(alloc::vec![]),
811 };
812 let entry = Obj::plain();
813 {
814 let mut eb = entry.borrow_mut();
815 eb.props.insert("url".into(), Value::str(cur_url));
816 eb.props.insert("state".into(), cur_state);
817 }
818 if let ObjKind::Array(items) = &mut stack.borrow_mut().kind {
819 items.push(Value::Object(entry));
820 }
821 o.borrow_mut().props.insert("_spa_back_stack".into(), Value::Object(stack));
822 o.borrow_mut().props.shift_remove("_spa_fwd_stack");
824 }
825
826 if !matches!(url_arg, Value::Undefined | Value::Null) {
827 let url = url_arg.to_js_string();
828 if !url.is_empty() {
829 let base = location_href(&it.global).unwrap_or_default();
830 let resolved = resolve_url(&base, &url);
831 update_location(&it.global, &resolved);
832 it.base_url = resolved;
833 }
834 }
835
836 let mut b = o.borrow_mut();
837 b.props.insert("state".into(), new_state);
838 if push {
839 let len = match b.props.get("length") {
840 Some(Value::Number(n)) => *n,
841 _ => 1.0,
842 };
843 b.props.insert("length".into(), Value::Number(len + 1.0));
844 }
845 } else {
846 if !matches!(url_arg, Value::Undefined | Value::Null) {
847 let url = url_arg.to_js_string();
848 if !url.is_empty() {
849 let base = location_href(&it.global).unwrap_or_default();
850 let resolved = resolve_url(&base, &url);
851 update_location(&it.global, &resolved);
852 it.base_url = resolved;
853 }
854 }
855 }
856 Ok(Value::Undefined)
857}
858
859pub(crate) fn history_push_state(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
860 history_change_state(it, &this, a, true)
861}
862
863pub(crate) fn history_replace_state(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
864 history_change_state(it, &this, a, false)
865}
866
867pub(crate) fn set_pending_nav(global: &Rc<RefCell<Scope>>, delta: i64) {
872 if delta == 0 {
873 return;
874 }
875 if let Some(Value::Object(h)) = global.borrow().vars.get("history") {
876 let cur = match h.borrow().props.get("_pending_nav") {
877 Some(Value::Number(n)) => *n as i64,
878 _ => 0,
879 };
880 h.borrow_mut()
881 .props
882 .insert("_pending_nav".into(), Value::Number((cur + delta) as f64));
883 }
884}
885
886pub(crate) fn history_back(it: &mut Interp, _this: Value, _a: &[Value]) -> Result<Value, Value> {
887 set_pending_nav(&it.global, -1);
888 Ok(Value::Undefined)
889}
890
891pub(crate) fn history_forward(it: &mut Interp, _this: Value, _a: &[Value]) -> Result<Value, Value> {
892 set_pending_nav(&it.global, 1);
893 Ok(Value::Undefined)
894}
895
896pub(crate) fn history_go(it: &mut Interp, _this: Value, a: &[Value]) -> Result<Value, Value> {
897 let n = arg(a, 0).to_number();
899 if n.is_finite() {
900 set_pending_nav(&it.global, n as i64);
901 }
902 Ok(Value::Undefined)
903}
904