diff --git a/rhino/src/main/java/org/mozilla/javascript/LambdaAccessorSlot.java b/rhino/src/main/java/org/mozilla/javascript/LambdaAccessorSlot.java
new file mode 100644
index 0000000000..18538473c5
--- /dev/null
+++ b/rhino/src/main/java/org/mozilla/javascript/LambdaAccessorSlot.java
@@ -0,0 +1,129 @@
+package org.mozilla.javascript;
+
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+
+/**
+ * A specialized property accessor using lambda functions, similar to {@link LambdaSlot}, but allows
+ * defining properties with getter and setter lambdas that require access to the owner object
+ * ('this'). This enables the implementation of properties that can access instance fields of the
+ * owner.
+ *
+ *
Unlike {@link LambdaSlot}, Lambda functions used to define getter and setter logic require the
+ * owner's `Scriptable` object as one of the parameters. This is particularly useful for
+ * implementing properties that behave like standard JavaScript properties, but are implemented with
+ * native functionality without the need for reflection.
+ */
+public class LambdaAccessorSlot extends Slot {
+ private transient Function getter;
+ private transient BiConsumer setter;
+ private LambdaFunction getterFunction;
+ private LambdaFunction setterFunction;
+
+ LambdaAccessorSlot(Object name, int index) {
+ super(name, index, 0);
+ }
+
+ LambdaAccessorSlot(Slot oldSlot) {
+ super(oldSlot);
+ }
+
+ @Override
+ boolean isValueSlot() {
+ return false;
+ }
+
+ @Override
+ boolean isSetterSlot() {
+ return true;
+ }
+
+ @Override
+ ScriptableObject getPropertyDescriptor(Context cx, Scriptable scope) {
+ ScriptableObject desc = (ScriptableObject) cx.newObject(scope);
+
+ int attr = getAttributes();
+ boolean es6 = cx.getLanguageVersion() >= Context.VERSION_ES6;
+ if (es6) {
+ if (getterFunction == null && setterFunction == null) {
+ desc.defineProperty(
+ "writable",
+ (attr & ScriptableObject.READONLY) == 0,
+ ScriptableObject.EMPTY);
+ }
+ } else {
+ desc.setCommonDescriptorProperties(
+ attr, getterFunction == null && setterFunction == null);
+ }
+
+ if (getterFunction != null) {
+ desc.defineProperty("get", this.getterFunction, ScriptableObject.EMPTY);
+ }
+
+ if (setterFunction != null) {
+ desc.defineProperty("set", this.setterFunction, ScriptableObject.EMPTY);
+ } else if (es6) {
+ desc.defineProperty("set", Undefined.instance, ScriptableObject.EMPTY);
+ }
+
+ if (es6) {
+ desc.defineProperty(
+ "enumerable", (attr & ScriptableObject.DONTENUM) == 0, ScriptableObject.EMPTY);
+ desc.defineProperty(
+ "configurable",
+ (attr & ScriptableObject.PERMANENT) == 0,
+ ScriptableObject.EMPTY);
+ }
+ return desc;
+ }
+
+ @Override
+ public boolean setValue(Object value, Scriptable scope, Scriptable start, boolean isThrow) {
+ if (setter == null) {
+ if (getter != null) {
+ throwNoSetterException(start, value);
+ return true;
+ }
+ } else {
+ setter.accept(start, value);
+ return true;
+ }
+
+ return super.setValue(value, start, start, isThrow);
+ }
+
+ @Override
+ public Object getValue(Scriptable owner) {
+ if (getter != null) {
+ return getter.apply(owner);
+ }
+ return super.getValue(owner);
+ }
+
+ public void setGetter(Scriptable scope, Function getter) {
+ this.getter = getter;
+ if (getter != null) {
+ this.getterFunction =
+ new LambdaFunction(
+ scope,
+ "get " + super.name,
+ 0,
+ (cx1, scope1, thisObj, args) -> getter.apply(thisObj));
+ }
+ }
+
+ public void setSetter(Scriptable scope, BiConsumer setter) {
+ this.setter = setter;
+ if (setter != null) {
+ this.setterFunction =
+ new LambdaFunction(
+ scope,
+ "set " + super.name,
+ 1,
+ (cx1, scope1, thisObj, args) -> {
+ setter.accept(thisObj, args[0]);
+ return Undefined.instance;
+ });
+ }
+ }
+}
diff --git a/rhino/src/main/java/org/mozilla/javascript/LambdaConstructor.java b/rhino/src/main/java/org/mozilla/javascript/LambdaConstructor.java
index 03c36c0b22..d0a8c0e86e 100644
--- a/rhino/src/main/java/org/mozilla/javascript/LambdaConstructor.java
+++ b/rhino/src/main/java/org/mozilla/javascript/LambdaConstructor.java
@@ -6,6 +6,9 @@
package org.mozilla.javascript;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+
/**
* This class implements a JavaScript function that may be used as a constructor by delegating to an
* interface that can be easily implemented as a lambda. The LambdaFunction class may be used to add
@@ -120,6 +123,25 @@ public void definePrototypeProperty(Symbol key, Object value, int attributes) {
proto.defineProperty(key, value, attributes);
}
+ public void definePrototypeProperty(
+ Context cx,
+ String name,
+ java.util.function.Function getter,
+ int attributes) {
+ ScriptableObject proto = getPrototypeScriptable();
+ proto.defineProperty(cx, name, getter, null, attributes);
+ }
+
+ public void definePrototypeProperty(
+ Context cx,
+ String name,
+ Function getter,
+ BiConsumer setter,
+ int attributes) {
+ ScriptableObject proto = getPrototypeScriptable();
+ proto.defineProperty(cx, name, getter, setter, attributes);
+ }
+
/**
* Define a function property directly on the constructor that is implemented under the covers
* by a LambdaFunction.
diff --git a/rhino/src/main/java/org/mozilla/javascript/ScriptableObject.java b/rhino/src/main/java/org/mozilla/javascript/ScriptableObject.java
index abe2881e6e..a01c62674f 100644
--- a/rhino/src/main/java/org/mozilla/javascript/ScriptableObject.java
+++ b/rhino/src/main/java/org/mozilla/javascript/ScriptableObject.java
@@ -28,6 +28,7 @@
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
+import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.mozilla.javascript.ScriptRuntime.StringIdOrIndex;
@@ -1690,6 +1691,63 @@ public void defineProperty(
slot.setter = setter;
}
+ /**
+ * Define a property on this object that is implemented using lambda functions accepting
+ * Scriptable `this` object as first parameter. Unlike with `defineProperty(String name,
+ * Supplier