• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

supabase / edge-runtime / 15218527727

23 May 2025 08:20PM UTC coverage: 49.447% (-2.0%) from 51.44%
15218527727

Pull #543

github

web-flow
Merge 36d428d17 into a1625a929
Pull Request #543: feat: add technical docs

17167 of 34718 relevant lines covered (49.45%)

1637.91 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

77.15
/ext/node/global.rs
1
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
2

3
use deno_core::v8;
4
use deno_core::v8::GetPropertyNamesArgs;
5
use deno_core::v8::MapFnTo;
6

7
// NOTE(bartlomieju): somehow calling `.map_fn_to()` multiple times on a function
8
// returns two different pointers. That shouldn't be the case as `.map_fn_to()`
9
// creates a thin wrapper that is a pure function. @piscisaureus suggests it
10
// might be a bug in Rust compiler; so for now we just create and store
11
// these mapped functions per-thread. We should revisit it in the future and
12
// ideally remove altogether.
13
thread_local! {
14
  pub static GETTER_MAP_FN: v8::NamedPropertyGetterCallback<'static> = getter.map_fn_to();
15
  pub static SETTER_MAP_FN: v8::NamedPropertySetterCallback<'static> = setter.map_fn_to();
16
  pub static QUERY_MAP_FN: v8::NamedPropertyQueryCallback<'static> = query.map_fn_to();
17
  pub static DELETER_MAP_FN: v8::NamedPropertyDeleterCallback<'static> = deleter.map_fn_to();
18
  pub static ENUMERATOR_MAP_FN: v8::NamedPropertyEnumeratorCallback<'static> = enumerator.map_fn_to();
19
  pub static DEFINER_MAP_FN: v8::NamedPropertyDefinerCallback<'static> = definer.map_fn_to();
20
  pub static DESCRIPTOR_MAP_FN: v8::NamedPropertyGetterCallback<'static> = descriptor.map_fn_to();
21
}
22

23
/// Convert an ASCII string to a UTF-16 byte encoding of the string.
24
const fn str_to_utf16<const N: usize>(s: &str) -> [u16; N] {
×
25
  let mut out = [0_u16; N];
×
26
  let mut i = 0;
×
27
  let bytes = s.as_bytes();
×
28
  assert!(N == bytes.len());
×
29
  while i < bytes.len() {
×
30
    assert!(bytes[i] < 128, "only works for ASCII strings");
×
31
    out[i] = bytes[i] as u16;
×
32
    i += 1;
×
33
  }
34
  out
×
35
}
×
36

37
// ext/node changes the global object to be a proxy object that intercepts all
38
// property accesses for globals that are different between Node and Deno and
39
// dynamically returns a different value depending on if the accessing code is
40
// in node_modules/ or not.
41
//
42
// To make this performant, a v8 named property handler is used, that only
43
// intercepts property accesses for properties that are not already present on
44
// the global object (it is non-masking). This means that in the common case,
45
// when a user accesses a global that is the same between Node and Deno (like
46
// Uint8Array or fetch), the proxy overhead is avoided.
47
//
48
// The Deno and Node specific globals are stored in a struct in a context slot.
49
//
50
// These are the globals that are handled:
51
// - Buffer (node only)
52
// - clearImmediate (node only)
53
// - clearInterval (both, but different implementation)
54
// - clearTimeout (both, but different implementation)
55
// - global (node only)
56
// - performance (both, but different implementation)
57
// - setImmediate (node only)
58
// - setInterval (both, but different implementation)
59
// - setTimeout (both, but different implementation)
60
// - window (deno only)
61

