Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 176 additions & 0 deletions rhino/src/main/java/org/mozilla/javascript/NativeWeakRef.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.javascript;

import java.lang.ref.WeakReference;

/**
* Implementation of the ES2021 WeakRef constructor and prototype.
*
* <p>WeakRef allows holding a weak reference to an object without preventing its garbage
* collection. This is useful for caches, mappings, or any scenario where you want to reference an
* object without keeping it alive.
*
* <p>The WeakRef object provides a single method, deref(), which returns the referenced object if
* it's still alive, or undefined if it has been collected.
*
* @see <a href="https://tc39.es/ecma262/#sec-weak-ref-objects">ECMAScript WeakRef Objects</a>
*/
public class NativeWeakRef extends ScriptableObject {
private static final long serialVersionUID = 1L;
private static final String CLASS_NAME = "WeakRef";
private static final String DEREF_METHOD = "deref";

private static final String MSG_NO_TARGET = "msg.weakref.no.target";
private static final String MSG_TARGET_NOT_OBJECT = "msg.weakref.target.not.object";
private static final int CONSTRUCTOR_ARITY = 1;
private static final int DEREF_ARITY = 0;

private WeakReference<Object> weakReference;

/**
* Initializes the WeakRef constructor and prototype in the given scope.
*
* @param cx the JavaScript context
* @param scope the scope to initialize in
* @param sealed whether to seal the constructor and prototype
* @return the WeakRef constructor
*/
public static Object init(Context cx, Scriptable scope, boolean sealed) {
LambdaConstructor constructor = createConstructor(scope);
configurePrototype(constructor, scope);

if (sealed) {
sealConstructor(constructor);
}
return constructor;
}

private static LambdaConstructor createConstructor(Scriptable scope) {
LambdaConstructor constructor =
new LambdaConstructor(
scope,
CLASS_NAME,
CONSTRUCTOR_ARITY,
LambdaConstructor.CONSTRUCTOR_NEW,
NativeWeakRef::constructor);
constructor.setPrototypePropertyAttributes(DONTENUM | READONLY | PERMANENT);
return constructor;
}

private static void configurePrototype(LambdaConstructor constructor, Scriptable scope) {
// Define prototype methods
constructor.definePrototypeMethod(
scope,
DEREF_METHOD,
DEREF_ARITY,
NativeWeakRef::deref,
DONTENUM,
DONTENUM | READONLY);

// Define Symbol.toStringTag
constructor.definePrototypeProperty(
SymbolKey.TO_STRING_TAG, CLASS_NAME, DONTENUM | READONLY);
}

private static void sealConstructor(LambdaConstructor constructor) {
constructor.sealObject();
ScriptableObject prototype = (ScriptableObject) constructor.getPrototypeProperty();
if (prototype != null) {
prototype.sealObject();
}
}

/**
* WeakRef constructor implementation. Creates a new WeakRef instance holding a weak reference
* to the target object.
*
* @param cx the current context
* @param scope the scope
* @param args constructor arguments, expects exactly one object argument
* @return the new WeakRef instance
* @throws TypeError if no argument provided or argument is not an object
*/
private static Scriptable constructor(Context cx, Scriptable scope, Object[] args) {
validateConstructorArgs(args);
return createWeakRef(args[0]);
}

private static NativeWeakRef createWeakRef(Object target) {
NativeWeakRef ref = new NativeWeakRef();
ref.weakReference = new WeakReference<>(target);
return ref;
}

/**
* Validates constructor arguments according to ES2021 spec.
*
* @param args the constructor arguments
* @throws TypeError if validation fails
*/
private static void validateConstructorArgs(Object[] args) {
if (args.length < 1) {
throw ScriptRuntime.typeErrorById(MSG_NO_TARGET);
}

Object target = args[0];
if (!isValidTarget(target)) {
throw ScriptRuntime.typeErrorById(MSG_TARGET_NOT_OBJECT);
}
}

/**
* Checks if a value is a valid WeakRef target according to the CanBeHeldWeakly spec. Objects
* and unregistered symbols can be held weakly, but registered symbols (created with
* Symbol.for()) cannot.
*
* @param target the value to check
* @return true if the target can be held weakly
*/
private static boolean isValidTarget(Object target) {
// Use the same logic as NativeWeakMap for consistency
return ScriptRuntime.isUnregisteredSymbol(target) || ScriptRuntime.isObject(target);
}

/**
* WeakRef.prototype.deref() implementation. Returns the WeakRef's target object if it's still
* alive, or undefined if it has been collected.
*
* @param cx the current context
* @param scope the scope
* @param thisObj the 'this' object
* @param args method arguments (none expected)
* @return the target object or undefined
*/
private static Object deref(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
NativeWeakRef self = ensureWeakRef(thisObj);
return self.dereference();
}

private static NativeWeakRef ensureWeakRef(Scriptable thisObj) {
return LambdaConstructor.convertThisObject(thisObj, NativeWeakRef.class);
}

/**
* Dereferences the weak reference.
*
* @return the target object if still alive, or Undefined.instance if collected
*/
private Object dereference() {
if (weakReference == null) {
return Undefined.instance;
}

Object target = weakReference.get();
return (target == null) ? Undefined.instance : target;
}

@Override
public String getClassName() {
return CLASS_NAME;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ public static ScriptableObject initSafeStandardObjects(
new LazilyLoadedCtor(scope, "BigInt", sealed, true, NativeBigInt::init);
new LazilyLoadedCtor(scope, "Proxy", sealed, true, NativeProxy::init);
new LazilyLoadedCtor(scope, "Reflect", sealed, true, NativeReflect::init);
new LazilyLoadedCtor(scope, "WeakRef", sealed, true, NativeWeakRef::init);
}

if (scope instanceof TopLevel) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1021,6 +1021,13 @@ msg.promise.all.toobig =\
msg.promise.any.toobig =\
Too many inputs to Promise.any

# WeakRef
msg.weakref.no.target =\
WeakRef constructor requires an object argument

msg.weakref.target.not.object =\
WeakRef target must be an object

msg.typed.array.receiver.incompatible = \
Method %TypedArray%.{0} called on incompatible receiver

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.javascript.tests.es2025;

import org.junit.Test;
import org.mozilla.javascript.testutils.Utils;

public class WeakRefTest {

@Test
public void weakRefConstructor() {
Utils.assertWithAllModes_ES6(
true,
"var obj = { value: 42 }; var ref = new WeakRef(obj); ref instanceof WeakRef");
}

@Test
public void weakRefDeref() {
String script =
"var obj = { value: 42 }; "
+ "var ref = new WeakRef(obj); "
+ "var derefed = ref.deref(); "
+ "derefed === obj && derefed.value === 42";
Utils.assertWithAllModes_ES6(true, script);
}

@Test
public void weakRefDerefMultipleTimes() {
Utils.assertWithAllModes_ES6(
true,
"var obj = { value: 42 }; var ref = new WeakRef(obj); ref.deref() === ref.deref()");
}

@Test
public void weakRefToStringTag() {
Utils.assertWithAllModes_ES6(
"[object WeakRef]",
"var ref = new WeakRef({}); Object.prototype.toString.call(ref)");
}

@Test
public void weakRefRequiresNew() {
Utils.assertWithAllModes_ES6(
true, "try { WeakRef({}); false; } catch(e) { e instanceof TypeError; }");
}

@Test
public void weakRefRequiresObject() {
Utils.assertWithAllModes_ES6(
true, "try { new WeakRef(42); false; } catch(e) { e instanceof TypeError; }");
}

@Test
public void weakRefNullTarget() {
Utils.assertWithAllModes_ES6(
true, "try { new WeakRef(null); false; } catch(e) { e instanceof TypeError; }");
}

@Test
public void weakRefUndefinedTarget() {
Utils.assertWithAllModes_ES6(
true,
"try { new WeakRef(undefined); false; } catch(e) { e instanceof TypeError; }");
}

@Test
public void weakRefNoArguments() {
Utils.assertWithAllModes_ES6(
true, "try { new WeakRef(); false; } catch(e) { e instanceof TypeError; }");
}

@Test
public void weakRefAcceptsUnregisteredSymbol() {
Utils.assertWithAllModes_ES6(
true, "var s = Symbol('test'); var w = new WeakRef(s); w.deref() === s");
}

@Test
public void weakRefRejectsRegisteredSymbol() {
Utils.assertWithAllModes_ES6(
true,
"try { new WeakRef(Symbol.for('registered')); false; } catch(e) { e instanceof TypeError; }");
}

@Test
public void weakRefWithArray() {
Utils.assertWithAllModes_ES6(
true, "var arr = [1,2,3]; var ref = new WeakRef(arr); ref.deref() === arr");
}

@Test
public void weakRefWithFunction() {
Utils.assertWithAllModes_ES6(
true, "var fn = function(){}; var ref = new WeakRef(fn); ref.deref() === fn");
}

@Test
public void weakRefWithRegExp() {
Utils.assertWithAllModes_ES6(
true, "var rx = /test/; var ref = new WeakRef(rx); ref.deref() === rx");
}

@Test
public void weakRefTypeofFunction() {
Utils.assertWithAllModes_ES6(true, "typeof WeakRef === 'function'");
}

@Test
public void weakRefPrototypeDeref() {
Utils.assertWithAllModes_ES6(true, "typeof WeakRef.prototype.deref === 'function'");
}

@Test
public void weakRefConstructorLength() {
Utils.assertWithAllModes_ES6(true, "WeakRef.length === 1");
}

@Test
public void weakRefConstructorName() {
Utils.assertWithAllModes_ES6(true, "WeakRef.name === 'WeakRef'");
}

@Test
public void weakRefDerefLength() {
Utils.assertWithAllModes_ES6(true, "WeakRef.prototype.deref.length === 0");
}

@Test
public void weakRefHasCorrectPrototype() {
Utils.assertWithAllModes_ES6(
true,
"var ref = new WeakRef({}); Object.getPrototypeOf(ref) === WeakRef.prototype");
}

@Test
public void weakRefDerefCallContext() {
Utils.assertWithAllModes_ES6(
true,
"try { WeakRef.prototype.deref.call({}); false; } catch(e) { e instanceof TypeError; }");
}

@Test
public void weakRefDerefApplyContext() {
Utils.assertWithAllModes_ES6(
true,
"try { WeakRef.prototype.deref.apply(null); false; } catch(e) { e instanceof TypeError; }");
}

@Test
public void weakRefConstructorPropertyDescriptor() {
Utils.assertWithAllModes_ES6(
true,
"var desc = Object.getOwnPropertyDescriptor(WeakRef.prototype, 'constructor'); desc.writable === true && desc.enumerable === false && desc.configurable === true");
}

@Test
public void weakRefDerefPropertyDescriptor() {
Utils.assertWithAllModes_ES6(
true,
"var desc = Object.getOwnPropertyDescriptor(WeakRef.prototype, 'deref'); desc.writable === true && desc.enumerable === false && desc.configurable === true");
}

@Test
public void weakRefToStringTagDescriptor() {
Utils.assertWithAllModes_ES6(
true,
"var desc = Object.getOwnPropertyDescriptor(WeakRef.prototype, Symbol.toStringTag); desc.value === 'WeakRef' && desc.writable === false && desc.enumerable === false && desc.configurable === true");
}

@Test
public void weakRefNotAvailableInES5() {
Utils.assertWithAllModes_1_8("undefined", "typeof WeakRef");
}

@Test
public void weakRefAvailableInES6() {
Utils.assertWithAllModes_ES6("function", "typeof WeakRef");
}

@Test
public void weakRefWithManyArguments() {
Utils.assertWithAllModes_ES6(
true,
"var obj = {}; var ref = new WeakRef(obj, 'extra', 'args', 'ignored'); ref.deref() === obj");
}

@Test
public void weakRefDerefWithArguments() {
Utils.assertWithAllModes_ES6(
true,
"var obj = {}; var ref = new WeakRef(obj); ref.deref('arg1', 'arg2') === obj");
}
}
Loading