script/dom/
console.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::convert::TryFrom;
6use std::ptr::{self, NonNull};
7use std::slice;
8
9use devtools_traits::{
10    ConsoleArgument, ConsoleArgumentObject, ConsoleArgumentPropertyValue, ConsoleLogLevel,
11    ConsoleMessage, ConsoleMessageFields, ScriptToDevtoolsControlMsg, StackFrame, get_time_stamp,
12};
13use embedder_traits::EmbedderMsg;
14use js::conversions::jsstr_to_string;
15use js::jsapi::{self, ESClass, JS_IsTypedArrayObject, PropertyDescriptor};
16use js::jsval::{Int32Value, UndefinedValue};
17use js::rust::wrappers::{
18    GetBuiltinClass, GetPropertyKeys, IsArray, JS_GetOwnPropertyDescriptorById, JS_GetPropertyById,
19    JS_IdToValue, JS_Stringify, JS_ValueToSource,
20};
21use js::rust::{
22    CapturedJSStack, HandleObject, HandleValue, IdVector, ToString, describe_scripted_caller,
23};
24use script_bindings::conversions::get_dom_class;
25
26use crate::dom::bindings::codegen::Bindings::ConsoleBinding::consoleMethods;
27use crate::dom::bindings::inheritance::Castable;
28use crate::dom::bindings::str::DOMString;
29use crate::dom::globalscope::GlobalScope;
30use crate::dom::workerglobalscope::WorkerGlobalScope;
31use crate::script_runtime::JSContext;
32
33/// The maximum object depth logged by console methods.
34const MAX_LOG_DEPTH: usize = 10;
35/// The maximum elements in an object logged by console methods.
36const MAX_LOG_CHILDREN: usize = 15;
37
38/// <https://developer.mozilla.org/en-US/docs/Web/API/Console>
39#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
40pub(crate) struct Console;
41
42impl Console {
43    #[expect(unsafe_code)]
44    fn build_message(
45        level: ConsoleLogLevel,
46        arguments: Vec<ConsoleArgument>,
47        stacktrace: Option<Vec<StackFrame>>,
48    ) -> ConsoleMessage {
49        let cx = GlobalScope::get_cx();
50        let caller = unsafe { describe_scripted_caller(*cx) }.unwrap_or_default();
51
52        ConsoleMessage {
53            fields: ConsoleMessageFields {
54                level,
55                filename: caller.filename,
56                line_number: caller.line,
57                column_number: caller.col,
58                time_stamp: get_time_stamp(),
59            },
60            arguments,
61            stacktrace,
62        }
63    }
64
65    /// Helper to send a message that only consists of a single string
66    fn send_string_message(global: &GlobalScope, level: ConsoleLogLevel, message: String) {
67        let prefix = global.current_group_label().unwrap_or_default();
68        let formatted_message = format!("{prefix}{message}");
69
70        Self::send_to_embedder(global, level.clone(), formatted_message);
71
72        let console_message = Self::build_message(level, vec![message.into()], None);
73
74        Self::send_to_devtools(global, console_message);
75    }
76
77    fn method(
78        global: &GlobalScope,
79        level: ConsoleLogLevel,
80        messages: Vec<HandleValue>,
81        include_stacktrace: IncludeStackTrace,
82    ) {
83        let cx = GlobalScope::get_cx();
84
85        let arguments = messages
86            .iter()
87            .map(|msg| console_argument_from_handle_value(cx, *msg, &mut Vec::new()))
88            .collect();
89        let stacktrace = (include_stacktrace == IncludeStackTrace::Yes)
90            .then_some(get_js_stack(*GlobalScope::get_cx()));
91        let console_message = Self::build_message(level.clone(), arguments, stacktrace);
92
93        Console::send_to_devtools(global, console_message);
94
95        let prefix = global.current_group_label().unwrap_or_default();
96        let msgs = stringify_handle_values(&messages);
97        let formatted_message = format!("{prefix}{msgs}");
98
99        Self::send_to_embedder(global, level, formatted_message);
100    }
101
102    fn send_to_devtools(global: &GlobalScope, message: ConsoleMessage) {
103        if let Some(chan) = global.devtools_chan() {
104            let worker_id = global
105                .downcast::<WorkerGlobalScope>()
106                .map(|worker| worker.worker_id());
107            let devtools_message =
108                ScriptToDevtoolsControlMsg::ConsoleAPI(global.pipeline_id(), message, worker_id);
109            chan.send(devtools_message).unwrap();
110        }
111    }
112
113    fn send_to_embedder(global: &GlobalScope, level: ConsoleLogLevel, message: String) {
114        global.send_to_embedder(EmbedderMsg::ShowConsoleApiMessage(
115            global.webview_id(),
116            level,
117            message,
118        ));
119    }
120
121    // Directly logs a DOMString, without processing the message
122    pub(crate) fn internal_warn(global: &GlobalScope, message: DOMString) {
123        Console::send_string_message(global, ConsoleLogLevel::Warn, String::from(message.clone()));
124    }
125}
126
127#[expect(unsafe_code)]
128unsafe fn handle_value_to_string(cx: *mut jsapi::JSContext, value: HandleValue) -> DOMString {
129    rooted!(in(cx) let mut js_string = std::ptr::null_mut::<jsapi::JSString>());
130    match std::ptr::NonNull::new(unsafe { JS_ValueToSource(cx, value) }) {
131        Some(js_str) => {
132            js_string.set(js_str.as_ptr());
133            DOMString::from_string(unsafe { jsstr_to_string(cx, js_str) })
134        },
135        None => "<error converting value to string>".into(),
136    }
137}
138
139#[expect(unsafe_code)]
140fn console_argument_from_handle_value(
141    cx: JSContext,
142    handle_value: HandleValue,
143    seen: &mut Vec<u64>,
144) -> ConsoleArgument {
145    if handle_value.is_string() {
146        let js_string = ptr::NonNull::new(handle_value.to_string()).unwrap();
147        let dom_string = unsafe { jsstr_to_string(*cx, js_string) };
148        return ConsoleArgument::String(dom_string);
149    }
150
151    if handle_value.is_int32() {
152        let integer = handle_value.to_int32();
153        return ConsoleArgument::Integer(integer);
154    }
155
156    if handle_value.is_number() {
157        let number = handle_value.to_number();
158        return ConsoleArgument::Number(number);
159    }
160
161    if handle_value.is_boolean() {
162        let boolean = handle_value.to_boolean();
163        return ConsoleArgument::Boolean(boolean);
164    }
165
166    if handle_value.is_object() {
167        // JS objects can create circular reference, and we want to avoid recursing infinitely
168        if seen.contains(&handle_value.asBits_) {
169            // FIXME: Handle this properly
170            return ConsoleArgument::String("[circular]".into());
171        }
172
173        seen.push(handle_value.asBits_);
174        let maybe_argument_object = console_object_from_handle_value(cx, handle_value, seen);
175        let js_value = seen.pop();
176        debug_assert_eq!(js_value, Some(handle_value.asBits_));
177
178        if let Some(console_argument_object) = maybe_argument_object {
179            return ConsoleArgument::Object(console_argument_object);
180        }
181    }
182
183    // FIXME: Handle more complex argument types here
184    let stringified_value = stringify_handle_value(handle_value);
185
186    ConsoleArgument::String(stringified_value.into())
187}
188
189#[expect(unsafe_code)]
190fn console_object_from_handle_value(
191    cx: JSContext,
192    handle_value: HandleValue,
193    seen: &mut Vec<u64>,
194) -> Option<ConsoleArgumentObject> {
195    rooted!(in(*cx) let object = handle_value.to_object());
196
197    // We should not generate object previews for arrays, although they are objects
198    let mut is_array = false;
199    if !unsafe { IsArray(*cx, object.handle(), &mut is_array) } || is_array {
200        return None;
201    }
202    if unsafe { JS_IsTypedArrayObject(object.get()) } {
203        return None;
204    }
205
206    let mut own_properties = Vec::new();
207    let mut ids = unsafe { IdVector::new(*cx) };
208    if !unsafe {
209        GetPropertyKeys(
210            *cx,
211            object.handle(),
212            jsapi::JSITER_OWNONLY | jsapi::JSITER_SYMBOLS | jsapi::JSITER_HIDDEN,
213            ids.handle_mut(),
214        )
215    } {
216        return None;
217    }
218
219    for id in ids.iter() {
220        rooted!(in(*cx) let id = *id);
221        rooted!(in(*cx) let mut descriptor = PropertyDescriptor::default());
222
223        let mut is_none = false;
224        if !unsafe {
225            JS_GetOwnPropertyDescriptorById(
226                *cx,
227                object.handle(),
228                id.handle(),
229                descriptor.handle_mut(),
230                &mut is_none,
231            )
232        } {
233            return None;
234        }
235
236        rooted!(in(*cx) let mut property = UndefinedValue());
237        if !unsafe { JS_GetPropertyById(*cx, object.handle(), id.handle(), property.handle_mut()) }
238        {
239            return None;
240        }
241
242        let key = if id.is_string() {
243            rooted!(in(*cx) let mut key_value = UndefinedValue());
244            let raw_id: jsapi::HandleId = id.handle().into();
245            if !unsafe { JS_IdToValue(*cx, *raw_id.ptr, key_value.handle_mut()) } {
246                continue;
247            }
248            rooted!(in(*cx) let js_string = key_value.to_string());
249            let Some(js_string) = NonNull::new(js_string.get()) else {
250                continue;
251            };
252            unsafe { jsstr_to_string(*cx, js_string) }
253        } else {
254            continue;
255        };
256
257        own_properties.push(ConsoleArgumentPropertyValue {
258            key,
259            configurable: descriptor.hasConfigurable_() && descriptor.configurable_(),
260            enumerable: descriptor.hasEnumerable_() && descriptor.enumerable_(),
261            writable: descriptor.hasWritable_() && descriptor.writable_(),
262            value: console_argument_from_handle_value(cx, property.handle(), seen),
263        });
264    }
265
266    Some(ConsoleArgumentObject {
267        class: "Object".to_owned(),
268        own_properties,
269    })
270}
271
272#[expect(unsafe_code)]
273fn stringify_handle_value(message: HandleValue) -> DOMString {
274    let cx = GlobalScope::get_cx();
275    unsafe {
276        if message.is_string() {
277            let jsstr = std::ptr::NonNull::new(message.to_string()).unwrap();
278            return DOMString::from_string(jsstr_to_string(*cx, jsstr));
279        }
280        unsafe fn stringify_object_from_handle_value(
281            cx: *mut jsapi::JSContext,
282            value: HandleValue,
283            parents: Vec<u64>,
284        ) -> DOMString {
285            rooted!(in(cx) let mut obj = value.to_object());
286            let mut object_class = ESClass::Other;
287            if !unsafe { GetBuiltinClass(cx, obj.handle(), &mut object_class as *mut _) } {
288                return DOMString::from("/* invalid */");
289            }
290            let mut ids = unsafe { IdVector::new(cx) };
291            if !unsafe {
292                GetPropertyKeys(
293                    cx,
294                    obj.handle(),
295                    jsapi::JSITER_OWNONLY | jsapi::JSITER_SYMBOLS,
296                    ids.handle_mut(),
297                )
298            } {
299                return DOMString::from("/* invalid */");
300            }
301            let truncate = ids.len() > MAX_LOG_CHILDREN;
302            if object_class != ESClass::Array && object_class != ESClass::Object {
303                if truncate {
304                    return DOMString::from("…");
305                } else {
306                    return unsafe { handle_value_to_string(cx, value) };
307                }
308            }
309
310            let mut explicit_keys = object_class == ESClass::Object;
311            let mut props = Vec::with_capacity(ids.len());
312            for id in ids.iter().take(MAX_LOG_CHILDREN) {
313                rooted!(in(cx) let id = *id);
314                rooted!(in(cx) let mut desc = PropertyDescriptor::default());
315
316                let mut is_none = false;
317                if !unsafe {
318                    JS_GetOwnPropertyDescriptorById(
319                        cx,
320                        obj.handle(),
321                        id.handle(),
322                        desc.handle_mut(),
323                        &mut is_none,
324                    )
325                } {
326                    return DOMString::from("/* invalid */");
327                }
328
329                rooted!(in(cx) let mut property = UndefinedValue());
330                if !unsafe {
331                    JS_GetPropertyById(cx, obj.handle(), id.handle(), property.handle_mut())
332                } {
333                    return DOMString::from("/* invalid */");
334                }
335
336                if !explicit_keys {
337                    if id.is_int() {
338                        if let Ok(id_int) = usize::try_from(id.to_int()) {
339                            explicit_keys = props.len() != id_int;
340                        } else {
341                            explicit_keys = false;
342                        }
343                    } else {
344                        explicit_keys = false;
345                    }
346                }
347                let value_string = stringify_inner(
348                    unsafe { JSContext::from_ptr(cx) },
349                    property.handle(),
350                    parents.clone(),
351                );
352                if explicit_keys {
353                    let key = if id.is_string() || id.is_symbol() || id.is_int() {
354                        rooted!(in(cx) let mut key_value = UndefinedValue());
355                        let raw_id: jsapi::HandleId = id.handle().into();
356                        if !unsafe { JS_IdToValue(cx, *raw_id.ptr, key_value.handle_mut()) } {
357                            return DOMString::from("/* invalid */");
358                        }
359                        unsafe { handle_value_to_string(cx, key_value.handle()) }
360                    } else {
361                        return DOMString::from("/* invalid */");
362                    };
363                    props.push(format!("{}: {}", key, value_string,));
364                } else {
365                    props.push(value_string.to_string());
366                }
367            }
368            if truncate {
369                props.push("…".to_string());
370            }
371            if object_class == ESClass::Array {
372                DOMString::from(format!("[{}]", itertools::join(props, ", ")))
373            } else {
374                DOMString::from(format!("{{{}}}", itertools::join(props, ", ")))
375            }
376        }
377        fn stringify_inner(cx: JSContext, value: HandleValue, mut parents: Vec<u64>) -> DOMString {
378            if parents.len() >= MAX_LOG_DEPTH {
379                return DOMString::from("...");
380            }
381            let value_bits = value.asBits_;
382            if parents.contains(&value_bits) {
383                return DOMString::from("[circular]");
384            }
385            if value.is_undefined() {
386                // This produces a better value than "(void 0)" from JS_ValueToSource.
387                return DOMString::from("undefined");
388            } else if !value.is_object() {
389                return unsafe { handle_value_to_string(*cx, value) };
390            }
391            parents.push(value_bits);
392
393            if value.is_object() {
394                if let Some(repr) = maybe_stringify_dom_object(cx, value) {
395                    return repr;
396                }
397            }
398            unsafe { stringify_object_from_handle_value(*cx, value, parents) }
399        }
400        stringify_inner(cx, message, Vec::new())
401    }
402}
403
404#[expect(unsafe_code)]
405fn maybe_stringify_dom_object(cx: JSContext, value: HandleValue) -> Option<DOMString> {
406    // The standard object serialization is not effective for DOM objects,
407    // since their properties generally live on the prototype object.
408    // Instead, fall back to the output of JSON.stringify combined
409    // with the class name extracted from the output of toString().
410    rooted!(in(*cx) let obj = value.to_object());
411    let is_dom_class = unsafe { get_dom_class(obj.get()).is_ok() };
412    if !is_dom_class {
413        return None;
414    }
415    rooted!(in(*cx) let class_name = unsafe { ToString(*cx, value) });
416    let Some(class_name) = NonNull::new(class_name.get()) else {
417        return Some("<error converting DOM object to string>".into());
418    };
419    let class_name = unsafe {
420        jsstr_to_string(*cx, class_name)
421            .replace("[object ", "")
422            .replace("]", "")
423    };
424    let mut repr = format!("{} ", class_name);
425    rooted!(in(*cx) let mut value = value.get());
426
427    #[expect(unsafe_code)]
428    unsafe extern "C" fn stringified(
429        string: *const u16,
430        len: u32,
431        data: *mut std::ffi::c_void,
432    ) -> bool {
433        let s = data as *mut String;
434        let string_chars = unsafe { slice::from_raw_parts(string, len as usize) };
435        unsafe { (*s).push_str(&String::from_utf16_lossy(string_chars)) };
436        true
437    }
438
439    rooted!(in(*cx) let space = Int32Value(2));
440    let stringify_result = unsafe {
441        JS_Stringify(
442            *cx,
443            value.handle_mut(),
444            HandleObject::null(),
445            space.handle(),
446            Some(stringified),
447            &mut repr as *mut String as *mut _,
448        )
449    };
450    if !stringify_result {
451        return Some("<error converting DOM object to string>".into());
452    }
453    Some(repr.into())
454}
455
456fn stringify_handle_values(messages: &[HandleValue]) -> DOMString {
457    DOMString::from(itertools::join(
458        messages.iter().copied().map(stringify_handle_value),
459        " ",
460    ))
461}
462
463#[derive(Debug, Eq, PartialEq)]
464enum IncludeStackTrace {
465    Yes,
466    No,
467}
468
469impl consoleMethods<crate::DomTypeHolder> for Console {
470    /// <https://developer.mozilla.org/en-US/docs/Web/API/Console/log>
471    fn Log(_cx: JSContext, global: &GlobalScope, messages: Vec<HandleValue>) {
472        Console::method(
473            global,
474            ConsoleLogLevel::Log,
475            messages,
476            IncludeStackTrace::No,
477        );
478    }
479
480    /// <https://developer.mozilla.org/en-US/docs/Web/API/Console/clear>
481    fn Clear(global: &GlobalScope) {
482        if let Some(chan) = global.devtools_chan() {
483            let worker_id = global
484                .downcast::<WorkerGlobalScope>()
485                .map(|worker| worker.worker_id());
486            let devtools_message =
487                ScriptToDevtoolsControlMsg::ClearConsole(global.pipeline_id(), worker_id);
488            if let Err(error) = chan.send(devtools_message) {
489                log::warn!("Error sending clear message to devtools: {error:?}");
490            }
491        }
492    }
493
494    /// <https://developer.mozilla.org/en-US/docs/Web/API/Console>
495    fn Debug(_cx: JSContext, global: &GlobalScope, messages: Vec<HandleValue>) {
496        Console::method(
497            global,
498            ConsoleLogLevel::Debug,
499            messages,
500            IncludeStackTrace::No,
501        );
502    }
503
504    /// <https://developer.mozilla.org/en-US/docs/Web/API/Console/info>
505    fn Info(_cx: JSContext, global: &GlobalScope, messages: Vec<HandleValue>) {
506        Console::method(
507            global,
508            ConsoleLogLevel::Info,
509            messages,
510            IncludeStackTrace::No,
511        );
512    }
513
514    /// <https://developer.mozilla.org/en-US/docs/Web/API/Console/warn>
515    fn Warn(_cx: JSContext, global: &GlobalScope, messages: Vec<HandleValue>) {
516        Console::method(
517            global,
518            ConsoleLogLevel::Warn,
519            messages,
520            IncludeStackTrace::No,
521        );
522    }
523
524    /// <https://developer.mozilla.org/en-US/docs/Web/API/Console/error>
525    fn Error(_cx: JSContext, global: &GlobalScope, messages: Vec<HandleValue>) {
526        Console::method(
527            global,
528            ConsoleLogLevel::Error,
529            messages,
530            IncludeStackTrace::No,
531        );
532    }
533
534    /// <https://console.spec.whatwg.org/#trace>
535    fn Trace(_cx: JSContext, global: &GlobalScope, messages: Vec<HandleValue>) {
536        Console::method(
537            global,
538            ConsoleLogLevel::Trace,
539            messages,
540            IncludeStackTrace::Yes,
541        );
542    }
543
544    /// <https://developer.mozilla.org/en-US/docs/Web/API/Console/assert>
545    fn Assert(_cx: JSContext, global: &GlobalScope, condition: bool, messages: Vec<HandleValue>) {
546        if !condition {
547            let message = format!("Assertion failed: {}", stringify_handle_values(&messages));
548
549            Console::send_string_message(global, ConsoleLogLevel::Log, message.clone());
550        }
551    }
552
553    /// <https://console.spec.whatwg.org/#time>
554    fn Time(global: &GlobalScope, label: DOMString) {
555        if let Ok(()) = global.time(label.clone()) {
556            let message = format!("{label}: timer started");
557            Console::send_string_message(global, ConsoleLogLevel::Log, message.clone());
558        }
559    }
560
561    /// <https://console.spec.whatwg.org/#timelog>
562    fn TimeLog(_cx: JSContext, global: &GlobalScope, label: DOMString, data: Vec<HandleValue>) {
563        if let Ok(delta) = global.time_log(&label) {
564            let message = format!("{label}: {delta}ms {}", stringify_handle_values(&data));
565
566            Console::send_string_message(global, ConsoleLogLevel::Log, message.clone());
567        }
568    }
569
570    /// <https://console.spec.whatwg.org/#timeend>
571    fn TimeEnd(global: &GlobalScope, label: DOMString) {
572        if let Ok(delta) = global.time_end(&label) {
573            let message = format!("{label}: {delta}ms");
574
575            Console::send_string_message(global, ConsoleLogLevel::Log, message.clone());
576        }
577    }
578
579    /// <https://console.spec.whatwg.org/#group>
580    fn Group(_cx: JSContext, global: &GlobalScope, messages: Vec<HandleValue>) {
581        global.push_console_group(stringify_handle_values(&messages));
582    }
583
584    /// <https://console.spec.whatwg.org/#groupcollapsed>
585    fn GroupCollapsed(_cx: JSContext, global: &GlobalScope, messages: Vec<HandleValue>) {
586        global.push_console_group(stringify_handle_values(&messages));
587    }
588
589    /// <https://console.spec.whatwg.org/#groupend>
590    fn GroupEnd(global: &GlobalScope) {
591        global.pop_console_group();
592    }
593
594    /// <https://console.spec.whatwg.org/#count>
595    fn Count(global: &GlobalScope, label: DOMString) {
596        let count = global.increment_console_count(&label);
597        let message = format!("{label}: {count}");
598
599        Console::send_string_message(global, ConsoleLogLevel::Log, message.clone());
600    }
601
602    /// <https://console.spec.whatwg.org/#countreset>
603    fn CountReset(global: &GlobalScope, label: DOMString) {
604        if global.reset_console_count(&label).is_err() {
605            Self::internal_warn(
606                global,
607                DOMString::from(format!("Counter “{label}” doesn’t exist.")),
608            )
609        }
610    }
611}
612
613#[expect(unsafe_code)]
614fn get_js_stack(cx: *mut jsapi::JSContext) -> Vec<StackFrame> {
615    const MAX_FRAME_COUNT: u32 = 128;
616
617    let mut frames = vec![];
618    rooted!(in(cx) let mut handle =  ptr::null_mut());
619    let captured_js_stack = unsafe { CapturedJSStack::new(cx, handle, Some(MAX_FRAME_COUNT)) };
620    let Some(captured_js_stack) = captured_js_stack else {
621        return frames;
622    };
623
624    captured_js_stack.for_each_stack_frame(|frame| {
625        rooted!(in(cx) let mut result: *mut jsapi::JSString = ptr::null_mut());
626
627        // Get function name
628        unsafe {
629            jsapi::GetSavedFrameFunctionDisplayName(
630                cx,
631                ptr::null_mut(),
632                frame.into(),
633                result.handle_mut().into(),
634                jsapi::SavedFrameSelfHosted::Include,
635            );
636        }
637        let function_name = if let Some(nonnull_result) = ptr::NonNull::new(*result) {
638            unsafe { jsstr_to_string(cx, nonnull_result) }
639        } else {
640            "<anonymous>".into()
641        };
642
643        // Get source file name
644        result.set(ptr::null_mut());
645        unsafe {
646            jsapi::GetSavedFrameSource(
647                cx,
648                ptr::null_mut(),
649                frame.into(),
650                result.handle_mut().into(),
651                jsapi::SavedFrameSelfHosted::Include,
652            );
653        }
654        let filename = if let Some(nonnull_result) = ptr::NonNull::new(*result) {
655            unsafe { jsstr_to_string(cx, nonnull_result) }
656        } else {
657            "<anonymous>".into()
658        };
659
660        // get line/column number
661        let mut line_number = 0;
662        unsafe {
663            jsapi::GetSavedFrameLine(
664                cx,
665                ptr::null_mut(),
666                frame.into(),
667                &mut line_number,
668                jsapi::SavedFrameSelfHosted::Include,
669            );
670        }
671
672        let mut column_number = jsapi::JS::TaggedColumnNumberOneOrigin { value_: 0 };
673        unsafe {
674            jsapi::GetSavedFrameColumn(
675                cx,
676                ptr::null_mut(),
677                frame.into(),
678                &mut column_number,
679                jsapi::SavedFrameSelfHosted::Include,
680            );
681        }
682        let frame = StackFrame {
683            filename,
684            function_name,
685            line_number,
686            column_number: column_number.value_,
687        };
688
689        frames.push(frame);
690    });
691
692    frames
693}