62
// UTF-16 encodings of the managed globals. THIS LIST MUST BE SORTED.
63
#[rustfmt::skip]
64
const MANAGED_GLOBALS: [&[u16]; 12] = [
65
  &str_to_utf16::<6>("Buffer"),
66
  &str_to_utf16::<17>("WorkerGlobalScope"),
67
  &str_to_utf16::<14>("clearImmediate"),
68
  &str_to_utf16::<13>("clearInterval"),
69
  &str_to_utf16::<12>("clearTimeout"),
70
  &str_to_utf16::<6>("global"),
71
  &str_to_utf16::<11>("performance"),
72
  &str_to_utf16::<4>("self"),
73
  &str_to_utf16::<12>("setImmediate"),
74
  &str_to_utf16::<11>("setInterval"),
75
  &str_to_utf16::<10>("setTimeout"),
76
  &str_to_utf16::<6>("window"),
77
];
78

79
// Calculates the shortest & longest length of global var names
80
const MANAGED_GLOBALS_INFO: (usize, usize) = {
81
  let l = MANAGED_GLOBALS[0].len();
82
  let (mut longest, mut shortest, mut i) = (l, l, 1);
83
  while i < MANAGED_GLOBALS.len() {
84
    let l = MANAGED_GLOBALS[i].len();
85
    if l > longest {
86
      longest = l
87
    }
88
    if l < shortest {
89
      shortest = l
90
    }
91
    i += 1;
92
  }
93
  (shortest, longest)
94
};
95

96
const SHORTEST_MANAGED_GLOBAL: usize = MANAGED_GLOBALS_INFO.0;
97
const LONGEST_MANAGED_GLOBAL: usize = MANAGED_GLOBALS_INFO.1;
98

99
#[derive(Debug, Clone, Copy)]
100
enum Mode {
101
  Deno,
102
  Node,
103
}
104

105
struct GlobalsStorage {
106
  deno_globals: v8::Global<v8::Object>,
107
  node_globals: v8::Global<v8::Object>,
108
}
109

110
impl GlobalsStorage {
111
  fn inner_for_mode(&self, mode: Mode) -> v8::Global<v8::Object> {
892✔
112
    match mode {
892✔
113
      Mode::Deno => &self.deno_globals,
804✔
114
      Mode::Node => &self.node_globals,
88✔
115
    }
116
    .clone()
892✔
117
  }
892✔
118
}
119

120
pub fn global_template_middleware<'s>(
1✔
121
  _scope: &mut v8::HandleScope<'s, ()>,
1✔
122
  template: v8::Local<'s, v8::ObjectTemplate>,
1✔
123
) -> v8::Local<'s, v8::ObjectTemplate> {
1✔
124
  let mut config = v8::NamedPropertyHandlerConfiguration::new().flags(
1✔
125
    v8::PropertyHandlerFlags::NON_MASKING
1✔
126
      | v8::PropertyHandlerFlags::HAS_NO_SIDE_EFFECT,
1✔
127
  );
1✔
128

1✔
129
  config = GETTER_MAP_FN.with(|getter| config.getter_raw(*getter));
1✔
130
  config = SETTER_MAP_FN.with(|setter| config.setter_raw(*setter));
1✔
131
  config = QUERY_MAP_FN.with(|query| config.query_raw(*query));
1✔
132
  config = DELETER_MAP_FN.with(|deleter| config.deleter_raw(*deleter));
1✔
133
  config =
1✔
134
    ENUMERATOR_MAP_FN.with(|enumerator| config.enumerator_raw(*enumerator));
1✔
135
  config = DEFINER_MAP_FN.with(|definer| config.definer_raw(*definer));
1✔
136
  config =
1✔
137
    DESCRIPTOR_MAP_FN.with(|descriptor| config.descriptor_raw(*descriptor));
1✔
138

1✔
139
  template.set_named_property_handler(config);
1✔
140

1✔
141
  template
1✔
142
}
1✔
143

144
pub fn global_object_middleware<'s>(
274✔
145
  scope: &mut v8::HandleScope<'s>,
274✔
146
  global: v8::Local<'s, v8::Object>,
