1use mozjs::jsapi::*;
21use mozjs::jsval::{Int32Value, JSVal, ObjectValue, StringValue, UndefinedValue};
22use mozjs::rooted;
23use mozjs::rust::wrappers2::{JS_DefineFunction, JS_DefineProperty3, JS_NewPlainObject};
24
25use bun_core::ZBox;
26
27#[allow(unsafe_op_in_unsafe_fn)]
32unsafe extern "C" fn bun_peek(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
33 let args = CallArgs::from_vp(vp, argc);
34 if args.argc_ == 0 {
35 args.rval().set(UndefinedValue());
36 return true;
37 }
38 let val = *args.get(0).ptr;
39 if !val.is_object() {
40 args.rval().set(val);
41 return true;
42 }
43 let mut wrapped = mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
44 let cx_ref = &mut wrapped;
45 rooted!(&in(cx_ref) let obj = val.to_object());
46
47 if JS::IsPromiseObject(obj.handle().into()) {
49 let state = JS::GetPromiseState(obj.handle().into());
50 match state {
51 PromiseState::Fulfilled | PromiseState::Rejected => {
52 let mut rv = UndefinedValue();
53 mozjs::glue::JS_GetPromiseResult(
54 obj.handle().into(),
55 MutableHandle::<Value> {
56 _phantom_0: ::std::marker::PhantomData,
57 ptr: &mut rv,
58 },
59 );
60 if state == PromiseState::Rejected {
61 JS_ClearPendingException(cx);
65 }
66 args.rval().set(rv);
67 return true;
68 }
69 PromiseState::Pending => {
70 args.rval().set(val);
71 return true;
72 }
73 }
74 }
75
76 let mut next_v = UndefinedValue();
80 if !JS_GetProperty(
81 cx,
82 obj.handle().into(),
83 c"next".as_ptr(),
84 MutableHandle::<Value> {
85 _phantom_0: ::std::marker::PhantomData,
86 ptr: &mut next_v,
87 },
88 ) {
89 JS_ClearPendingException(cx);
90 args.rval().set(val);
91 return true;
92 }
93 if !next_v.is_object() {
94 args.rval().set(val);
95 return true;
96 }
97 rooted!(&in(cx_ref) let next_obj = next_v.to_object());
98 if !JS_ObjectIsFunction(next_obj.get()) {
99 args.rval().set(val);
100 return true;
101 }
102 let mut has_iter = false;
103 JS_HasProperty(
104 cx,
105 obj.handle().into(),
106 c"Symbol.iterator".as_ptr(),
107 &mut has_iter,
108 );
109 if has_iter {
110 args.rval().set(val);
111 return true;
112 }
113
114 rooted!(&in(cx_ref) let next_val = ObjectValue(next_obj.get()));
116 let call_args = HandleValueArray {
117 length_: 1,
118 elements_: &*next_val.handle(),
119 };
120 let mut rval = UndefinedValue();
121 let ok = JS_CallFunctionValue(
122 cx,
123 obj.handle().into(),
124 next_val.handle().into(),
125 &call_args,
126 MutableHandle::<Value> {
127 _phantom_0: ::std::marker::PhantomData,
128 ptr: &mut rval,
129 },
130 );
131 if !ok || !rval.is_object() {
132 if !ok {
133 JS_ClearPendingException(cx);
134 }
135 args.rval().set(val);
136 return true;
137 }
138 rooted!(&in(cx_ref) let res = rval.to_object());
139 let mut done_v = UndefinedValue();
140 JS_GetProperty(
141 cx,
142 res.handle().into(),
143 c"done".as_ptr(),
144 MutableHandle::<Value> {
145 _phantom_0: ::std::marker::PhantomData,
146 ptr: &mut done_v,
147 },
148 );
149 if done_v.to_boolean() {
150 args.rval().set(val);
151 return true;
152 }
153 let mut value_v = UndefinedValue();
154 JS_GetProperty(
155 cx,
156 res.handle().into(),
157 c"value".as_ptr(),
158 MutableHandle::<Value> {
159 _phantom_0: ::std::marker::PhantomData,
160 ptr: &mut value_v,
161 },
162 );
163
164 let peeked_src = r#"(function(original, firstValue) {
167 var done = false;
168 return {
169 get peeked() { return firstValue; },
170 next: function() {
171 if (done) return { value: undefined, done: true };
172 if (firstValue !== undefined) {
173 var v = firstValue;
174 firstValue = undefined;
175 return { value: v, done: false };
176 }
177 var r = original.next();
178 if (r.done) done = true;
179 return r;
180 },
181 __originalIterator: original,
182 };
183})"#;
184 let mut text = mozjs::rust::transform_str_to_source_text(peeked_src);
185 let opts = mozjs::glue::NewCompileOptions(cx, c"<bun:peek>".as_ptr(), 1);
186 if opts.is_null() {
187 args.rval().set(val);
188 return true;
189 }
190 let mut ctor = UndefinedValue();
191 let ctor_h = MutableHandle::<Value> {
192 _phantom_0: ::std::marker::PhantomData,
193 ptr: &mut ctor,
194 };
195 let evaluated = mozjs_sys::jsapi::JS::Evaluate2(cx, opts, &mut text, ctor_h);
196 libc::free(opts as *mut _);
197 if !evaluated || !ctor.is_object() {
198 JS_ClearPendingException(cx);
199 args.rval().set(val);
200 return true;
201 }
202 rooted!(&in(cx_ref) let ctor_obj = ctor.to_object());
203 rooted!(&in(cx_ref) let ctor_val = ObjectValue(ctor_obj.get()));
204 rooted!(&in(cx_ref) let this_arg = val);
205 rooted!(&in(cx_ref) let fv = value_v);
206 let call_vals = [this_arg.handle().get(), fv.handle().get()];
207 let call_arr = HandleValueArray {
208 length_: 2,
209 elements_: call_vals.as_ptr(),
210 };
211 let mut out = UndefinedValue();
212 let ok2 = JS_CallFunctionValue(
213 cx,
214 ctor_obj.handle().into(),
215 ctor_val.handle().into(),
216 &call_arr,
217 MutableHandle::<Value> {
218 _phantom_0: ::std::marker::PhantomData,
219 ptr: &mut out,
220 },
221 );
222 if !ok2 || !out.is_object() {
223 if !ok2 {
224 JS_ClearPendingException(cx);
225 }
226 args.rval().set(val);
227 return true;
228 }
229 args.rval().set(out);
230 true
231}
232
233#[allow(unsafe_op_in_unsafe_fn)]
234unsafe extern "C" fn bun_peek_status(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
235 let args = CallArgs::from_vp(vp, argc);
236 let val = if args.argc_ > 0 { *args.get(0).ptr } else { UndefinedValue() };
237 let name = if !val.is_object() {
238 "fulfilled" } else {
240 let mut wrapped = mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
241 let cx_ref = &mut wrapped;
242 rooted!(&in(cx_ref) let obj = val.to_object());
243 if JS::IsPromiseObject(obj.handle().into()) {
244 match JS::GetPromiseState(obj.handle().into()) {
245 PromiseState::Pending => "pending",
246 PromiseState::Fulfilled => "fulfilled",
247 _ => "rejected",
248 }
249 } else {
250 "fulfilled"
251 }
252 };
253 let c_name = ZBox::from_bytes(name.as_bytes());
254 let js_str = JS_NewStringCopyZ(cx, c_name.as_ptr());
255 args.rval().set(if js_str.is_null() {
256 UndefinedValue()
257 } else {
258 StringValue(&*js_str)
259 });
260 true
261}
262
263#[allow(unsafe_op_in_unsafe_fn)]
268unsafe extern "C" fn bun_string_width(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
269 let args = CallArgs::from_vp(vp, argc);
270 let val = if args.argc_ > 0 { *args.get(0).ptr } else { UndefinedValue() };
271 if val.is_undefined() {
272 args.rval().set(Int32Value(0));
273 return true;
274 }
275 if !val.is_string() {
276 JS_ReportErrorUTF8(cx, c"Bun.stringWidth expects a string".as_ptr());
277 return false;
278 }
279
280 let mut count_ansi = false;
284 if args.argc_ > 1 && (*args.get(1).ptr).is_object() {
285 let mut wrapped = mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
286 let cx_ref = &mut wrapped;
287 rooted!(&in(cx_ref) let oobj = (*args.get(1).ptr).to_object());
288 let mut v = UndefinedValue();
289 if JS_GetProperty(
290 cx,
291 oobj.handle().into(),
292 c"countAnsiEscapeCodes".as_ptr(),
293 MutableHandle::<Value> {
294 _phantom_0: ::std::marker::PhantomData,
295 ptr: &mut v,
296 },
297 ) && v.is_boolean()
298 {
299 count_ansi = v.to_boolean();
300 }
301 }
302
303 let mut wrapped = mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
314 let cx_ref = &mut wrapped;
315 rooted!(&in(cx_ref) let sv = val);
316 let jsstr = mozjs::rust::ToString(cx_ref, sv.handle());
317 if jsstr.is_null() {
318 JS_ClearPendingException(cx);
319 args.rval().set(Int32Value(0));
320 return true;
321 }
322 let str_val = StringValue(&*jsstr);
323 let utf16: Vec<u16> = crate::js_to_rust_string(cx, str_val).encode_utf16().collect();
324 let width = utf16_visible_width(&utf16, count_ansi);
325 args.rval().set(Int32Value(width as i32));
326 true
327}
328
329use ::std::io::Write as _;
330
331fn utf16_visible_width(input: &[u16], count_ansi_literal: bool) -> usize {
337 #[derive(PartialEq)]
338 enum St {
339 Code,
340 Csi,
341 Osc,
342 OscEsc,
343 }
344 use unicode_width::UnicodeWidthChar as _;
345 let mut st = St::Code;
346 let mut width = 0usize;
347 let mut i = 0usize;
348 while i < input.len() {
349 let c = input[i];
350 let (ch, step): (char, usize) = if (0xD800..0xDC00).contains(&c)
353 && i + 1 < input.len()
354 && (0xDC00..0xE000).contains(&input[i + 1])
355 {
356 let hi = (c as u32 - 0xD800) << 10;
357 let lo = input[i + 1] as u32 - 0xDC00;
358 (::std::char::from_u32(0x10000 + hi + lo).unwrap_or('\u{FFFD}'), 2)
359 } else {
360 (::std::char::from_u32(c as u32).unwrap_or('\u{FFFD}'), 1)
361 };
362 match st {
363 St::Code => {
364 if ch == '\u{1b}' {
365 st = if input.get(i + 1) == Some(&(b'[' as u16)) {
366 St::Csi
367 } else if input.get(i + 1) == Some(&(b']' as u16)) {
368 St::Osc
369 } else {
370 St::Csi };
372 i += 2;
373 continue;
374 }
375 width += ch.width().unwrap_or(0);
376 }
377 St::Csi => {
378 if (0x40..=0x7e).contains(&c) {
379 if count_ansi_literal {
380 width += 1; }
382 st = St::Code;
383 } else if (0x20..0x3f).contains(&c) {
384 if count_ansi_literal {
385 width += 1;
386 }
387 }
388 }
389 St::Osc => {
390 if ch == '\u{7}' {
391 st = St::Code;
392 } else if ch == '\u{1b}' {
393 st = St::OscEsc;
394 } else if count_ansi_literal && c >= 0x20 {
395 width += 1;
396 }
397 }
398 St::OscEsc => {
399 if ch == '\\' {
400 st = St::Code;
401 } else {
402 st = St::Osc;
403 }
404 }
405 }
406 i += step;
407 }
408 width
409}
410
411#[allow(unsafe_op_in_unsafe_fn)]
412unsafe extern "C" fn regexp_escape(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
413 let args = CallArgs::from_vp(vp, argc);
414 if args.argc_ == 0 || !(*args.get(0).ptr).is_string() {
415 JS_ReportErrorUTF8(cx, c"expected string argument".as_ptr());
417 return false;
418 }
419 let input = crate::js_to_rust_string(cx, *args.get(0).ptr);
420 let mut out: Vec<u8> = Vec::with_capacity(input.len() + 8);
421 let _ = bun_core::string::escape_reg_exp::escape_reg_exp(input.as_bytes(), &mut out);
422 let c_out = ZBox::from_vec(out);
423 let js_str = JS_NewStringCopyZ(cx, c_out.as_ptr());
424 args.rval().set(if js_str.is_null() {
425 UndefinedValue()
426 } else {
427 StringValue(&*js_str)
428 });
429 true
430}
431
432#[allow(unsafe_op_in_unsafe_fn)]
437unsafe extern "C" fn readable_stream_to_array(
438 cx: *mut JSContext,
439 argc: u32,
440 vp: *mut JSVal,
441) -> bool {
442 let args = CallArgs::from_vp(vp, argc);
443 if args.argc_ == 0 {
444 JS_ReportErrorUTF8(cx, c"Bun.readableStreamToArray expects a ReadableStream".as_ptr());
445 return false;
446 }
447 let src = r#"(async function(stream) {
448 var out = [];
449 var reader = stream.getReader();
450 while (true) {
451 var r = await reader.read();
452 if (r.done) break;
453 out.push(r.value);
454 }
455 reader.releaseLock && reader.releaseLock();
456 return out;
457})"#;
458 let mut wrapped = mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
459 let cx_ref = &mut wrapped;
460 let mut text = mozjs::rust::transform_str_to_source_text(src);
461 let opts = mozjs::glue::NewCompileOptions(cx, c"<bun:rs2a>".as_ptr(), 1);
462 if opts.is_null() {
463 JS_ReportErrorUTF8(cx, c"Bun.readableStreamToArray: compile failed".as_ptr());
464 return false;
465 }
466 let mut fn_val = UndefinedValue();
467 let fn_h = MutableHandle::<Value> {
468 _phantom_0: ::std::marker::PhantomData,
469 ptr: &mut fn_val,
470 };
471 let ok = mozjs_sys::jsapi::JS::Evaluate2(cx, opts, &mut text, fn_h);
472 libc::free(opts as *mut _);
473 if !ok || !fn_val.is_object() {
474 JS_ClearPendingException(cx);
475 JS_ReportErrorUTF8(cx, c"Bun.readableStreamToArray: compile failed".as_ptr());
476 return false;
477 }
478 rooted!(&in(cx_ref) let fn_obj = fn_val.to_object());
479 rooted!(&in(cx_ref) let fn_call_val = ObjectValue(fn_obj.get()));
480 rooted!(&in(cx_ref) let stream_arg = *args.get(0).ptr);
481 let call_vals = [stream_arg.handle().get()];
482 let call_arr = HandleValueArray {
483 length_: 1,
484 elements_: call_vals.as_ptr(),
485 };
486 rooted!(&in(cx_ref) let null_obj = ::std::ptr::null_mut::<JSObject>());
487 let mut rval = UndefinedValue();
488 let called = JS_CallFunctionValue(
489 cx,
490 null_obj.handle().into(),
491 fn_call_val.handle().into(),
492 &call_arr,
493 MutableHandle::<Value> {
494 _phantom_0: ::std::marker::PhantomData,
495 ptr: &mut rval,
496 },
497 );
498 if !called {
499 return false;
500 }
501 args.rval().set(rval);
502 true
503}
504
505#[allow(unsafe_op_in_unsafe_fn)]
510unsafe extern "C" fn bun_tcp_socket(cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
511 let args = CallArgs::from_vp(vp, _argc);
512 let _ = &args;
513 JS_ReportErrorUTF8(
514 cx,
515 c"Bun.tcpSocket is not implemented in Bao: use Bun.connect (TCP client) / Bun.listen (server) — the socket family is owned by the net domain (bun_listen.rs)".as_ptr(),
516 );
517 false
518}
519
520pub unsafe fn install(
529 cx: &mut mozjs::context::JSContext,
530 bun_obj: mozjs::rust::Handle<*mut JSObject>,
531) {
532 JS_DefineFunction(
533 cx,
534 bun_obj,
535 c"peek".as_ptr(),
536 Some(bun_peek),
537 1,
538 JSPROP_ENUMERATE as u32,
539 );
540 JS_DefineFunction(
541 cx,
542 bun_obj,
543 c"peekStatus".as_ptr(),
544 Some(bun_peek_status),
545 1,
546 JSPROP_ENUMERATE as u32,
547 );
548 JS_DefineFunction(
549 cx,
550 bun_obj,
551 c"stringWidth".as_ptr(),
552 Some(bun_string_width),
553 2,
554 JSPROP_ENUMERATE as u32,
555 );
556 JS_DefineFunction(
557 cx,
558 bun_obj,
559 c"readableStreamToArray".as_ptr(),
560 Some(readable_stream_to_array),
561 1,
562 JSPROP_ENUMERATE as u32,
563 );
564 JS_DefineFunction(
565 cx,
566 bun_obj,
567 c"tcpSocket".as_ptr(),
568 Some(bun_tcp_socket),
569 0,
570 JSPROP_ENUMERATE as u32,
571 );
572
573 rooted!(&in(cx) let re_ns = JS_NewPlainObject(cx));
575 if !re_ns.get().is_null() {
576 JS_DefineFunction(
577 cx,
578 re_ns.handle(),
579 c"escape".as_ptr(),
580 Some(regexp_escape),
581 1,
582 JSPROP_ENUMERATE as u32,
583 );
584 JS_DefineProperty3(
585 cx,
586 bun_obj,
587 c"RegExp".as_ptr(),
588 re_ns.handle(),
589 JSPROP_ENUMERATE as u32,
590 );
591 }
592}