Skip to main content

script_bindings/
js.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::ffi::CStr;
6use std::os::raw::c_uint;
7use std::ptr;
8
9use bitflags::bitflags;
10use js::context::JSContext;
11use js::jsapi::{
12    ExceptionStackBehavior, JSClass, JSObject, JSPROP_ENUMERATE, JSPROP_PERMANENT, JSPROP_READONLY,
13    MutableHandleIdVector, PropertyDescriptor, jsid,
14};
15use js::rust::{
16    HandleId, HandleObject, HandleValue, MutableHandle, MutableHandleObject, MutableHandleValue,
17};
18
19bitflags! {
20    /// Flags for property definitions in JavaScript.
21    #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
22    pub struct PropertyFlags: u8 {
23        /// The property shows up when enumerating properties on the object.
24        const ENUMERABLE = 1 << 0;
25        /// The property can be changed.
26        const WRITABLE = 1 << 1;
27        /// The property can be deleted or have its attributes changed.
28        const CONFIGURABLE = 1 << 2;
29    }
30}
31
32impl PropertyFlags {
33    /// Convert the flags to the underlying SpiderMonkey `JSPROP_*` attributes.
34    pub fn as_jsprop_flags(&self) -> u32 {
35        let mut attrs: u32 = 0;
36        if self.contains(Self::ENUMERABLE) {
37            attrs |= JSPROP_ENUMERATE as u32;
38        }
39        if !self.contains(Self::CONFIGURABLE) {
40            attrs |= JSPROP_PERMANENT as u32;
41        }
42        if !self.contains(Self::WRITABLE) {
43            attrs |= JSPROP_READONLY as u32;
44        }
45        attrs
46    }
47}
48
49/// Safe wrapper around [`js::rust::wrappers2::JS_DefineProperty`].
50#[allow(non_snake_case)]
51#[inline]
52pub fn JS_DefineProperty(
53    cx: &mut JSContext,
54    obj: HandleObject,
55    name: &CStr,
56    value: HandleValue,
57    flags: PropertyFlags,
58) -> bool {
59    let attrs = flags.as_jsprop_flags();
60    unsafe { js::rust::wrappers2::JS_DefineProperty(cx, obj, name.as_ptr(), value, attrs) }
61}
62
63/// Safe wrapper around [`js::rust::wrappers2::JS_SetPendingException`].
64#[allow(non_snake_case)]
65#[inline]
66pub fn JS_SetPendingException(
67    cx: &mut JSContext,
68    value: HandleValue,
69    behavior: ExceptionStackBehavior,
70) {
71    unsafe { js::rust::wrappers2::JS_SetPendingException(cx, value, behavior) }
72}
73
74/// Safe wrapper around [`js::rust::wrappers2::JS_IsExceptionPending`].
75#[allow(non_snake_case)]
76#[inline]
77pub fn JS_IsExceptionPending(cx: &mut JSContext) -> bool {
78    unsafe { js::rust::wrappers2::JS_IsExceptionPending(cx) }
79}
80
81/// Safe wrapper around [`js::rust::wrappers2::JS_IdToValue`].
82#[allow(non_snake_case)]
83#[inline]
84pub fn JS_IdToValue(cx: &mut JSContext, id: jsid, vp: MutableHandleValue) -> bool {
85    unsafe { js::rust::wrappers2::JS_IdToValue(cx, id, vp) }
86}
87
88/// Safe wrapper around [`js::rust::wrappers2::JS_GetPropertyById`].
89#[allow(non_snake_case)]
90#[inline]
91pub fn JS_GetPropertyById(
92    cx: &mut JSContext,
93    obj: HandleObject,
94    id: HandleId,
95    vp: MutableHandleValue,
96) -> bool {
97    unsafe { js::rust::wrappers2::JS_GetPropertyById(cx, obj, id, vp) }
98}
99
100/// Safe wrapper around [`js::rust::wrappers2::JS_NewObject`].
101/// Passing `None` for `clasp` creates a plain object.
102#[allow(non_snake_case)]
103#[inline]
104pub fn JS_NewObject(cx: &mut JSContext, clasp: Option<&'static JSClass>) -> *mut JSObject {
105    let clasp = clasp.map_or(ptr::null(), |clasp| clasp as *const JSClass);
106    unsafe { js::rust::wrappers2::JS_NewObject(cx, clasp) }
107}
108
109/// Safe wrapper around [`js::rust::wrappers2::JS_NewObjectWithGivenProto`].
110/// Passing `None` for `clasp` creates a plain object.
111#[allow(non_snake_case)]
112#[inline]
113pub fn JS_NewObjectWithGivenProto(
114    cx: &mut JSContext,
115    clasp: Option<&'static JSClass>,
116    proto: HandleObject,
117) -> *mut JSObject {
118    let clasp = clasp.map_or(ptr::null(), |clasp| clasp as *const JSClass);
119    unsafe { js::rust::wrappers2::JS_NewObjectWithGivenProto(cx, clasp, proto) }
120}
121
122/// Safe wrapper around [`js::rust::wrappers2::JS_GetProperty`].
123#[allow(non_snake_case)]
124#[inline]
125pub fn JS_GetProperty(
126    cx: &mut JSContext,
127    obj: HandleObject,
128    name: &CStr,
129    vp: MutableHandleValue,
130) -> bool {
131    unsafe { js::rust::wrappers2::JS_GetProperty(cx, obj, name.as_ptr(), vp) }
132}
133
134/// Safe wrapper around [`js::rust::wrappers2::JS_GetPrototype`].
135#[allow(non_snake_case)]
136#[inline]
137pub fn JS_GetPrototype(cx: &mut JSContext, obj: HandleObject, result: MutableHandleObject) -> bool {
138    unsafe { js::rust::wrappers2::JS_GetPrototype(cx, obj, result) }
139}
140
141/// Safe wrapper around [`js::rust::wrappers2::GetPropertyKeys`].
142#[allow(non_snake_case)]
143#[inline]
144pub fn GetPropertyKeys(
145    cx: &mut JSContext,
146    obj: HandleObject,
147    flags: c_uint,
148    props: MutableHandleIdVector,
149) -> bool {
150    unsafe { js::rust::wrappers2::GetPropertyKeys(cx, obj, flags, props) }
151}
152
153/// Safe wrapper around [`js::rust::wrappers2::JS_GetOwnPropertyDescriptorById`].
154#[allow(non_snake_case)]
155#[inline]
156pub fn JS_GetOwnPropertyDescriptorById(
157    cx: &mut JSContext,
158    obj: HandleObject,
159    id: HandleId,
160    desc: MutableHandle<PropertyDescriptor>,
161    is_none: &mut bool,
162) -> bool {
163    unsafe { js::rust::wrappers2::JS_GetOwnPropertyDescriptorById(cx, obj, id, desc, is_none) }
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169
170    #[test]
171    fn test_property_flags_mapping() {
172        // Enumerable, Writable, Configurable -> only JSPROP_ENUMERATE
173        let all = PropertyFlags::ENUMERABLE | PropertyFlags::WRITABLE | PropertyFlags::CONFIGURABLE;
174        assert_eq!(all.as_jsprop_flags(), JSPROP_ENUMERATE as u32);
175
176        // Constant property: Enumerable, but not Writable or Configurable
177        // -> JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT
178        let constant = PropertyFlags::ENUMERABLE;
179        assert_eq!(
180            constant.as_jsprop_flags(),
181            (JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT) as u32
182        );
183
184        // Non-enumerable, non-writable, non-configurable (empty)
185        // -> JSPROP_READONLY | JSPROP_PERMANENT
186        let empty = PropertyFlags::empty();
187        assert_eq!(
188            empty.as_jsprop_flags(),
189            (JSPROP_READONLY | JSPROP_PERMANENT) as u32
190        );
191
192        // Writable and Configurable, not Enumerable
193        // -> 0
194        let method = PropertyFlags::WRITABLE | PropertyFlags::CONFIGURABLE;
195        assert_eq!(method.as_jsprop_flags(), 0);
196    }
197}