274✔
147
) {
274✔
148
  // ensure the global object is not Object.prototype
274✔
149
  let object_key =
274✔
150
    v8::String::new_external_onebyte_static(scope, b"Object").unwrap();
274✔
151
  let object = global
274✔
152
    .get(scope, object_key.into())
274✔
153
    .unwrap()
274✔
154
    .to_object(scope)
274✔
155
    .unwrap();
274✔
156
  let prototype_key =
274✔
157
    v8::String::new_external_onebyte_static(scope, b"prototype").unwrap();
274✔
158
  let object_prototype = object
274✔
159
    .get(scope, prototype_key.into())
274✔
160
    .unwrap()
274✔
161
    .to_object(scope)
274✔
162
    .unwrap();
274✔
163
  assert_ne!(global, object_prototype);
274✔
164

165
  // globalThis.__bootstrap.ext_node_denoGlobals and
166
  // globalThis.__bootstrap.ext_node_nodeGlobals are the objects that contain
167
  // the Deno and Node specific globals respectively. If they do not yet exist
168
  // on the global object, create them as null prototype objects.
169
  let bootstrap_key =
274✔
170
    v8::String::new_external_onebyte_static(scope, b"__bootstrap").unwrap();
274✔
171
  let bootstrap = match global.get(scope, bootstrap_key.into()) {
274✔
172
    Some(value) if value.is_object() => value.to_object(scope).unwrap(),
274✔
173
    Some(value) if value.is_undefined() => {
1✔
174
      let null = v8::null(scope);
1✔
175
      let obj =
1✔
176
        v8::Object::with_prototype_and_properties(scope, null.into(), &[], &[]);
1✔
177
      global.set(scope, bootstrap_key.into(), obj.into());
1✔
178
      obj
1✔
179
    }
180
    _ => panic!("__bootstrap should not be tampered with"),
×
181
  };
182
  let deno_globals_key =
274✔
183
    v8::String::new_external_onebyte_static(scope, b"ext_node_denoGlobals")
274✔
184
      .unwrap();
274✔
185
  let deno_globals = match bootstrap.get(scope, deno_globals_key.into()) {
274✔
186
    Some(value) if value.is_object() => value,
274✔
187
    Some(value) if value.is_undefined() => {
1✔
188
      let null = v8::null(scope);
1✔
189
      let obj =
1✔
190
        v8::Object::with_prototype_and_properties(scope, null.into(), &[], &[])
1✔
191
          .into();
1✔
192
      bootstrap.set(scope, deno_globals_key.into(), obj);
1✔
193
      obj
1✔
194
    }
195
    _ => panic!("__bootstrap.ext_node_denoGlobals should not be tampered with"),
×
196
  };
197
  let deno_globals_obj: v8::Local<v8::Object> =
274✔
198
    deno_globals.try_into().unwrap();
274✔
199
  let deno_globals = v8::Global::new(scope, deno_globals_obj);
274✔
200
  let node_globals_key =
274✔
201
    v8::String::new_external_onebyte_static(scope, b"ext_node_nodeGlobals")
274✔
202
      .unwrap();
274✔
203
  let node_globals = match bootstrap.get(scope, node_globals_key.into()) {
274✔
204
    Some(value) if value.is_object() => value,
274✔
205
    Some(value) if value.is_undefined() => {
1✔
206
      let null = v8::null(scope);
1✔
207
      let obj =
1✔
208
        v8::Object::with_prototype_and_properties(scope, null.into(), &[], &[])
1✔
209
          .into();
1✔
210
      bootstrap.set(scope, node_globals_key.into(), obj);
1✔
211
      obj
1✔
212
    }
213
    _ => panic!("__bootstrap.ext_node_nodeGlobals should not be tampered with"),
×
214
  };
215
  let node_globals_obj: v8::Local<v8::Object> =
274✔
216
    node_globals.try_into().unwrap();
274✔
217
  let node_globals = v8::Global::new(scope, node_globals_obj);
274✔
218

274✔
219
  // Create the storage struct and store it in a context slot.
274✔
220
  let storage = GlobalsStorage {
274✔
221
    deno_globals,
274✔
222
    node_globals,
274✔
223
  };
274✔
224
  scope.get_current_context().set_slot(storage);
274✔
225
}
274✔
226

