Skip to content

Commit ee2e927

Browse files
committed
Preserve referential equality for rules
1 parent 0393f61 commit ee2e927

File tree

2 files changed

+40
-9
lines changed

2 files changed

+40
-9
lines changed

node/src/rule_list.rs

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use napi::{CallContext, JsNumber, JsObject, JsUndefined, JsUnknown, JsFunction, Property, Ref, Result, Env};
1+
use napi::{CallContext, sys, JsNumber, JsObject, NapiRaw, NapiValue, JsUndefined, JsUnknown, JsFunction, Property, Ref, Result, Env};
22
use std::cell::RefCell;
33
use parcel_css::stylesheet::StyleSheet;
44
use std::rc::Rc;
@@ -7,6 +7,11 @@ use crate::rule;
77
// TODO: this is probably not safe.
88
static mut CLASS: RefCell<Option<Ref<()>>> = RefCell::new(None);
99

10+
struct CSSRuleList {
11+
stylesheet: Rc<RefCell<StyleSheet>>,
12+
rules: Vec<sys::napi_ref>
13+
}
14+
1015
#[js_function(0)]
1116
fn constructor(ctx: CallContext) -> Result<JsUndefined> {
1217
ctx.env.get_undefined()
@@ -15,17 +20,42 @@ fn constructor(ctx: CallContext) -> Result<JsUndefined> {
1520
#[js_function(0)]
1621
fn get_length(ctx: CallContext) -> Result<JsNumber> {
1722
let this: JsObject = ctx.this_unchecked();
18-
let stylesheet: &mut Rc<RefCell<StyleSheet>> = ctx.env.unwrap(&this)?;
19-
ctx.env.create_uint32(stylesheet.borrow().rules.0.len() as u32)
23+
let list: &mut CSSRuleList = ctx.env.unwrap(&this)?;
24+
ctx.env.create_uint32(list.stylesheet.borrow().rules.0.len() as u32)
2025
}
2126

2227
#[js_function(1)]
2328
fn item(ctx: CallContext) -> Result<JsObject> {
2429
let this: JsObject = ctx.this_unchecked();
25-
let index = ctx.get::<JsNumber>(0)?.get_uint32()?;
26-
let stylesheet: &mut Rc<RefCell<StyleSheet>> = ctx.env.unwrap(&this)?;
27-
let r = stylesheet.borrow().rules.0[index as usize].clone();
28-
rule::create(ctx.env, r)
30+
let index = ctx.get::<JsNumber>(0)?.get_uint32()? as usize;
31+
let list: &mut CSSRuleList = ctx.env.unwrap(&this)?;
32+
33+
// See if we already have a JS object created for this rule to preserve referential equality.
34+
// e.g. rules.item(0) === rules.item(0)
35+
// This drops down to low level C bindings for napi because there is currently no way
36+
// to create a weak reference in napi-rs.
37+
match &list.rules.get(index) {
38+
Some(r) if !r.is_null() => {
39+
let mut js_value = std::ptr::null_mut();
40+
unsafe {
41+
sys::napi_get_reference_value(ctx.env.raw(), **r, &mut js_value);
42+
if !js_value.is_null() {
43+
return JsObject::from_raw(ctx.env.raw(), js_value)
44+
}
45+
};
46+
}
47+
_ => {}
48+
};
49+
50+
let r = list.stylesheet.borrow().rules.0[index].clone();
51+
let obj = rule::create(ctx.env, r)?;
52+
while list.rules.len() <= index {
53+
list.rules.push(std::ptr::null_mut());
54+
}
55+
unsafe {
56+
sys::napi_create_reference(ctx.env.raw(), obj.raw(), 0, &mut list.rules[index]);
57+
};
58+
Ok(obj)
2959
}
3060

3161
pub fn init(exports: &mut JsObject, env: Env) -> Result<()> {
@@ -41,11 +71,11 @@ pub fn init(exports: &mut JsObject, env: Env) -> Result<()> {
4171
Ok(())
4272
}
4373

44-
pub fn create(env: &Env, list: Rc<RefCell<StyleSheet>>) -> Result<JsObject> {
74+
pub fn create(env: &Env, stylesheet: Rc<RefCell<StyleSheet>>) -> Result<JsObject> {
4575
let r = unsafe { CLASS.borrow() };
4676
let r = r.as_ref().unwrap();
4777
let c: JsFunction = env.get_reference_value(&r)?;
4878
let mut instance = c.new::<JsUnknown>(&[])?;
49-
env.wrap(&mut instance, list)?;
79+
env.wrap(&mut instance, CSSRuleList { stylesheet, rules: Vec::new() })?;
5080
Ok(instance)
5181
}

test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ stylesheet.replaceSync(`
1919

2020
console.log(rules.length)
2121
console.log(rules.item(0) instanceof CSSStyleRule, rules.item(0) instanceof CSSRule);
22+
console.log(rules.item(0) === rules.item(0));
2223
console.log(rules.item(0).cssText);
2324
console.log(rules.item(0).selectorText);
2425

0 commit comments

Comments
 (0)