Skip to main content

ferrijs_std/buffer/
mod.rs

1//! Vendored `llrt_buffer`: `Buffer`, `Blob` and `File`.
2
3// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
4// SPDX-License-Identifier: Apache-2.0
5use crate::utils::{
6    module::{export_default, ModuleInfo},
7    object::define_subclass,
8    primordials::{BasePrimordials, Primordial},
9};
10use rquickjs::{
11    function::{Args, Constructor, Rest},
12    module::{Declarations, Exports, ModuleDef},
13    Ctx, Function, IntoJs, Object, Result, Value,
14};
15
16pub use self::array_buffer_view::*;
17pub use self::blob::*;
18pub use self::class::*;
19pub use self::file::*;
20
21mod array_buffer_view;
22mod blob;
23mod class;
24mod file;
25
26pub struct BufferModule;
27
28impl ModuleDef for BufferModule {
29    fn declare(declare: &Declarations) -> Result<()> {
30        declare.declare(stringify!(Buffer))?;
31        declare.declare("atob")?;
32        declare.declare("btoa")?;
33        declare.declare("constants")?;
34        declare.declare("default")?;
35        Ok(())
36    }
37
38    fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> {
39        let globals = ctx.globals();
40        let buf: Constructor = globals.get(stringify!(Buffer))?;
41
42        let constants = Object::new(ctx.clone())?;
43        constants.set("MAX_LENGTH", u32::MAX)?; // For QuickJS
44        constants.set("MAX_STRING_LENGTH", (1 << 30) - 1)?; // For QuickJS
45
46        let atob: Function = ctx.globals().get("atob")?;
47        let btoa: Function = ctx.globals().get("btoa")?;
48
49        export_default(ctx, exports, |default| {
50            default.set(stringify!(Buffer), buf)?;
51            default.set("atob", atob.into_js(ctx)?)?;
52            default.set("btoa", btoa.into_js(ctx)?)?;
53            default.set("constants", constants)?;
54            Ok(())
55        })?;
56
57        Ok(())
58    }
59}
60
61impl From<BufferModule> for ModuleInfo<BufferModule> {
62    fn from(val: BufferModule) -> Self {
63        ModuleInfo {
64            name: "buffer",
65            module: val,
66        }
67    }
68}
69
70pub fn init<'js>(ctx: &Ctx<'js>) -> Result<()> {
71    BasePrimordials::init(ctx)?;
72
73    // Buffer extends the native Uint8Array: it forwards construction to the
74    // Uint8Array constructor and inherits its static and prototype members.
75    let uint8array = BasePrimordials::get(ctx)?.constructor_uint8array.clone();
76    let buffer_ctor = define_subclass(
77        ctx,
78        stringify!(Buffer),
79        &uint8array,
80        |ctx: Ctx<'js>, args: Rest<Value<'js>>| {
81            let uint8array = &BasePrimordials::get(&ctx)?.constructor_uint8array;
82            let mut ctor_args = Args::new(ctx.clone(), args.0.len());
83            ctor_args.push_args(args.0)?;
84            ctor_args.construct::<Value>(uint8array)
85        },
86    )?;
87    let buffer: Object = buffer_ctor.into_value().into_object().unwrap();
88    set_prototype(ctx, buffer)?;
89
90    BufferPrimordials::init(ctx)?;
91
92    // Local delta: `equals` and `toJSON` are Node `Buffer` methods
93    // upstream does not define, and the implementation this vendoring
94    // replaced had both. Everything else on the prototype comes from
95    // upstream or from `Uint8Array`.
96    add_missing_prototype_methods(ctx)?;
97
98    // Blob
99    rquickjs::Class::<Blob>::define(&ctx.globals())?;
100
101    // File
102    rquickjs::Class::<File>::define(&ctx.globals())?;
103    // `File` extends `Blob` in the spec. rquickjs classes do not inherit,
104    // and upstream leaves the two unrelated, so `file instanceof Blob` is
105    // false and none of Blob's methods are reachable — the chain is wired
106    // here.
107    chain_file_to_blob(ctx)?;
108
109    //init primordials
110    let _ = BufferPrimordials::get(ctx)?;
111
112    Ok(())
113}
114
115/// Node `Buffer` prototype methods `llrt_buffer` does not define.
116fn add_missing_prototype_methods<'js>(ctx: &Ctx<'js>) -> Result<()> {
117    let buffer: Constructor<'js> = ctx.globals().get(stringify!(Buffer))?;
118    let prototype: Object<'js> = buffer.get(rquickjs::atom::PredefinedAtom::Prototype)?;
119
120    prototype.set(
121        "equals",
122        Function::new(ctx.clone(), |this: rquickjs::function::This<Object<'js>>, other: Object<'js>| -> Result<bool> {
123            let (a, b) = (bytes_of(&this.0)?, bytes_of(&other)?);
124            Ok(a == b)
125        })?,
126    )?;
127
128    prototype.set(
129        "toJSON",
130        Function::new(ctx.clone(), |ctx: Ctx<'js>, this: rquickjs::function::This<Object<'js>>| -> Result<Object<'js>> {
131            let json = Object::new(ctx.clone())?;
132            json.set("type", "Buffer")?;
133            json.set("data", bytes_of(&this.0)?)?;
134            Ok(json)
135        })?,
136    )?;
137
138    Ok(())
139}
140
141/// The bytes behind a `Buffer` (or any `Uint8Array` view).
142fn bytes_of<'js>(object: &Object<'js>) -> Result<Vec<u8>> {
143    match crate::utils::bytes::ObjectBytes::from_array_buffer(object)? {
144        Some(bytes) => Ok(bytes.as_bytes(object.ctx())?.to_vec()),
145        None => Ok(Vec::new()),
146    }
147}
148
149/// Point `File.prototype` at `Blob.prototype`, which is what makes a
150/// `File` an instance of `Blob`.
151fn chain_file_to_blob<'js>(ctx: &Ctx<'js>) -> Result<()> {
152    let blob_proto = rquickjs::Class::<Blob<'js>>::prototype(ctx)?;
153    let file_proto = rquickjs::Class::<File<'js>>::prototype(ctx)?;
154    if let (Some(blob_proto), Some(file_proto)) = (blob_proto, file_proto) {
155        file_proto.set_prototype(Some(&blob_proto))?;
156    }
157    Ok(())
158}