227
fn is_managed_key(
9,658✔
228
  scope: &mut v8::HandleScope,
9,658✔
229
  key: v8::Local<v8::Name>,
9,658✔
230
) -> bool {
9,658✔
231
  let Ok(str): Result<v8::Local<v8::String>, _> = key.try_into() else {
9,658✔
232
    return false;
1,215✔
233
  };
234
  let len = str.length();
8,443✔
235

8,443✔
236
  #[allow(clippy::manual_range_contains)]
8,443✔
237
  if len < SHORTEST_MANAGED_GLOBAL || len > LONGEST_MANAGED_GLOBAL {
8,443✔
238
    return false;
590✔
239
  }
7,853✔
240
  let buf = &mut [0u16; LONGEST_MANAGED_GLOBAL];
7,853✔
241
  let written = str.write(
7,853✔
242
    scope,
7,853✔
243
    buf.as_mut_slice(),
7,853✔
244
    0,
7,853✔
245
    v8::WriteOptions::NO_NULL_TERMINATION,
7,853✔
246
  );
7,853✔
247
  assert_eq!(written, len);
7,853✔
248
  MANAGED_GLOBALS.binary_search(&&buf[..len]).is_ok()
7,853✔
249
}
9,658✔
250

251
fn current_mode(scope: &mut v8::HandleScope) -> Mode {
892✔
252
  let Some(host_defined_options) = scope.get_current_host_defined_options()
892✔
253
  else {
254
    return Mode::Deno;
×
255
  };
256
  // SAFETY: host defined options must always be a PrimitiveArray in current V8.
257
  let host_defined_options = unsafe {
892✔
258
    v8::Local::<v8::PrimitiveArray>::cast_unchecked(host_defined_options)
892✔
259
  };
892✔
260
  if host_defined_options.length() < 1 {
892✔
261
    return Mode::Deno;
804✔
262
  }
88✔
263
  let is_node = host_defined_options.get(scope, 0).is_true();
88✔
264
  if is_node {
88✔
265
    Mode::Node
88✔
266
  } else {
267
    Mode::Deno
×
268
  }
269
}
892✔
270

271
pub fn getter<'s>(
2,199✔
272
  scope: &mut v8::HandleScope<'s>,
2,199✔
273
  key: v8::Local<'s, v8::Name>,
2,199✔
274
  args: v8::PropertyCallbackArguments<'s>,
2,199✔
275
  mut rv: v8::ReturnValue,
2,199✔
276
) -> v8::Intercepted {
2,199✔
277
  if !is_managed_key(scope, key) {
2,199✔
278
    return v8::Intercepted::No;
1,327✔
279
  };
872✔
280

872✔
281
  let this = args.this();
872✔
282
  let mode = current_mode(scope);
872✔
283

872✔
284
  let context = scope.get_current_context();
872✔
285
  let inner = {
872✔
286
    let storage = context.get_slot::<GlobalsStorage>().unwrap();
872✔
287
    storage.inner_for_mode(mode)
872✔
288
  };
872✔
289
  let inner = v8::Local::new(scope, inner);
872✔
290

872✔
291
  if !inner.has_own_property(scope, key).unwrap_or(false) {
872✔
292
    return v8::Intercepted::No;
7✔
293
  }
865✔
294

295
  let Some(value) = inner.get_with_receiver(scope, key.into(), this) else {
865✔
296
    return v8::Intercepted::No;
×
297
  };
298

299
  rv.set(value);
865✔
300
  v8::Intercepted::Yes
865✔
301
}
2,199✔
302

303
pub fn setter<'s>(
1,021✔
304
  scope: &mut v8::HandleScope<'s>,
1,021✔
305
  key: v8::Local<'s, v8::Name>,
1,021✔
306
  value: v8::Local<'s, v8::Value>,
1,021✔
307
  args: v8::PropertyCallbackArguments<'s>,
1,021✔
308
  mut rv: v8::ReturnValue<()>,
1,021✔
309
) -> v8::Intercepted {
1,021✔
310
  if !is_managed_key(scope, key) {
1,021✔
311
    return v8::Intercepted::No;
1,021✔
312
  };
×
313

×
314
  let this = args.this();
×
315
  let mode = current_mode(scope);
×
316

×
317
  let context = scope.get_current_context();
×
318
  let inner = {
×
319
    let storage = context.get_slot::<GlobalsStorage>().unwrap();
×
320
    storage.inner_for_mode(mode)
×
321
  };
×
322
  let inner = v8::Local::new(scope, inner);
×
323

324
  let Some(success) = inner.set_with_receiver(scope, key.into(), value, this)
×
325
  else {
326
    return v8::Intercepted::No;
×
327
  };
328

329
  rv.set_bool(success);
×
330
  v8::Intercepted::Yes
×
331
}
1,021✔
332

333
pub fn query<'s>(
420✔
334
  scope: &mut v8::HandleScope<'s>,
420✔
335
  key: v8::Local<'s, v8::Name>,
420✔
336
  _args: v8::PropertyCallbackArguments<'s>,
420✔
337
  mut rv: v8::ReturnValue<v8::Integer>,
420✔
338
) -> v8::Intercepted {
420✔
339
  if !is_managed_key(scope, key) {
420✔
340
    return v8::Intercepted::No;
416✔
341
  };
4✔
342
  let mode = current_mode(scope);
4✔
343

4✔
344
  let context = scope.get_current_context();
4✔
345
  let inner = {
4✔
346
    let storage = context.get_slot::<GlobalsStorage>().unwrap();
4✔
347
    storage.inner_for_mode(mode)
4✔
348
  };
4✔
349
  let inner = v8::Local::new(scope, inner);
4✔
350

4✔
351
  let Some(true) = inner.has_own_property(scope, key) else {
4✔
352
    return v8::Intercepted::No;
4✔
353
  };
354

355
  let Some(attributes) = inner.get_property_attributes(scope, key.into())
×
356
  else {
357
    return v8::Intercepted::No;
×
358
  };
359

360
  rv.set_uint32(attributes.as_u32());
×
361
  v8::Intercepted::Yes
×
362
}
420✔
363

364
pub fn deleter<'s>(
×
365
  scope: &mut v8::HandleScope<'s>,
×
366
  key: v8::Local<'s, v8::Name>,
×
367
  args: v8::PropertyCallbackArguments<'s>,
×
368
  mut rv: v8::ReturnValue<v8::Boolean>,
×
369
) -> v8::Intercepted {
×
370
  if !is_managed_key(scope, key) {
×
371
    return v8::Intercepted::No;
×
372
  };
×
373

×
374
  let mode = current_mode(scope);
×
375

×
376
  let context = scope.get_current_context();
×
377
  let inner = {
×
378
    let storage = context.get_slot::<GlobalsStorage>().unwrap();
×
379
    storage.inner_for_mode(mode)
×
380
  };
×
381
  let inner = v8::Local::new(scope, inner);
×
382

383
  let Some(success) = inner.delete(scope, key.into()) else {
×
384
    return v8::Intercepted::No;
×
385
  };
386

387
  if args.should_throw_on_error() && !success {
×
388
    let message = v8::String::new(scope, "Cannot delete property").unwrap();
×
389
    let exception = v8::Exception::type_error(scope, message);
×
390
    scope.throw_exception(exception);
×
391
    return v8::Intercepted::Yes;
×
392
  }
×
393

×
394
  rv.set_bool(success);
×
395
  v8::Intercepted::Yes
×
396
}
×
397

398
pub fn enumerator<'s>(
2✔
399
  scope: &mut v8::HandleScope<'s>,
2✔
400
  _args: v8::PropertyCallbackArguments<'s>,
2✔
401
  mut rv: v8::ReturnValue<v8::Array>,
2✔
402
) {
2✔
403
  let mode = current_mode(scope);
2✔
404

2✔
405
  let context = scope.get_current_context();
2✔
406
  let inner = {
2✔
407
    let storage = context.get_slot::<GlobalsStorage>().unwrap();
2✔
408
    storage.inner_for_mode(mode)
2✔
409
  };
2✔
410
  let inner = v8::Local::new(scope, inner);
2✔
411

412
  let Some(array) = inner.get_property_names(
2✔
413
    scope,
2✔
414
    GetPropertyNamesArgs {
2✔
415
      mode: v8::KeyCollectionMode::OwnOnly,
2✔
416
      property_filter: v8::PropertyFilter::ALL_PROPERTIES,
2✔
417
      ..Default::default()
2✔
418
    },
2✔
419
  ) else {
2✔
420
    return;
×
421
  };
422

423
  rv.set(array);
2✔
424
}
2✔
425

426
pub fn definer<'s>(
2,802✔
427
  scope: &mut v8::HandleScope<'s>,
2,802✔
428
  key: v8::Local<'s, v8::Name>,
2,802✔
429
  descriptor: &v8::PropertyDescriptor,
2,802✔
430
  args: v8::PropertyCallbackArguments<'s>,
2,802✔
431
  _rv: v8::ReturnValue<()>,
2,802✔
432
) -> v8::Intercepted {
2,802✔
433
  if !is_managed_key(scope, key) {
2,802✔
434
    return v8::Intercepted::No;
2,795✔
435
  };
7✔
436

7✔
437
  let mode = current_mode(scope);
7✔
438

7✔
439
  let context = scope.get_current_context();
7✔
440
  let inner = {
7✔
441
    let storage = context.get_slot::<GlobalsStorage>().unwrap();
7✔
442
    storage.inner_for_mode(mode)
7✔
443
  };
7✔
444
  let inner = v8::Local::new(scope, inner);
7✔
445

446
  let Some(success) = inner.define_property(scope, key, descriptor) else {
7✔
447
    return v8::Intercepted::No;
×
448
  };
449

450
  if args.should_throw_on_error() && !success {
7✔
451
    let message = v8::String::new(scope, "Cannot define property").unwrap();
×
452
    let exception = v8::Exception::type_error(scope, message);
×
453
    scope.throw_exception(exception);
×
454
  }
7✔
455

456
  v8::Intercepted::Yes
7✔
457
}
2,802✔
458

459
pub fn descriptor<'s>(
3,216✔
460
  scope: &mut v8::HandleScope<'s>,
3,216✔
461
  key: v8::Local<'s, v8::Name>,
3,216✔
462
  _args: v8::PropertyCallbackArguments<'s>,
3,216✔
463
  mut rv: v8::ReturnValue,
3,216✔
464
) -> v8::Intercepted {
3,216✔
465
  if !is_managed_key(scope, key) {
3,216✔
466
    return v8::Intercepted::No;
3,209✔
467
  };
7✔
468

7✔
469
  let mode = current_mode(scope);
7✔
470

7✔
471
  let scope = &mut v8::TryCatch::new(scope);
7✔
472

7✔
473
  let context = scope.get_current_context();
7✔
474
  let inner = {
7✔
475
    let storage = context.get_slot::<GlobalsStorage>().unwrap();
7✔
476
    storage.inner_for_mode(mode)
7✔
477
  };
7✔
478
  let inner = v8::Local::new(scope, inner);
7✔
479

480
  let Some(descriptor) = inner.get_own_property_descriptor(scope, key) else {
7✔
481
    scope.rethrow().expect("to have caught an exception");
×
482
    return v8::Intercepted::Yes;
×
483
  };
484

485
  if descriptor.is_undefined() {
7✔
486
    return v8::Intercepted::No;
7✔
487
  }
×
488

×
489
  rv.set(descriptor);
×
490
  v8::Intercepted::Yes
×
491
}
3,216✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc